├── ChatClient
├── .flowconfig
├── .gitignore
├── .watchmanconfig
├── Chat.js
├── Phoenix.js
├── Root.jsx
├── android
│ ├── app
│ │ ├── build.gradle
│ │ ├── proguard-rules.pro
│ │ ├── react.gradle
│ │ └── src
│ │ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── chatclient
│ │ │ │ └── MainActivity.java
│ │ │ └── res
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ └── values
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── index.android.js
├── index.ios.js
├── ios
│ ├── ChatClient.xcodeproj
│ │ ├── project.pbxproj
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── ChatClient.xcscheme
│ ├── ChatClient
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Base.lproj
│ │ │ └── LaunchScreen.xib
│ │ ├── Images.xcassets
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ └── main.m
│ └── ChatClientTests
│ │ ├── ChatClientTests.m
│ │ └── Info.plist
└── package.json
├── ChatServer
├── .gitignore
├── Procfile
├── README.md
├── brunch-config.js
├── config
│ ├── config.exs
│ ├── dev.exs
│ ├── prod.exs
│ ├── prod.secret.exs
│ └── test.exs
├── elixir_buildpack.config
├── lib
│ ├── chat.ex
│ └── chat
│ │ ├── endpoint.ex
│ │ └── repo.ex
├── mix.exs
├── mix.lock
├── package.json
├── priv
│ └── static
│ │ ├── css
│ │ ├── app.css
│ │ └── app.css.map
│ │ ├── images
│ │ └── phoenix.png
│ │ └── js
│ │ ├── app.js
│ │ └── app.js.map
├── test
│ ├── chat_test.exs
│ └── test_helper.exs
└── web
│ ├── channels
│ ├── room_channel.ex
│ └── user_socket.ex
│ ├── controllers
│ └── page_controller.ex
│ ├── router.ex
│ ├── static
│ ├── css
│ │ └── app.css
│ ├── js
│ │ └── app.js
│ └── vendor
│ │ ├── bootstrap-theme.min.css
│ │ ├── bootstrap.css
│ │ ├── jquery.min.js
│ │ └── phoenix.js
│ ├── templates
│ ├── layout
│ │ └── app.html.eex
│ └── page
│ │ └── index.html.eex
│ ├── views
│ ├── error_view.ex
│ ├── layout_view.ex
│ └── page_view.ex
│ └── web.ex
├── LICENSE
├── README.md
└── images
└── screenshot.png
/ChatClient/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 |
3 | # We fork some components by platform.
4 | .*/*.web.js
5 | .*/*.android.js
6 |
7 | # Some modules have their own node_modules with overlap
8 | .*/node_modules/node-haste/.*
9 |
10 | # Ugh
11 | .*/node_modules/babel.*
12 | .*/node_modules/babylon.*
13 | .*/node_modules/invariant.*
14 |
15 | # Ignore react and fbjs where there are overlaps, but don't ignore
16 | # anything that react-native relies on
17 | .*/node_modules/fbjs/lib/Map.js
18 | .*/node_modules/fbjs/lib/Promise.js
19 | .*/node_modules/fbjs/lib/fetch.js
20 | .*/node_modules/fbjs/lib/ExecutionEnvironment.js
21 | .*/node_modules/fbjs/lib/isEmpty.js
22 | .*/node_modules/fbjs/lib/crc32.js
23 | .*/node_modules/fbjs/lib/ErrorUtils.js
24 |
25 | # Flow has a built-in definition for the 'react' module which we prefer to use
26 | # over the currently-untyped source
27 | .*/node_modules/react/react.js
28 | .*/node_modules/react/lib/React.js
29 | .*/node_modules/react/lib/ReactDOM.js
30 |
31 | # Ignore commoner tests
32 | .*/node_modules/commoner/test/.*
33 |
34 | # See https://github.com/facebook/flow/issues/442
35 | .*/react-tools/node_modules/commoner/lib/reader.js
36 |
37 | # Ignore jest
38 | .*/node_modules/jest-cli/.*
39 |
40 | # Ignore Website
41 | .*/website/.*
42 |
43 | [include]
44 |
45 | [libs]
46 | node_modules/react-native/Libraries/react-native/react-native-interface.js
47 |
48 | [options]
49 | module.system=haste
50 |
51 | munge_underscores=true
52 |
53 | module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub'
54 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.png$' -> 'RelativeImageStub'
55 |
56 | suppress_type=$FlowIssue
57 | suppress_type=$FlowFixMe
58 | suppress_type=$FixMe
59 |
60 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-0]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
61 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-0]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
62 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
63 |
64 | [version]
65 | 0.20.1
66 |
--------------------------------------------------------------------------------
/ChatClient/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | project.xcworkspace
24 |
25 | # Android/IJ
26 | #
27 | .idea
28 | .gradle
29 | local.properties
30 |
31 | # node.js
32 | #
33 | node_modules/
34 | npm-debug.log
35 |
--------------------------------------------------------------------------------
/ChatClient/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/ChatClient/Chat.js:
--------------------------------------------------------------------------------
1 | import { Socket } from './Phoenix'
2 |
3 | const TIMEOUT = 10000
4 | const URL = 'http://localhost:4000/socket'
5 | const LOBBY = 'rooms:lobby'
6 |
7 | export default (user, onChat) => {
8 | // construct a socket
9 | const socket = new Socket(URL)
10 |
11 | // configure the event handlers
12 | socket.onOpen(event => console.log('Connected.'))
13 | socket.onError(event => console.log('Cannot connect.'))
14 | socket.onClose(event => console.log('Goodbye.'))
15 |
16 | // open a connection to the server
17 | socket.connect()
18 |
19 | // configure a channel into a room - https://www.youtube.com/watch?v=vWFX4ylV_ko
20 | const chan = socket.channel(LOBBY, { user })
21 |
22 | // join the channel and listen for admittance
23 | chan.join()
24 | .receive('ignore', () => console.log('Access denied.'))
25 | .receive('ok', () => console.log('Access granted.'))
26 | .receive('timeout', () => console.log('Must be a MongoDB.'))
27 |
28 | // add some channel-level event handlers
29 | chan.onError(event => console.log('Channel blew up.'))
30 | chan.onClose(event => console.log('Channel closed.'))
31 |
32 | // when we receive a new chat message, just trigger the appropriate callback
33 | chan.on('new:msg', msg => onChat && onChat(msg))
34 |
35 | // you can can listen to multiple types
36 | chan.on('user:entered', msg => console.log('say hello to ', msg))
37 |
38 | // a function to shut it all down
39 | const close = () => socket.disconnect()
40 |
41 | // a function to send a message
42 | const send = (message) => {
43 | chan.push('new:msg', {body: message, user}, TIMEOUT)
44 | .receive('ok', (msg) => console.log('sent'))
45 | .receive('error', (reasons) => console.log('flop', reasons))
46 | .receive('timeout', () => console.log('slow much?'))
47 | }
48 |
49 | // reveal a couple ways to drive this bus
50 | return { close, send }
51 | }
52 |
--------------------------------------------------------------------------------
/ChatClient/Phoenix.js:
--------------------------------------------------------------------------------
1 | // Phoenix Channels JavaScript client
2 | //
3 | // ## Socket Connection
4 | //
5 | // A single connection is established to the server and
6 | // channels are mulitplexed over the connection.
7 | // Connect to the server using the `Socket` class:
8 | //
9 | // let socket = new Socket("/ws", {params: {userToken: "123"}})
10 | // socket.connect()
11 | //
12 | // The `Socket` constructor takes the mount point of the socket,
13 | // the authentication params, as well as options that can be found in
14 | // the Socket docs, such as configuring the `LongPoll` transport, and
15 | // heartbeat.
16 | //
17 | // ## Channels
18 | //
19 | // Channels are isolated, concurrent processes on the server that
20 | // subscribe to topics and broker events between the client and server.
21 | // To join a channel, you must provide the topic, and channel params for
22 | // authorization. Here's an example chat room example where `"new_msg"`
23 | // events are listened for, messages are pushed to the server, and
24 | // the channel is joined with ok/error/timeout matches:
25 | //
26 | // let channel = socket.channel("rooms:123", {token: roomToken})
27 | // channel.on("new_msg", msg => console.log("Got message", msg) )
28 | // $input.onEnter( e => {
29 | // channel.push("new_msg", {body: e.target.val}, 10000)
30 | // .receive("ok", (msg) => console.log("created message", msg) )
31 | // .receive("error", (reasons) => console.log("create failed", reasons) )
32 | // .receive("timeout", () => console.log("Networking issue...") )
33 | // })
34 | // channel.join()
35 | // .receive("ok", ({messages}) => console.log("catching up", messages) )
36 | // .receive("error", ({reason}) => console.log("failed join", reason) )
37 | // .receive("timeout", () => console.log("Networking issue. Still waiting...") )
38 | //
39 | //
40 | // ## Joining
41 | //
42 | // Creating a channel with `socket.channel(topic, params)`, binds the params to
43 | // `channel.params`, which are sent up on `channel.join()`.
44 | // Subsequent rejoins will send up the modified params for
45 | // updating authorization params, or passing up last_message_id information.
46 | // Successful joins receive an "ok" status, while unsuccessful joins
47 | // receive "error".
48 | //
49 | //
50 | // ## Pushing Messages
51 | //
52 | // From the previous example, we can see that pushing messages to the server
53 | // can be done with `channel.push(eventName, payload)` and we can optionally
54 | // receive responses from the push. Additionally, we can use
55 | // `receive("timeout", callback)` to abort waiting for our other `receive` hooks
56 | // and take action after some period of waiting. The default timeout is 5000ms.
57 | //
58 | //
59 | // ## Socket Hooks
60 | //
61 | // Lifecycle events of the multiplexed connection can be hooked into via
62 | // `socket.onError()` and `socket.onClose()` events, ie:
63 | //
64 | // socket.onError( () => console.log("there was an error with the connection!") )
65 | // socket.onClose( () => console.log("the connection dropped") )
66 | //
67 | //
68 | // ## Channel Hooks
69 | //
70 | // For each joined channel, you can bind to `onError` and `onClose` events
71 | // to monitor the channel lifecycle, ie:
72 | //
73 | // channel.onError( () => console.log("there was an error!") )
74 | // channel.onClose( () => console.log("the channel has gone away gracefully") )
75 | //
76 | // ### onError hooks
77 | //
78 | // `onError` hooks are invoked if the socket connection drops, or the channel
79 | // crashes on the server. In either case, a channel rejoin is attemtped
80 | // automatically in an exponential backoff manner.
81 | //
82 | // ### onClose hooks
83 | //
84 | // `onClose` hooks are invoked only in two cases. 1) the channel explicitly
85 | // closed on the server, or 2). The client explicitly closed, by calling
86 | // `channel.leave()`
87 | //
88 | //
89 | // ## Presence
90 | //
91 | // The `Presence` object provides features for syncing presence information
92 | // from the server with the client and handling presences joining and leaving.
93 | //
94 | // ### Syncing initial state from the server
95 | //
96 | // `Presence.syncState` is used to sync the list of presences on the server
97 | // with the client's state. An optional `onJoin` and `onLeave` callback can
98 | // be provided to react to changes in the client's local presences across
99 | // disconnects and reconnects with the server.
100 | //
101 | // `Presence.syncDiff` is used to sync a diff of presence join and leave
102 | // events from the server, as they happen. Like `syncState`, `syncDiff`
103 | // accepts optional `onJoin` and `onLeave` callbacks to react to a user
104 | // joining or leaving from a device.
105 | //
106 | // ### Listing Presences
107 | //
108 | // `Presence.list` is used to return a list of presence information
109 | // based on the local state of metadata. By default, all presence
110 | // metadata is returned, but a `listBy` function can be supplied to
111 | // allow the client to select which metadata to use for a given presence.
112 | // For example, you may have a user online from different devices with a
113 | // a metadata status of "online", but they have set themselves to "away"
114 | // on another device. In this case, they app may choose to use the "away"
115 | // status for what appears on the UI. The example below defines a `listBy`
116 | // function which prioritizes the first metadata which was registered for
117 | // each user. This could be the first tab they opened, or the first device
118 | // they came online from:
119 | //
120 | // let state = {}
121 | // Presence.syncState(state, stateFromServer)
122 | // let listBy = (id, {metas: [first, ...rest]}) => {
123 | // first.count = rest.length + 1 // count of this user's presences
124 | // first.id = id
125 | // return first
126 | // }
127 | // let onlineUsers = Presence.list(state, listBy)
128 | //
129 | //
130 | // ### Example Usage
131 | //
132 | // // detect if user has joined for the 1st time or from another tab/device
133 | // let onJoin = (id, current, newPres) => {
134 | // if(!current){
135 | // console.log("user has entered for the first time", newPres)
136 | // } else {
137 | // console.log("user additional presence", newPres)
138 | // }
139 | // }
140 | // // detect if user has left from all tabs/devices, or is still present
141 | // let onLeave = (id, current, leftPres) => {
142 | // if(current.metas.length === 0){
143 | // console.log("user has left from all devices", leftPres)
144 | // } else {
145 | // console.log("user left from a device", leftPres)
146 | // }
147 | // }
148 | // let presences = {} // client's initial empty presence state
149 | // // receive initial presence data from server, sent after join
150 | // myChannel.on("presences", state => {
151 | // Presence.syncState(presences, state, onJoin, onLeave)
152 | // displayUsers(Presence.list(presences))
153 | // })
154 | // // receive "presence_diff" from server, containing join/leave events
155 | // myChannel.on("presence_diff", diff => {
156 | // Presence.syncDiff(presences, diff, onJoin, onLeave)
157 | // this.setState({users: Presence.list(room.presences, listBy)})
158 | // })
159 | //
160 | const VSN = "1.0.0"
161 | const SOCKET_STATES = {connecting: 0, open: 1, closing: 2, closed: 3}
162 | const DEFAULT_TIMEOUT = 10000
163 | const CHANNEL_STATES = {
164 | closed: "closed",
165 | errored: "errored",
166 | joined: "joined",
167 | joining: "joining",
168 | }
169 | const CHANNEL_EVENTS = {
170 | close: "phx_close",
171 | error: "phx_error",
172 | join: "phx_join",
173 | reply: "phx_reply",
174 | leave: "phx_leave"
175 | }
176 | const TRANSPORTS = {
177 | longpoll: "longpoll",
178 | websocket: "websocket"
179 | }
180 |
181 | class Push {
182 |
183 | // Initializes the Push
184 | //
185 | // channel - The Channel
186 | // event - The event, for example `"phx_join"`
187 | // payload - The payload, for example `{user_id: 123}`
188 | // timeout - The push timeout in milliseconds
189 | //
190 | constructor(channel, event, payload, timeout){
191 | this.channel = channel
192 | this.event = event
193 | this.payload = payload || {}
194 | this.receivedResp = null
195 | this.timeout = timeout
196 | this.timeoutTimer = null
197 | this.recHooks = []
198 | this.sent = false
199 | }
200 |
201 | resend(timeout){
202 | this.timeout = timeout
203 | this.cancelRefEvent()
204 | this.ref = null
205 | this.refEvent = null
206 | this.receivedResp = null
207 | this.sent = false
208 | this.send()
209 | }
210 |
211 | send(){ if(this.hasReceived("timeout")){ return }
212 | this.startTimeout()
213 | this.sent = true
214 | this.channel.socket.push({
215 | topic: this.channel.topic,
216 | event: this.event,
217 | payload: this.payload,
218 | ref: this.ref
219 | })
220 | }
221 |
222 | receive(status, callback){
223 | if(this.hasReceived(status)){
224 | callback(this.receivedResp.response)
225 | }
226 |
227 | this.recHooks.push({status, callback})
228 | return this
229 | }
230 |
231 |
232 | // private
233 |
234 | matchReceive({status, response, ref}){
235 | this.recHooks.filter( h => h.status === status )
236 | .forEach( h => h.callback(response) )
237 | }
238 |
239 | cancelRefEvent(){ if(!this.refEvent){ return }
240 | this.channel.off(this.refEvent)
241 | }
242 |
243 | cancelTimeout(){
244 | clearTimeout(this.timeoutTimer)
245 | this.timeoutTimer = null
246 | }
247 |
248 | startTimeout(){ if(this.timeoutTimer){ return }
249 | this.ref = this.channel.socket.makeRef()
250 | this.refEvent = this.channel.replyEventName(this.ref)
251 |
252 | this.channel.on(this.refEvent, payload => {
253 | this.cancelRefEvent()
254 | this.cancelTimeout()
255 | this.receivedResp = payload
256 | this.matchReceive(payload)
257 | })
258 |
259 | this.timeoutTimer = setTimeout(() => {
260 | this.trigger("timeout", {})
261 | }, this.timeout)
262 | }
263 |
264 | hasReceived(status){
265 | return this.receivedResp && this.receivedResp.status === status
266 | }
267 |
268 | trigger(status, response){
269 | this.channel.trigger(this.refEvent, {status, response})
270 | }
271 | }
272 |
273 | export class Channel {
274 | constructor(topic, params, socket) {
275 | this.state = CHANNEL_STATES.closed
276 | this.topic = topic
277 | this.params = params || {}
278 | this.socket = socket
279 | this.bindings = []
280 | this.timeout = this.socket.timeout
281 | this.joinedOnce = false
282 | this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout)
283 | this.pushBuffer = []
284 | this.rejoinTimer = new Timer(
285 | () => this.rejoinUntilConnected(),
286 | this.socket.reconnectAfterMs
287 | )
288 | this.joinPush.receive("ok", () => {
289 | this.state = CHANNEL_STATES.joined
290 | this.rejoinTimer.reset()
291 | this.pushBuffer.forEach( pushEvent => pushEvent.send() )
292 | this.pushBuffer = []
293 | })
294 | this.onClose( () => {
295 | this.socket.log("channel", `close ${this.topic}`)
296 | this.state = CHANNEL_STATES.closed
297 | this.socket.remove(this)
298 | })
299 | this.onError( reason => {
300 | this.socket.log("channel", `error ${this.topic}`, reason)
301 | this.state = CHANNEL_STATES.errored
302 | this.rejoinTimer.scheduleTimeout()
303 | })
304 | this.joinPush.receive("timeout", () => {
305 | if(this.state !== CHANNEL_STATES.joining){ return }
306 |
307 | this.socket.log("channel", `timeout ${this.topic}`, this.joinPush.timeout)
308 | this.state = CHANNEL_STATES.errored
309 | this.rejoinTimer.scheduleTimeout()
310 | })
311 | this.on(CHANNEL_EVENTS.reply, (payload, ref) => {
312 | this.trigger(this.replyEventName(ref), payload)
313 | })
314 | }
315 |
316 | rejoinUntilConnected(){
317 | this.rejoinTimer.scheduleTimeout()
318 | if(this.socket.isConnected()){
319 | this.rejoin()
320 | }
321 | }
322 |
323 | join(timeout = this.timeout){
324 | if(this.joinedOnce){
325 | throw(`tried to join multiple times. 'join' can only be called a single time per channel instance`)
326 | } else {
327 | this.joinedOnce = true
328 | }
329 | this.rejoin(timeout)
330 | return this.joinPush
331 | }
332 |
333 | onClose(callback){ this.on(CHANNEL_EVENTS.close, callback) }
334 |
335 | onError(callback){
336 | this.on(CHANNEL_EVENTS.error, reason => callback(reason) )
337 | }
338 |
339 | on(event, callback){ this.bindings.push({event, callback}) }
340 |
341 | off(event){ this.bindings = this.bindings.filter( bind => bind.event !== event ) }
342 |
343 | canPush(){ return this.socket.isConnected() && this.state === CHANNEL_STATES.joined }
344 |
345 | push(event, payload, timeout = this.timeout){
346 | if(!this.joinedOnce){
347 | throw(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`)
348 | }
349 | let pushEvent = new Push(this, event, payload, timeout)
350 | if(this.canPush()){
351 | pushEvent.send()
352 | } else {
353 | pushEvent.startTimeout()
354 | this.pushBuffer.push(pushEvent)
355 | }
356 |
357 | return pushEvent
358 | }
359 |
360 | // Leaves the channel
361 | //
362 | // Unsubscribes from server events, and
363 | // instructs channel to terminate on server
364 | //
365 | // Triggers onClose() hooks
366 | //
367 | // To receive leave acknowledgements, use the a `receive`
368 | // hook to bind to the server ack, ie:
369 | //
370 | // channel.leave().receive("ok", () => alert("left!") )
371 | //
372 | leave(timeout = this.timeout){
373 | let onClose = () => {
374 | this.socket.log("channel", `leave ${this.topic}`)
375 | this.trigger(CHANNEL_EVENTS.close, "leave")
376 | }
377 | let leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout)
378 | leavePush.receive("ok", () => onClose() )
379 | .receive("timeout", () => onClose() )
380 | leavePush.send()
381 | if(!this.canPush()){ leavePush.trigger("ok", {}) }
382 |
383 | return leavePush
384 | }
385 |
386 | // Overridable message hook
387 | //
388 | // Receives all events for specialized message handling
389 | onMessage(event, payload, ref){}
390 |
391 | // private
392 |
393 | isMember(topic){ return this.topic === topic }
394 |
395 | sendJoin(timeout){
396 | this.state = CHANNEL_STATES.joining
397 | this.joinPush.resend(timeout)
398 | }
399 |
400 | rejoin(timeout = this.timeout){ this.sendJoin(timeout) }
401 |
402 | trigger(triggerEvent, payload, ref){
403 | this.onMessage(triggerEvent, payload, ref)
404 | this.bindings.filter( bind => bind.event === triggerEvent )
405 | .map( bind => bind.callback(payload, ref) )
406 | }
407 |
408 | replyEventName(ref){ return `chan_reply_${ref}` }
409 | }
410 |
411 | export class Socket {
412 |
413 | // Initializes the Socket
414 | //
415 | // endPoint - The string WebSocket endpoint, ie, "ws://example.com/ws",
416 | // "wss://example.com"
417 | // "/ws" (inherited host & protocol)
418 | // opts - Optional configuration
419 | // transport - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.
420 | // Defaults to WebSocket with automatic LongPoll fallback.
421 | // timeout - The default timeout in milliseconds to trigger push timeouts.
422 | // Defaults `DEFAULT_TIMEOUT`
423 | // heartbeatIntervalMs - The millisec interval to send a heartbeat message
424 | // reconnectAfterMs - The optional function that returns the millsec
425 | // reconnect interval. Defaults to stepped backoff of:
426 | //
427 | // function(tries){
428 | // return [1000, 5000, 10000][tries - 1] || 10000
429 | // }
430 | //
431 | // logger - The optional function for specialized logging, ie:
432 | // `logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }
433 | //
434 | // longpollerTimeout - The maximum timeout of a long poll AJAX request.
435 | // Defaults to 20s (double the server long poll timer).
436 | //
437 | // params - The optional params to pass when connecting
438 | //
439 | // For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)
440 | //
441 | constructor(endPoint, opts = {}){
442 | this.stateChangeCallbacks = {open: [], close: [], error: [], message: []}
443 | this.channels = []
444 | this.sendBuffer = []
445 | this.ref = 0
446 | this.timeout = opts.timeout || DEFAULT_TIMEOUT
447 | this.transport = opts.transport || window.WebSocket || LongPoll
448 | this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000
449 | this.reconnectAfterMs = opts.reconnectAfterMs || function(tries){
450 | return [1000, 2000, 5000, 10000][tries - 1] || 10000
451 | }
452 | this.logger = opts.logger || function(){} // noop
453 | this.longpollerTimeout = opts.longpollerTimeout || 20000
454 | this.params = opts.params || {}
455 | this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`
456 | this.reconnectTimer = new Timer(() => {
457 | this.disconnect(() => this.connect())
458 | }, this.reconnectAfterMs)
459 | }
460 |
461 | protocol(){ return location.protocol.match(/^https/) ? "wss" : "ws" }
462 |
463 | endPointURL(){
464 | let uri = Ajax.appendParams(
465 | Ajax.appendParams(this.endPoint, this.params), {vsn: VSN})
466 | if(uri.charAt(0) !== "/"){ return uri }
467 | if(uri.charAt(1) === "/"){ return `${this.protocol()}:${uri}` }
468 |
469 | return `${this.protocol()}://${location.host}${uri}`
470 | }
471 |
472 | disconnect(callback, code, reason){
473 | if(this.conn){
474 | this.conn.onclose = function(){} // noop
475 | if(code){ this.conn.close(code, reason || "") } else { this.conn.close() }
476 | this.conn = null
477 | }
478 | callback && callback()
479 | }
480 |
481 | // params - The params to send when connecting, for example `{user_id: userToken}`
482 | connect(params){
483 | if(params){
484 | console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor")
485 | this.params = params
486 | }
487 | if(this.conn){ return }
488 |
489 | this.conn = new this.transport(this.endPointURL())
490 | this.conn.timeout = this.longpollerTimeout
491 | this.conn.onopen = () => this.onConnOpen()
492 | this.conn.onerror = error => this.onConnError(error)
493 | this.conn.onmessage = event => this.onConnMessage(event)
494 | this.conn.onclose = event => this.onConnClose(event)
495 | }
496 |
497 | // Logs the message. Override `this.logger` for specialized logging. noops by default
498 | log(kind, msg, data){ this.logger(kind, msg, data) }
499 |
500 | // Registers callbacks for connection state change events
501 | //
502 | // Examples
503 | //
504 | // socket.onError(function(error){ alert("An error occurred") })
505 | //
506 | onOpen (callback){ this.stateChangeCallbacks.open.push(callback) }
507 | onClose (callback){ this.stateChangeCallbacks.close.push(callback) }
508 | onError (callback){ this.stateChangeCallbacks.error.push(callback) }
509 | onMessage (callback){ this.stateChangeCallbacks.message.push(callback) }
510 |
511 | onConnOpen(){
512 | this.log("transport", `connected to ${this.endPointURL()}`, this.transport.prototype)
513 | this.flushSendBuffer()
514 | this.reconnectTimer.reset()
515 | if(!this.conn.skipHeartbeat){
516 | clearInterval(this.heartbeatTimer)
517 | this.heartbeatTimer = setInterval(() => this.sendHeartbeat(), this.heartbeatIntervalMs)
518 | }
519 | this.stateChangeCallbacks.open.forEach( callback => callback() )
520 | }
521 |
522 | onConnClose(event){
523 | this.log("transport", "close", event)
524 | this.triggerChanError()
525 | clearInterval(this.heartbeatTimer)
526 | this.reconnectTimer.scheduleTimeout()
527 | this.stateChangeCallbacks.close.forEach( callback => callback(event) )
528 | }
529 |
530 | onConnError(error){
531 | this.log("transport", error)
532 | this.triggerChanError()
533 | this.stateChangeCallbacks.error.forEach( callback => callback(error) )
534 | }
535 |
536 | triggerChanError(){
537 | this.channels.forEach( channel => channel.trigger(CHANNEL_EVENTS.error) )
538 | }
539 |
540 | connectionState(){
541 | switch(this.conn && this.conn.readyState){
542 | case SOCKET_STATES.connecting: return "connecting"
543 | case SOCKET_STATES.open: return "open"
544 | case SOCKET_STATES.closing: return "closing"
545 | default: return "closed"
546 | }
547 | }
548 |
549 | isConnected(){ return this.connectionState() === "open" }
550 |
551 | remove(channel){
552 | this.channels = this.channels.filter( c => !c.isMember(channel.topic) )
553 | }
554 |
555 | channel(topic, chanParams = {}){
556 | let chan = new Channel(topic, chanParams, this)
557 | this.channels.push(chan)
558 | return chan
559 | }
560 |
561 | push(data){
562 | let {topic, event, payload, ref} = data
563 | let callback = () => this.conn.send(JSON.stringify(data))
564 | this.log("push", `${topic} ${event} (${ref})`, payload)
565 | if(this.isConnected()){
566 | callback()
567 | }
568 | else {
569 | this.sendBuffer.push(callback)
570 | }
571 | }
572 |
573 | // Return the next message ref, accounting for overflows
574 | makeRef(){
575 | let newRef = this.ref + 1
576 | if(newRef === this.ref){ this.ref = 0 } else { this.ref = newRef }
577 |
578 | return this.ref.toString()
579 | }
580 |
581 | sendHeartbeat(){ if(!this.isConnected()){ return }
582 | this.push({topic: "phoenix", event: "heartbeat", payload: {}, ref: this.makeRef()})
583 | }
584 |
585 | flushSendBuffer(){
586 | if(this.isConnected() && this.sendBuffer.length > 0){
587 | this.sendBuffer.forEach( callback => callback() )
588 | this.sendBuffer = []
589 | }
590 | }
591 |
592 | onConnMessage(rawMessage){
593 | let msg = JSON.parse(rawMessage.data)
594 | let {topic, event, payload, ref} = msg
595 | this.log("receive", `${payload.status || ""} ${topic} ${event} ${ref && "(" + ref + ")" || ""}`, payload)
596 | this.channels.filter( channel => channel.isMember(topic) )
597 | .forEach( channel => channel.trigger(event, payload, ref) )
598 | this.stateChangeCallbacks.message.forEach( callback => callback(msg) )
599 | }
600 | }
601 |
602 |
603 | export class LongPoll {
604 |
605 | constructor(endPoint){
606 | this.endPoint = null
607 | this.token = null
608 | this.skipHeartbeat = true
609 | this.onopen = function(){} // noop
610 | this.onerror = function(){} // noop
611 | this.onmessage = function(){} // noop
612 | this.onclose = function(){} // noop
613 | this.pollEndpoint = this.normalizeEndpoint(endPoint)
614 | this.readyState = SOCKET_STATES.connecting
615 |
616 | this.poll()
617 | }
618 |
619 | normalizeEndpoint(endPoint){
620 | return(endPoint
621 | .replace("ws://", "http://")
622 | .replace("wss://", "https://")
623 | .replace(new RegExp("(.*)\/" + TRANSPORTS.websocket), "$1/" + TRANSPORTS.longpoll))
624 | }
625 |
626 | endpointURL(){
627 | return Ajax.appendParams(this.pollEndpoint, {token: this.token})
628 | }
629 |
630 | closeAndRetry(){
631 | this.close()
632 | this.readyState = SOCKET_STATES.connecting
633 | }
634 |
635 | ontimeout(){
636 | this.onerror("timeout")
637 | this.closeAndRetry()
638 | }
639 |
640 | poll(){
641 | if(!(this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting)){ return }
642 |
643 | Ajax.request("GET", this.endpointURL(), "application/json", null, this.timeout, this.ontimeout.bind(this), (resp) => {
644 | if(resp){
645 | var {status, token, messages} = resp
646 | this.token = token
647 | } else{
648 | var status = 0
649 | }
650 |
651 | switch(status){
652 | case 200:
653 | messages.forEach( msg => this.onmessage({data: JSON.stringify(msg)}) )
654 | this.poll()
655 | break
656 | case 204:
657 | this.poll()
658 | break
659 | case 410:
660 | this.readyState = SOCKET_STATES.open
661 | this.onopen()
662 | this.poll()
663 | break
664 | case 0:
665 | case 500:
666 | this.onerror()
667 | this.closeAndRetry()
668 | break
669 | default: throw(`unhandled poll status ${status}`)
670 | }
671 | })
672 | }
673 |
674 | send(body){
675 | Ajax.request("POST", this.endpointURL(), "application/json", body, this.timeout, this.onerror.bind(this, "timeout"), (resp) => {
676 | if(!resp || resp.status !== 200){
677 | this.onerror(status)
678 | this.closeAndRetry()
679 | }
680 | })
681 | }
682 |
683 | close(code, reason){
684 | this.readyState = SOCKET_STATES.closed
685 | this.onclose()
686 | }
687 | }
688 |
689 |
690 | export class Ajax {
691 |
692 | static request(method, endPoint, accept, body, timeout, ontimeout, callback){
693 | if(window.XDomainRequest){
694 | let req = new XDomainRequest() // IE8, IE9
695 | this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback)
696 | } else {
697 | let req = window.XMLHttpRequest ?
698 | new XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
699 | new ActiveXObject("Microsoft.XMLHTTP") // IE6, IE5
700 | this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback)
701 | }
702 | }
703 |
704 | static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback){
705 | req.timeout = timeout
706 | req.open(method, endPoint)
707 | req.onload = () => {
708 | let response = this.parseJSON(req.responseText)
709 | callback && callback(response)
710 | }
711 | if(ontimeout){ req.ontimeout = ontimeout }
712 |
713 | // Work around bug in IE9 that requires an attached onprogress handler
714 | req.onprogress = () => {}
715 |
716 | req.send(body)
717 | }
718 |
719 | static xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback){
720 | req.timeout = timeout
721 | req.open(method, endPoint, true)
722 | req.setRequestHeader("Content-Type", accept)
723 | req.onerror = () => { callback && callback(null) }
724 | req.onreadystatechange = () => {
725 | if(req.readyState === this.states.complete && callback){
726 | let response = this.parseJSON(req.responseText)
727 | callback(response)
728 | }
729 | }
730 | if(ontimeout){ req.ontimeout = ontimeout }
731 |
732 | req.send(body)
733 | }
734 |
735 | static parseJSON(resp){
736 | return (resp && resp !== "") ?
737 | JSON.parse(resp) :
738 | null
739 | }
740 |
741 | static serialize(obj, parentKey){
742 | let queryStr = [];
743 | for(var key in obj){ if(!obj.hasOwnProperty(key)){ continue }
744 | let paramKey = parentKey ? `${parentKey}[${key}]` : key
745 | let paramVal = obj[key]
746 | if(typeof paramVal === "object"){
747 | queryStr.push(this.serialize(paramVal, paramKey))
748 | } else {
749 | queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal))
750 | }
751 | }
752 | return queryStr.join("&")
753 | }
754 |
755 | static appendParams(url, params){
756 | if(Object.keys(params).length === 0){ return url }
757 |
758 | let prefix = url.match(/\?/) ? "&" : "?"
759 | return `${url}${prefix}${this.serialize(params)}`
760 | }
761 | }
762 |
763 | Ajax.states = {complete: 4}
764 |
765 |
766 |
767 | export var Presence = {
768 |
769 | syncState(state, newState, onJoin, onLeave){
770 | let joins = {}
771 | let leaves = {}
772 |
773 | this.map(state, (key, presence) => {
774 | if(!newState[key]){
775 | leaves[key] = this.clone(presence)
776 | }
777 | })
778 | this.map(newState, (key, newPresence) => {
779 | let currentPresence = state[key]
780 | if(currentPresence){
781 | let newRefs = newPresence.metas.map(m => m.phx_ref)
782 | let curRefs = currentPresence.metas.map(m => m.phx_ref)
783 | let joinedMetas = newPresence.metas.filter(m => curRefs.indexOf(m.phx_ref) < 0)
784 | let leftMetas = currentPresence.metas.filter(m => newRefs.indexOf(m.phx_ref) < 0)
785 | if(joinedMetas.length > 0){
786 | joins[key] = newPresence
787 | joins[key].metas = joinedMetas
788 | }
789 | if(leftMetas.length > 0){
790 | leaves[key] = this.clone(currentPresence)
791 | leaves[key].metas = leftMetas
792 | }
793 | } else {
794 | joins[key] = newPresence
795 | }
796 | })
797 | this.syncDiff(state, {joins: joins, leaves: leaves}, onJoin, onLeave)
798 | },
799 |
800 | syncDiff(state, {joins, leaves}, onJoin, onLeave){
801 | if(!onJoin){ onJoin = function(){} }
802 | if(!onLeave){ onLeave = function(){} }
803 |
804 | this.map(joins, (key, newPresence) => {
805 | let currentPresence = state[key]
806 | state[key] = newPresence
807 | if(currentPresence){
808 | state[key].metas.unshift(...currentPresence.metas)
809 | }
810 | onJoin(key, currentPresence, newPresence)
811 | })
812 | this.map(leaves, (key, leftPresence) => {
813 | let currentPresence = state[key]
814 | if(!currentPresence){ return }
815 | let refsToRemove = leftPresence.metas.map(m => m.phx_ref)
816 | currentPresence.metas = currentPresence.metas.filter(p => {
817 | return refsToRemove.indexOf(p.phx_ref) < 0
818 | })
819 | onLeave(key, currentPresence, leftPresence)
820 | if(currentPresence.metas.length === 0){
821 | delete state[key]
822 | }
823 | })
824 | },
825 |
826 | list(presences, chooser){
827 | if(!chooser){ chooser = function(key, pres){ return pres } }
828 |
829 | return this.map(presences, (key, presence) => {
830 | return chooser(key, presence)
831 | })
832 | },
833 |
834 | // private
835 |
836 | map(obj, func){
837 | return Object.getOwnPropertyNames(obj).map(key => func(key, obj[key]))
838 | },
839 |
840 | clone(obj){ return JSON.parse(JSON.stringify(obj)) }
841 | }
842 |
843 |
844 | // Creates a timer that accepts a `timerCalc` function to perform
845 | // calculated timeout retries, such as exponential backoff.
846 | //
847 | // ## Examples
848 | //
849 | // let reconnectTimer = new Timer(() => this.connect(), function(tries){
850 | // return [1000, 5000, 10000][tries - 1] || 10000
851 | // })
852 | // reconnectTimer.scheduleTimeout() // fires after 1000
853 | // reconnectTimer.scheduleTimeout() // fires after 5000
854 | // reconnectTimer.reset()
855 | // reconnectTimer.scheduleTimeout() // fires after 1000
856 | //
857 | class Timer {
858 | constructor(callback, timerCalc){
859 | this.callback = callback
860 | this.timerCalc = timerCalc
861 | this.timer = null
862 | this.tries = 0
863 | }
864 |
865 | reset(){
866 | this.tries = 0
867 | clearTimeout(this.timer)
868 | }
869 |
870 | // Cancels any previous scheduleTimeout and schedules callback
871 | scheduleTimeout(){
872 | clearTimeout(this.timer)
873 |
874 | this.timer = setTimeout(() => {
875 | this.tries = this.tries + 1
876 | this.callback()
877 | }, this.timerCalc(this.tries + 1))
878 | }
879 | }
880 |
--------------------------------------------------------------------------------
/ChatClient/Root.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, View, Dimensions } from 'react-native'
2 | import GiftedMessenger from 'react-native-gifted-messenger'
3 | import moment from 'moment'
4 | import Chat from './Chat'
5 |
6 | // layout numbers
7 | const SCREEN_HEIGHT = Dimensions.get('window').height
8 | const STATUS_BAR_HEIGHT = 40 // i know, but let's pretend its cool
9 | const CHAT_MAX_HEIGHT = SCREEN_HEIGHT - STATUS_BAR_HEIGHT
10 |
11 | // yes, i'm 41 years old.
12 | const NAMES = ['Girl', 'Boy', 'Horse', 'Poo', 'Face', 'Giant', 'Super', 'Butt', 'Captain', 'Lazer']
13 | const getRandomInt = (min, max) => Math.floor(Math.random() * (max - min)) + min
14 | const getRandomName = () => NAMES[getRandomInt(0, NAMES.length)]
15 | const getRandomUser = () => `${ getRandomName() }${ getRandomName() }${ getRandomName() }`
16 | const user = getRandomUser()
17 | const isMe = (someUser) => user === someUser
18 | const avatar = { uri: 'https://facebook.github.io/react/img/logo_og.png' }
19 |
20 | class Root extends Component {
21 |
22 | constructor (props) {
23 | super(props)
24 | // bind our functions to the right scope
25 | this.handleSend = this.handleSend.bind(this)
26 | this.receiveChatMessage = this.receiveChatMessage.bind(this)
27 | // let's chat!
28 | this.chat = Chat(user, this.receiveChatMessage)
29 | }
30 |
31 | // fires when we receive a message
32 | receiveChatMessage (message) {
33 | const { user } = message
34 | if (isMe(user)) return // prevent echoing yourself (TODO: server could handle this i guess?)
35 | this.refs.giftedMessenger.appendMessage({
36 | text: message.body,
37 | name: message.user,
38 | image: avatar,
39 | position: 'left',
40 | date: moment()
41 | })
42 | }
43 |
44 | // fires when we need to send a message
45 | handleSend (message) {
46 | this.chat.send(message.text)
47 | }
48 |
49 | // draw our ui
50 | render () {
51 | return (
52 |
53 |
59 |
60 | )
61 | }
62 |
63 | }
64 |
65 | export default Root
66 |
--------------------------------------------------------------------------------
/ChatClient/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 |
3 | import com.android.build.OutputFile
4 |
5 | /**
6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
7 | * and bundleReleaseJsAndAssets).
8 | * These basically call `react-native bundle` with the correct arguments during the Android build
9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
10 | * bundle directly from the development server. Below you can see all the possible configurations
11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the
12 | * `apply from: "react.gradle"` line.
13 | *
14 | * project.ext.react = [
15 | * // the name of the generated asset file containing your JS bundle
16 | * bundleAssetName: "index.android.bundle",
17 | *
18 | * // the entry file for bundle generation
19 | * entryFile: "index.android.js",
20 | *
21 | * // whether to bundle JS and assets in debug mode
22 | * bundleInDebug: false,
23 | *
24 | * // whether to bundle JS and assets in release mode
25 | * bundleInRelease: true,
26 | *
27 | * // whether to bundle JS and assets in another build variant (if configured).
28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
29 | * // The configuration property is in the format 'bundleIn${productFlavor}${buildType}'
30 | * // bundleInFreeDebug: true,
31 | * // bundleInPaidRelease: true,
32 | * // bundleInBeta: true,
33 | *
34 | * // the root of your project, i.e. where "package.json" lives
35 | * root: "../../",
36 | *
37 | * // where to put the JS bundle asset in debug mode
38 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
39 | *
40 | * // where to put the JS bundle asset in release mode
41 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release",
42 | *
43 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
44 | * // require('./image.png')), in debug mode
45 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
46 | *
47 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
48 | * // require('./image.png')), in release mode
49 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
50 | *
51 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means
52 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
53 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle
54 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
55 | * // for example, you might want to remove it from here.
56 | * inputExcludes: ["android/**", "ios/**"]
57 | * ]
58 | */
59 |
60 | apply from: "react.gradle"
61 |
62 | /**
63 | * Set this to true to create two separate APKs instead of one:
64 | * - An APK that only works on ARM devices
65 | * - An APK that only works on x86 devices
66 | * The advantage is the size of the APK is reduced by about 4MB.
67 | * Upload all the APKs to the Play Store and people will download
68 | * the correct one based on the CPU architecture of their device.
69 | */
70 | def enableSeparateBuildPerCPUArchitecture = false
71 |
72 | /**
73 | * Run Proguard to shrink the Java bytecode in release builds.
74 | */
75 | def enableProguardInReleaseBuilds = true
76 |
77 | android {
78 | compileSdkVersion 23
79 | buildToolsVersion "23.0.1"
80 |
81 | defaultConfig {
82 | applicationId "com.chatclient"
83 | minSdkVersion 16
84 | targetSdkVersion 22
85 | versionCode 1
86 | versionName "1.0"
87 | ndk {
88 | abiFilters "armeabi-v7a", "x86"
89 | }
90 | }
91 | splits {
92 | abi {
93 | enable enableSeparateBuildPerCPUArchitecture
94 | universalApk false // Also generate an universal APK
95 | reset()
96 | include "armeabi-v7a", "x86"
97 | }
98 | }
99 | buildTypes {
100 | release {
101 | minifyEnabled enableProguardInReleaseBuilds
102 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
103 | }
104 | }
105 | // applicationVariants are e.g. debug, release
106 | applicationVariants.all { variant ->
107 | variant.outputs.each { output ->
108 | // For each separate APK per architecture, set a unique version code as described here:
109 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
110 | def versionCodes = ["armeabi-v7a":1, "x86":2]
111 | def abi = output.getFilter(OutputFile.ABI)
112 | if (abi != null) { // null for the universal-debug, universal-release variants
113 | output.versionCodeOverride =
114 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
115 | }
116 | }
117 | }
118 | }
119 |
120 | dependencies {
121 | compile fileTree(dir: "libs", include: ["*.jar"])
122 | compile "com.android.support:appcompat-v7:23.0.1"
123 | compile "com.facebook.react:react-native:0.19.+"
124 | }
125 |
--------------------------------------------------------------------------------
/ChatClient/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Disabling obfuscation is useful if you collect stack traces from production crashes
20 | # (unless you are using a system that supports de-obfuscate the stack traces).
21 | -dontobfuscate
22 |
23 | # React Native
24 |
25 | # Keep our interfaces so they can be used by other ProGuard rules.
26 | # See http://sourceforge.net/p/proguard/bugs/466/
27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
29 |
30 | # Do not strip any method/class that is annotated with @DoNotStrip
31 | -keep @com.facebook.proguard.annotations.DoNotStrip class *
32 | -keepclassmembers class * {
33 | @com.facebook.proguard.annotations.DoNotStrip *;
34 | }
35 |
36 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
37 | void set*(***);
38 | *** get*();
39 | }
40 |
41 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
42 | -keep class * extends com.facebook.react.bridge.NativeModule { *; }
43 | -keepclassmembers,includedescriptorclasses class * { native ; }
44 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; }
45 | -keepclassmembers class * { @com.facebook.react.uimanager.ReactProp ; }
46 | -keepclassmembers class * { @com.facebook.react.uimanager.ReactPropGroup ; }
47 |
48 | -dontwarn com.facebook.react.**
49 |
50 | # okhttp
51 |
52 | -keepattributes Signature
53 | -keepattributes *Annotation*
54 | -keep class com.squareup.okhttp.** { *; }
55 | -keep interface com.squareup.okhttp.** { *; }
56 | -dontwarn com.squareup.okhttp.**
57 |
58 | # okio
59 |
60 | -keep class sun.misc.Unsafe { *; }
61 | -dontwarn java.nio.file.*
62 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
63 | -dontwarn okio.**
64 |
65 | # stetho
66 |
67 | -dontwarn com.facebook.stetho.**
68 |
--------------------------------------------------------------------------------
/ChatClient/android/app/react.gradle:
--------------------------------------------------------------------------------
1 | import org.apache.tools.ant.taskdefs.condition.Os
2 |
3 | def config = project.hasProperty("react") ? project.react : [];
4 |
5 | def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
6 | def entryFile = config.entryFile ?: "index.android.js"
7 |
8 | // because elvis operator
9 | def elvisFile(thing) {
10 | return thing ? file(thing) : null;
11 | }
12 |
13 | def reactRoot = elvisFile(config.root) ?: file("../../")
14 | def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
15 |
16 | void runBefore(String dependentTaskName, Task task) {
17 | Task dependentTask = tasks.findByPath(dependentTaskName);
18 | if (dependentTask != null) {
19 | dependentTask.dependsOn task
20 | }
21 | }
22 |
23 | gradle.projectsEvaluated {
24 | // Grab all build types and product flavors
25 | def buildTypes = android.buildTypes.collect { type -> type.name }
26 | def productFlavors = android.productFlavors.collect { flavor -> flavor.name }
27 |
28 | // When no product flavors defined, use empty
29 | if (!productFlavors) productFlavors.add('')
30 |
31 | productFlavors.each { productFlavorName ->
32 | buildTypes.each { buildTypeName ->
33 | // Create variant and source names
34 | def sourceName = "${buildTypeName}"
35 | def targetName = "${sourceName.capitalize()}"
36 | if (productFlavorName) {
37 | sourceName = "${productFlavorName}${targetName}"
38 | }
39 |
40 | // React js bundle directories
41 | def jsBundleDirConfigName = "jsBundleDir${targetName}"
42 | def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?:
43 | file("$buildDir/intermediates/assets/${sourceName}")
44 |
45 | def resourcesDirConfigName = "jsBundleDir${targetName}"
46 | def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?:
47 | file("$buildDir/intermediates/res/merged/${sourceName}")
48 | def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
49 |
50 | // Bundle task name for variant
51 | def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets"
52 |
53 | def currentBundleTask = tasks.create(
54 | name: bundleJsAndAssetsTaskName,
55 | type: Exec) {
56 | group = "react"
57 | description = "bundle JS and assets for ${targetName}."
58 |
59 | // Create dirs if they are not there (e.g. the "clean" task just ran)
60 | doFirst {
61 | jsBundleDir.mkdirs()
62 | resourcesDir.mkdirs()
63 | }
64 |
65 | // Set up inputs and outputs so gradle can cache the result
66 | inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
67 | outputs.dir jsBundleDir
68 | outputs.dir resourcesDir
69 |
70 | // Set up the call to the react-native cli
71 | workingDir reactRoot
72 |
73 | // Set up dev mode
74 | def devEnabled = !targetName.toLowerCase().contains("release")
75 | if (Os.isFamily(Os.FAMILY_WINDOWS)) {
76 | commandLine "cmd", "/c", "react-native", "bundle", "--platform", "android", "--dev", "${devEnabled}",
77 | "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir
78 | } else {
79 | commandLine "react-native", "bundle", "--platform", "android", "--dev", "${devEnabled}",
80 | "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir
81 | }
82 |
83 | enabled config."bundleIn${targetName}" ?: targetName.toLowerCase().contains("release")
84 | }
85 |
86 | // Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process
87 | currentBundleTask.dependsOn("merge${targetName}Resources")
88 | currentBundleTask.dependsOn("merge${targetName}Assets")
89 |
90 | runBefore("processArmeabi-v7a${targetName}Resources", currentBundleTask)
91 | runBefore("processX86${targetName}Resources", currentBundleTask)
92 | runBefore("processUniversal${targetName}Resources", currentBundleTask)
93 | runBefore("process${targetName}Resources", currentBundleTask)
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/ChatClient/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/ChatClient/android/app/src/main/java/com/chatclient/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.chatclient;
2 |
3 | import com.facebook.react.ReactActivity;
4 | import com.facebook.react.ReactPackage;
5 | import com.facebook.react.shell.MainReactPackage;
6 |
7 | import java.util.Arrays;
8 | import java.util.List;
9 |
10 | public class MainActivity extends ReactActivity {
11 |
12 | /**
13 | * Returns the name of the main component registered from JavaScript.
14 | * This is used to schedule rendering of the component.
15 | */
16 | @Override
17 | protected String getMainComponentName() {
18 | return "ChatClient";
19 | }
20 |
21 | /**
22 | * Returns whether dev mode should be enabled.
23 | * This enables e.g. the dev menu.
24 | */
25 | @Override
26 | protected boolean getUseDeveloperSupport() {
27 | return BuildConfig.DEBUG;
28 | }
29 |
30 | /**
31 | * A list of packages used by the app. If the app uses additional views
32 | * or modules besides the default ones, add more packages here.
33 | */
34 | @Override
35 | protected List getPackages() {
36 | return Arrays.asList(
37 | new MainReactPackage()
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/ChatClient/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skellock/phoenix-react-native-mashup/3c22b7ae060c0c2a5b18f459d6a65b7c6defbbe9/ChatClient/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ChatClient/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skellock/phoenix-react-native-mashup/3c22b7ae060c0c2a5b18f459d6a65b7c6defbbe9/ChatClient/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ChatClient/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skellock/phoenix-react-native-mashup/3c22b7ae060c0c2a5b18f459d6a65b7c6defbbe9/ChatClient/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ChatClient/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skellock/phoenix-react-native-mashup/3c22b7ae060c0c2a5b18f459d6a65b7c6defbbe9/ChatClient/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ChatClient/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ChatClient
3 |
4 |
--------------------------------------------------------------------------------
/ChatClient/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ChatClient/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.3.1'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | mavenLocal()
18 | jcenter()
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ChatClient/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | android.useDeprecatedNdk=true
21 |
--------------------------------------------------------------------------------
/ChatClient/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skellock/phoenix-react-native-mashup/3c22b7ae060c0c2a5b18f459d6a65b7c6defbbe9/ChatClient/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/ChatClient/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
6 |
--------------------------------------------------------------------------------
/ChatClient/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/ChatClient/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/ChatClient/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'ChatClient'
2 |
3 | include ':app'
4 |
--------------------------------------------------------------------------------
/ChatClient/index.android.js:
--------------------------------------------------------------------------------
1 | import {AppRegistry} from 'react-native'
2 | import Root from './Root'
3 |
4 | AppRegistry.registerComponent('ChatClient', () => Root)
5 |
--------------------------------------------------------------------------------
/ChatClient/index.ios.js:
--------------------------------------------------------------------------------
1 | import {AppRegistry} from 'react-native'
2 | import Root from './Root'
3 |
4 | AppRegistry.registerComponent('ChatClient', () => Root)
5 |
--------------------------------------------------------------------------------
/ChatClient/ios/ChatClient.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
11 | 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
12 | 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; };
13 | 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; };
14 | 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; };
15 | 00E356F31AD99517003FC87E /* ChatClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* ChatClientTests.m */; };
16 | 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; };
17 | 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; };
18 | 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; };
19 | 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
20 | 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
21 | 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
22 | 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
23 | 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; };
24 | 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
25 | /* End PBXBuildFile section */
26 |
27 | /* Begin PBXContainerItemProxy section */
28 | 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */ = {
29 | isa = PBXContainerItemProxy;
30 | containerPortal = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */;
31 | proxyType = 2;
32 | remoteGlobalIDString = 134814201AA4EA6300B7C361;
33 | remoteInfo = RCTActionSheet;
34 | };
35 | 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */ = {
36 | isa = PBXContainerItemProxy;
37 | containerPortal = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */;
38 | proxyType = 2;
39 | remoteGlobalIDString = 134814201AA4EA6300B7C361;
40 | remoteInfo = RCTGeolocation;
41 | };
42 | 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */ = {
43 | isa = PBXContainerItemProxy;
44 | containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */;
45 | proxyType = 2;
46 | remoteGlobalIDString = 58B5115D1A9E6B3D00147676;
47 | remoteInfo = RCTImage;
48 | };
49 | 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */ = {
50 | isa = PBXContainerItemProxy;
51 | containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */;
52 | proxyType = 2;
53 | remoteGlobalIDString = 58B511DB1A9E6C8500147676;
54 | remoteInfo = RCTNetwork;
55 | };
56 | 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */ = {
57 | isa = PBXContainerItemProxy;
58 | containerPortal = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */;
59 | proxyType = 2;
60 | remoteGlobalIDString = 832C81801AAF6DEF007FA2F7;
61 | remoteInfo = RCTVibration;
62 | };
63 | 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = {
64 | isa = PBXContainerItemProxy;
65 | containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
66 | proxyType = 1;
67 | remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
68 | remoteInfo = ChatClient;
69 | };
70 | 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = {
71 | isa = PBXContainerItemProxy;
72 | containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */;
73 | proxyType = 2;
74 | remoteGlobalIDString = 134814201AA4EA6300B7C361;
75 | remoteInfo = RCTSettings;
76 | };
77 | 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */ = {
78 | isa = PBXContainerItemProxy;
79 | containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */;
80 | proxyType = 2;
81 | remoteGlobalIDString = 3C86DF461ADF2C930047B81A;
82 | remoteInfo = RCTWebSocket;
83 | };
84 | 146834031AC3E56700842450 /* PBXContainerItemProxy */ = {
85 | isa = PBXContainerItemProxy;
86 | containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
87 | proxyType = 2;
88 | remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192;
89 | remoteInfo = React;
90 | };
91 | 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = {
92 | isa = PBXContainerItemProxy;
93 | containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */;
94 | proxyType = 2;
95 | remoteGlobalIDString = 134814201AA4EA6300B7C361;
96 | remoteInfo = RCTLinking;
97 | };
98 | 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = {
99 | isa = PBXContainerItemProxy;
100 | containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
101 | proxyType = 2;
102 | remoteGlobalIDString = 58B5119B1A9E6C1200147676;
103 | remoteInfo = RCTText;
104 | };
105 | /* End PBXContainerItemProxy section */
106 |
107 | /* Begin PBXFileReference section */
108 | 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = main.jsbundle; path = main.jsbundle; sourceTree = ""; };
109 | 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; };
110 | 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; };
111 | 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; };
112 | 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; };
113 | 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = ""; };
114 | 00E356EE1AD99517003FC87E /* ChatClientTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChatClientTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
115 | 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
116 | 00E356F21AD99517003FC87E /* ChatClientTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ChatClientTests.m; sourceTree = ""; };
117 | 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; };
118 | 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; };
119 | 13B07F961A680F5B00A75B9A /* ChatClient.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ChatClient.app; sourceTree = BUILT_PRODUCTS_DIR; };
120 | 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = ChatClient/AppDelegate.h; sourceTree = ""; };
121 | 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = ChatClient/AppDelegate.m; sourceTree = ""; };
122 | 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
123 | 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ChatClient/Images.xcassets; sourceTree = ""; };
124 | 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ChatClient/Info.plist; sourceTree = ""; };
125 | 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = ChatClient/main.m; sourceTree = ""; };
126 | 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../node_modules/react-native/React/React.xcodeproj; sourceTree = ""; };
127 | 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; };
128 | 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../node_modules/react-native/Libraries/Text/RCTText.xcodeproj; sourceTree = ""; };
129 | /* End PBXFileReference section */
130 |
131 | /* Begin PBXFrameworksBuildPhase section */
132 | 00E356EB1AD99517003FC87E /* Frameworks */ = {
133 | isa = PBXFrameworksBuildPhase;
134 | buildActionMask = 2147483647;
135 | files = (
136 | );
137 | runOnlyForDeploymentPostprocessing = 0;
138 | };
139 | 13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
140 | isa = PBXFrameworksBuildPhase;
141 | buildActionMask = 2147483647;
142 | files = (
143 | 146834051AC3E58100842450 /* libReact.a in Frameworks */,
144 | 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */,
145 | 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */,
146 | 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */,
147 | 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */,
148 | 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */,
149 | 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */,
150 | 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */,
151 | 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */,
152 | 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */,
153 | );
154 | runOnlyForDeploymentPostprocessing = 0;
155 | };
156 | /* End PBXFrameworksBuildPhase section */
157 |
158 | /* Begin PBXGroup section */
159 | 00C302A81ABCB8CE00DB3ED1 /* Products */ = {
160 | isa = PBXGroup;
161 | children = (
162 | 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */,
163 | );
164 | name = Products;
165 | sourceTree = "";
166 | };
167 | 00C302B61ABCB90400DB3ED1 /* Products */ = {
168 | isa = PBXGroup;
169 | children = (
170 | 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */,
171 | );
172 | name = Products;
173 | sourceTree = "";
174 | };
175 | 00C302BC1ABCB91800DB3ED1 /* Products */ = {
176 | isa = PBXGroup;
177 | children = (
178 | 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */,
179 | );
180 | name = Products;
181 | sourceTree = "";
182 | };
183 | 00C302D41ABCB9D200DB3ED1 /* Products */ = {
184 | isa = PBXGroup;
185 | children = (
186 | 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */,
187 | );
188 | name = Products;
189 | sourceTree = "";
190 | };
191 | 00C302E01ABCB9EE00DB3ED1 /* Products */ = {
192 | isa = PBXGroup;
193 | children = (
194 | 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */,
195 | );
196 | name = Products;
197 | sourceTree = "";
198 | };
199 | 00E356EF1AD99517003FC87E /* ChatClientTests */ = {
200 | isa = PBXGroup;
201 | children = (
202 | 00E356F21AD99517003FC87E /* ChatClientTests.m */,
203 | 00E356F01AD99517003FC87E /* Supporting Files */,
204 | );
205 | path = ChatClientTests;
206 | sourceTree = "";
207 | };
208 | 00E356F01AD99517003FC87E /* Supporting Files */ = {
209 | isa = PBXGroup;
210 | children = (
211 | 00E356F11AD99517003FC87E /* Info.plist */,
212 | );
213 | name = "Supporting Files";
214 | sourceTree = "";
215 | };
216 | 139105B71AF99BAD00B5F7CC /* Products */ = {
217 | isa = PBXGroup;
218 | children = (
219 | 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */,
220 | );
221 | name = Products;
222 | sourceTree = "";
223 | };
224 | 139FDEE71B06529A00C62182 /* Products */ = {
225 | isa = PBXGroup;
226 | children = (
227 | 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */,
228 | );
229 | name = Products;
230 | sourceTree = "";
231 | };
232 | 13B07FAE1A68108700A75B9A /* ChatClient */ = {
233 | isa = PBXGroup;
234 | children = (
235 | 008F07F21AC5B25A0029DE68 /* main.jsbundle */,
236 | 13B07FAF1A68108700A75B9A /* AppDelegate.h */,
237 | 13B07FB01A68108700A75B9A /* AppDelegate.m */,
238 | 13B07FB51A68108700A75B9A /* Images.xcassets */,
239 | 13B07FB61A68108700A75B9A /* Info.plist */,
240 | 13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
241 | 13B07FB71A68108700A75B9A /* main.m */,
242 | );
243 | name = ChatClient;
244 | sourceTree = "";
245 | };
246 | 146834001AC3E56700842450 /* Products */ = {
247 | isa = PBXGroup;
248 | children = (
249 | 146834041AC3E56700842450 /* libReact.a */,
250 | );
251 | name = Products;
252 | sourceTree = "";
253 | };
254 | 78C398B11ACF4ADC00677621 /* Products */ = {
255 | isa = PBXGroup;
256 | children = (
257 | 78C398B91ACF4ADC00677621 /* libRCTLinking.a */,
258 | );
259 | name = Products;
260 | sourceTree = "";
261 | };
262 | 832341AE1AAA6A7D00B99B32 /* Libraries */ = {
263 | isa = PBXGroup;
264 | children = (
265 | 146833FF1AC3E56700842450 /* React.xcodeproj */,
266 | 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */,
267 | 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */,
268 | 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */,
269 | 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */,
270 | 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */,
271 | 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */,
272 | 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */,
273 | 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */,
274 | 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */,
275 | );
276 | name = Libraries;
277 | sourceTree = "";
278 | };
279 | 832341B11AAA6A8300B99B32 /* Products */ = {
280 | isa = PBXGroup;
281 | children = (
282 | 832341B51AAA6A8300B99B32 /* libRCTText.a */,
283 | );
284 | name = Products;
285 | sourceTree = "";
286 | };
287 | 83CBB9F61A601CBA00E9B192 = {
288 | isa = PBXGroup;
289 | children = (
290 | 13B07FAE1A68108700A75B9A /* ChatClient */,
291 | 832341AE1AAA6A7D00B99B32 /* Libraries */,
292 | 00E356EF1AD99517003FC87E /* ChatClientTests */,
293 | 83CBBA001A601CBA00E9B192 /* Products */,
294 | );
295 | indentWidth = 2;
296 | sourceTree = "";
297 | tabWidth = 2;
298 | };
299 | 83CBBA001A601CBA00E9B192 /* Products */ = {
300 | isa = PBXGroup;
301 | children = (
302 | 13B07F961A680F5B00A75B9A /* ChatClient.app */,
303 | 00E356EE1AD99517003FC87E /* ChatClientTests.xctest */,
304 | );
305 | name = Products;
306 | sourceTree = "";
307 | };
308 | /* End PBXGroup section */
309 |
310 | /* Begin PBXNativeTarget section */
311 | 00E356ED1AD99517003FC87E /* ChatClientTests */ = {
312 | isa = PBXNativeTarget;
313 | buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "ChatClientTests" */;
314 | buildPhases = (
315 | 00E356EA1AD99517003FC87E /* Sources */,
316 | 00E356EB1AD99517003FC87E /* Frameworks */,
317 | 00E356EC1AD99517003FC87E /* Resources */,
318 | );
319 | buildRules = (
320 | );
321 | dependencies = (
322 | 00E356F51AD99517003FC87E /* PBXTargetDependency */,
323 | );
324 | name = ChatClientTests;
325 | productName = ChatClientTests;
326 | productReference = 00E356EE1AD99517003FC87E /* ChatClientTests.xctest */;
327 | productType = "com.apple.product-type.bundle.unit-test";
328 | };
329 | 13B07F861A680F5B00A75B9A /* ChatClient */ = {
330 | isa = PBXNativeTarget;
331 | buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ChatClient" */;
332 | buildPhases = (
333 | 13B07F871A680F5B00A75B9A /* Sources */,
334 | 13B07F8C1A680F5B00A75B9A /* Frameworks */,
335 | 13B07F8E1A680F5B00A75B9A /* Resources */,
336 | 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
337 | );
338 | buildRules = (
339 | );
340 | dependencies = (
341 | );
342 | name = ChatClient;
343 | productName = "Hello World";
344 | productReference = 13B07F961A680F5B00A75B9A /* ChatClient.app */;
345 | productType = "com.apple.product-type.application";
346 | };
347 | /* End PBXNativeTarget section */
348 |
349 | /* Begin PBXProject section */
350 | 83CBB9F71A601CBA00E9B192 /* Project object */ = {
351 | isa = PBXProject;
352 | attributes = {
353 | LastUpgradeCheck = 0610;
354 | ORGANIZATIONNAME = Facebook;
355 | TargetAttributes = {
356 | 00E356ED1AD99517003FC87E = {
357 | CreatedOnToolsVersion = 6.2;
358 | TestTargetID = 13B07F861A680F5B00A75B9A;
359 | };
360 | };
361 | };
362 | buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ChatClient" */;
363 | compatibilityVersion = "Xcode 3.2";
364 | developmentRegion = English;
365 | hasScannedForEncodings = 0;
366 | knownRegions = (
367 | en,
368 | Base,
369 | );
370 | mainGroup = 83CBB9F61A601CBA00E9B192;
371 | productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
372 | projectDirPath = "";
373 | projectReferences = (
374 | {
375 | ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */;
376 | ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */;
377 | },
378 | {
379 | ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */;
380 | ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */;
381 | },
382 | {
383 | ProductGroup = 00C302BC1ABCB91800DB3ED1 /* Products */;
384 | ProjectRef = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */;
385 | },
386 | {
387 | ProductGroup = 78C398B11ACF4ADC00677621 /* Products */;
388 | ProjectRef = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */;
389 | },
390 | {
391 | ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */;
392 | ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */;
393 | },
394 | {
395 | ProductGroup = 139105B71AF99BAD00B5F7CC /* Products */;
396 | ProjectRef = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */;
397 | },
398 | {
399 | ProductGroup = 832341B11AAA6A8300B99B32 /* Products */;
400 | ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
401 | },
402 | {
403 | ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */;
404 | ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */;
405 | },
406 | {
407 | ProductGroup = 139FDEE71B06529A00C62182 /* Products */;
408 | ProjectRef = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */;
409 | },
410 | {
411 | ProductGroup = 146834001AC3E56700842450 /* Products */;
412 | ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */;
413 | },
414 | );
415 | projectRoot = "";
416 | targets = (
417 | 13B07F861A680F5B00A75B9A /* ChatClient */,
418 | 00E356ED1AD99517003FC87E /* ChatClientTests */,
419 | );
420 | };
421 | /* End PBXProject section */
422 |
423 | /* Begin PBXReferenceProxy section */
424 | 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */ = {
425 | isa = PBXReferenceProxy;
426 | fileType = archive.ar;
427 | path = libRCTActionSheet.a;
428 | remoteRef = 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */;
429 | sourceTree = BUILT_PRODUCTS_DIR;
430 | };
431 | 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */ = {
432 | isa = PBXReferenceProxy;
433 | fileType = archive.ar;
434 | path = libRCTGeolocation.a;
435 | remoteRef = 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */;
436 | sourceTree = BUILT_PRODUCTS_DIR;
437 | };
438 | 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */ = {
439 | isa = PBXReferenceProxy;
440 | fileType = archive.ar;
441 | path = libRCTImage.a;
442 | remoteRef = 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */;
443 | sourceTree = BUILT_PRODUCTS_DIR;
444 | };
445 | 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */ = {
446 | isa = PBXReferenceProxy;
447 | fileType = archive.ar;
448 | path = libRCTNetwork.a;
449 | remoteRef = 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */;
450 | sourceTree = BUILT_PRODUCTS_DIR;
451 | };
452 | 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */ = {
453 | isa = PBXReferenceProxy;
454 | fileType = archive.ar;
455 | path = libRCTVibration.a;
456 | remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */;
457 | sourceTree = BUILT_PRODUCTS_DIR;
458 | };
459 | 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = {
460 | isa = PBXReferenceProxy;
461 | fileType = archive.ar;
462 | path = libRCTSettings.a;
463 | remoteRef = 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */;
464 | sourceTree = BUILT_PRODUCTS_DIR;
465 | };
466 | 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */ = {
467 | isa = PBXReferenceProxy;
468 | fileType = archive.ar;
469 | path = libRCTWebSocket.a;
470 | remoteRef = 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */;
471 | sourceTree = BUILT_PRODUCTS_DIR;
472 | };
473 | 146834041AC3E56700842450 /* libReact.a */ = {
474 | isa = PBXReferenceProxy;
475 | fileType = archive.ar;
476 | path = libReact.a;
477 | remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */;
478 | sourceTree = BUILT_PRODUCTS_DIR;
479 | };
480 | 78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = {
481 | isa = PBXReferenceProxy;
482 | fileType = archive.ar;
483 | path = libRCTLinking.a;
484 | remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */;
485 | sourceTree = BUILT_PRODUCTS_DIR;
486 | };
487 | 832341B51AAA6A8300B99B32 /* libRCTText.a */ = {
488 | isa = PBXReferenceProxy;
489 | fileType = archive.ar;
490 | path = libRCTText.a;
491 | remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */;
492 | sourceTree = BUILT_PRODUCTS_DIR;
493 | };
494 | /* End PBXReferenceProxy section */
495 |
496 | /* Begin PBXResourcesBuildPhase section */
497 | 00E356EC1AD99517003FC87E /* Resources */ = {
498 | isa = PBXResourcesBuildPhase;
499 | buildActionMask = 2147483647;
500 | files = (
501 | );
502 | runOnlyForDeploymentPostprocessing = 0;
503 | };
504 | 13B07F8E1A680F5B00A75B9A /* Resources */ = {
505 | isa = PBXResourcesBuildPhase;
506 | buildActionMask = 2147483647;
507 | files = (
508 | 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
509 | 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
510 | );
511 | runOnlyForDeploymentPostprocessing = 0;
512 | };
513 | /* End PBXResourcesBuildPhase section */
514 |
515 | /* Begin PBXShellScriptBuildPhase section */
516 | 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
517 | isa = PBXShellScriptBuildPhase;
518 | buildActionMask = 2147483647;
519 | files = (
520 | );
521 | inputPaths = (
522 | );
523 | name = "Bundle React Native code and images";
524 | outputPaths = (
525 | );
526 | runOnlyForDeploymentPostprocessing = 0;
527 | shellPath = /bin/sh;
528 | shellScript = "export NODE_BINARY=node\n../node_modules/react-native/packager/react-native-xcode.sh";
529 | showEnvVarsInLog = 1;
530 | };
531 | /* End PBXShellScriptBuildPhase section */
532 |
533 | /* Begin PBXSourcesBuildPhase section */
534 | 00E356EA1AD99517003FC87E /* Sources */ = {
535 | isa = PBXSourcesBuildPhase;
536 | buildActionMask = 2147483647;
537 | files = (
538 | 00E356F31AD99517003FC87E /* ChatClientTests.m in Sources */,
539 | );
540 | runOnlyForDeploymentPostprocessing = 0;
541 | };
542 | 13B07F871A680F5B00A75B9A /* Sources */ = {
543 | isa = PBXSourcesBuildPhase;
544 | buildActionMask = 2147483647;
545 | files = (
546 | 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
547 | 13B07FC11A68108700A75B9A /* main.m in Sources */,
548 | );
549 | runOnlyForDeploymentPostprocessing = 0;
550 | };
551 | /* End PBXSourcesBuildPhase section */
552 |
553 | /* Begin PBXTargetDependency section */
554 | 00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
555 | isa = PBXTargetDependency;
556 | target = 13B07F861A680F5B00A75B9A /* ChatClient */;
557 | targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
558 | };
559 | /* End PBXTargetDependency section */
560 |
561 | /* Begin PBXVariantGroup section */
562 | 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = {
563 | isa = PBXVariantGroup;
564 | children = (
565 | 13B07FB21A68108700A75B9A /* Base */,
566 | );
567 | name = LaunchScreen.xib;
568 | path = ChatClient;
569 | sourceTree = "";
570 | };
571 | /* End PBXVariantGroup section */
572 |
573 | /* Begin XCBuildConfiguration section */
574 | 00E356F61AD99517003FC87E /* Debug */ = {
575 | isa = XCBuildConfiguration;
576 | buildSettings = {
577 | BUNDLE_LOADER = "$(TEST_HOST)";
578 | FRAMEWORK_SEARCH_PATHS = (
579 | "$(SDKROOT)/Developer/Library/Frameworks",
580 | "$(inherited)",
581 | );
582 | GCC_PREPROCESSOR_DEFINITIONS = (
583 | "DEBUG=1",
584 | "$(inherited)",
585 | );
586 | INFOPLIST_FILE = ChatClientTests/Info.plist;
587 | IPHONEOS_DEPLOYMENT_TARGET = 8.2;
588 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
589 | PRODUCT_NAME = "$(TARGET_NAME)";
590 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChatClient.app/ChatClient";
591 | };
592 | name = Debug;
593 | };
594 | 00E356F71AD99517003FC87E /* Release */ = {
595 | isa = XCBuildConfiguration;
596 | buildSettings = {
597 | BUNDLE_LOADER = "$(TEST_HOST)";
598 | COPY_PHASE_STRIP = NO;
599 | FRAMEWORK_SEARCH_PATHS = (
600 | "$(SDKROOT)/Developer/Library/Frameworks",
601 | "$(inherited)",
602 | );
603 | INFOPLIST_FILE = ChatClientTests/Info.plist;
604 | IPHONEOS_DEPLOYMENT_TARGET = 8.2;
605 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
606 | PRODUCT_NAME = "$(TARGET_NAME)";
607 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChatClient.app/ChatClient";
608 | };
609 | name = Release;
610 | };
611 | 13B07F941A680F5B00A75B9A /* Debug */ = {
612 | isa = XCBuildConfiguration;
613 | buildSettings = {
614 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
615 | DEAD_CODE_STRIPPING = NO;
616 | HEADER_SEARCH_PATHS = (
617 | "$(inherited)",
618 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
619 | "$(SRCROOT)/../node_modules/react-native/React/**",
620 | );
621 | INFOPLIST_FILE = "ChatClient/Info.plist";
622 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
623 | OTHER_LDFLAGS = "-ObjC";
624 | PRODUCT_NAME = ChatClient;
625 | };
626 | name = Debug;
627 | };
628 | 13B07F951A680F5B00A75B9A /* Release */ = {
629 | isa = XCBuildConfiguration;
630 | buildSettings = {
631 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
632 | HEADER_SEARCH_PATHS = (
633 | "$(inherited)",
634 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
635 | "$(SRCROOT)/../node_modules/react-native/React/**",
636 | );
637 | INFOPLIST_FILE = "ChatClient/Info.plist";
638 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
639 | OTHER_LDFLAGS = "-ObjC";
640 | PRODUCT_NAME = ChatClient;
641 | };
642 | name = Release;
643 | };
644 | 83CBBA201A601CBA00E9B192 /* Debug */ = {
645 | isa = XCBuildConfiguration;
646 | buildSettings = {
647 | ALWAYS_SEARCH_USER_PATHS = NO;
648 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
649 | CLANG_CXX_LIBRARY = "libc++";
650 | CLANG_ENABLE_MODULES = YES;
651 | CLANG_ENABLE_OBJC_ARC = YES;
652 | CLANG_WARN_BOOL_CONVERSION = YES;
653 | CLANG_WARN_CONSTANT_CONVERSION = YES;
654 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
655 | CLANG_WARN_EMPTY_BODY = YES;
656 | CLANG_WARN_ENUM_CONVERSION = YES;
657 | CLANG_WARN_INT_CONVERSION = YES;
658 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
659 | CLANG_WARN_UNREACHABLE_CODE = YES;
660 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
661 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
662 | COPY_PHASE_STRIP = NO;
663 | ENABLE_STRICT_OBJC_MSGSEND = YES;
664 | GCC_C_LANGUAGE_STANDARD = gnu99;
665 | GCC_DYNAMIC_NO_PIC = NO;
666 | GCC_OPTIMIZATION_LEVEL = 0;
667 | GCC_PREPROCESSOR_DEFINITIONS = (
668 | "DEBUG=1",
669 | "$(inherited)",
670 | );
671 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
672 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
673 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
674 | GCC_WARN_UNDECLARED_SELECTOR = YES;
675 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
676 | GCC_WARN_UNUSED_FUNCTION = YES;
677 | GCC_WARN_UNUSED_VARIABLE = YES;
678 | HEADER_SEARCH_PATHS = (
679 | "$(inherited)",
680 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
681 | "$(SRCROOT)/../node_modules/react-native/React/**",
682 | );
683 | IPHONEOS_DEPLOYMENT_TARGET = 7.0;
684 | MTL_ENABLE_DEBUG_INFO = YES;
685 | ONLY_ACTIVE_ARCH = YES;
686 | SDKROOT = iphoneos;
687 | };
688 | name = Debug;
689 | };
690 | 83CBBA211A601CBA00E9B192 /* Release */ = {
691 | isa = XCBuildConfiguration;
692 | buildSettings = {
693 | ALWAYS_SEARCH_USER_PATHS = NO;
694 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
695 | CLANG_CXX_LIBRARY = "libc++";
696 | CLANG_ENABLE_MODULES = YES;
697 | CLANG_ENABLE_OBJC_ARC = YES;
698 | CLANG_WARN_BOOL_CONVERSION = YES;
699 | CLANG_WARN_CONSTANT_CONVERSION = YES;
700 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
701 | CLANG_WARN_EMPTY_BODY = YES;
702 | CLANG_WARN_ENUM_CONVERSION = YES;
703 | CLANG_WARN_INT_CONVERSION = YES;
704 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
705 | CLANG_WARN_UNREACHABLE_CODE = YES;
706 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
707 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
708 | COPY_PHASE_STRIP = YES;
709 | ENABLE_NS_ASSERTIONS = NO;
710 | ENABLE_STRICT_OBJC_MSGSEND = YES;
711 | GCC_C_LANGUAGE_STANDARD = gnu99;
712 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
713 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
714 | GCC_WARN_UNDECLARED_SELECTOR = YES;
715 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
716 | GCC_WARN_UNUSED_FUNCTION = YES;
717 | GCC_WARN_UNUSED_VARIABLE = YES;
718 | HEADER_SEARCH_PATHS = (
719 | "$(inherited)",
720 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
721 | "$(SRCROOT)/../node_modules/react-native/React/**",
722 | );
723 | IPHONEOS_DEPLOYMENT_TARGET = 7.0;
724 | MTL_ENABLE_DEBUG_INFO = NO;
725 | SDKROOT = iphoneos;
726 | VALIDATE_PRODUCT = YES;
727 | };
728 | name = Release;
729 | };
730 | /* End XCBuildConfiguration section */
731 |
732 | /* Begin XCConfigurationList section */
733 | 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "ChatClientTests" */ = {
734 | isa = XCConfigurationList;
735 | buildConfigurations = (
736 | 00E356F61AD99517003FC87E /* Debug */,
737 | 00E356F71AD99517003FC87E /* Release */,
738 | );
739 | defaultConfigurationIsVisible = 0;
740 | defaultConfigurationName = Release;
741 | };
742 | 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ChatClient" */ = {
743 | isa = XCConfigurationList;
744 | buildConfigurations = (
745 | 13B07F941A680F5B00A75B9A /* Debug */,
746 | 13B07F951A680F5B00A75B9A /* Release */,
747 | );
748 | defaultConfigurationIsVisible = 0;
749 | defaultConfigurationName = Release;
750 | };
751 | 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ChatClient" */ = {
752 | isa = XCConfigurationList;
753 | buildConfigurations = (
754 | 83CBBA201A601CBA00E9B192 /* Debug */,
755 | 83CBBA211A601CBA00E9B192 /* Release */,
756 | );
757 | defaultConfigurationIsVisible = 0;
758 | defaultConfigurationName = Release;
759 | };
760 | /* End XCConfigurationList section */
761 | };
762 | rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
763 | }
764 |
--------------------------------------------------------------------------------
/ChatClient/ios/ChatClient.xcodeproj/xcshareddata/xcschemes/ChatClient.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
75 |
77 |
83 |
84 |
85 |
86 |
87 |
88 |
94 |
96 |
102 |
103 |
104 |
105 |
107 |
108 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/ChatClient/ios/ChatClient/AppDelegate.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 |
10 | #import
11 |
12 | @interface AppDelegate : UIResponder
13 |
14 | @property (nonatomic, strong) UIWindow *window;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/ChatClient/ios/ChatClient/AppDelegate.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 |
10 | #import "AppDelegate.h"
11 |
12 | #import "RCTRootView.h"
13 |
14 | @implementation AppDelegate
15 |
16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
17 | {
18 | NSURL *jsCodeLocation;
19 |
20 | /**
21 | * Loading JavaScript code - uncomment the one you want.
22 | *
23 | * OPTION 1
24 | * Load from development server. Start the server from the repository root:
25 | *
26 | * $ npm start
27 | *
28 | * To run on device, change `localhost` to the IP address of your computer
29 | * (you can get this by typing `ifconfig` into the terminal and selecting the
30 | * `inet` value under `en0:`) and make sure your computer and iOS device are
31 | * on the same Wi-Fi network.
32 | */
33 |
34 | jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
35 |
36 | /**
37 | * OPTION 2
38 | * Load from pre-bundled file on disk. The static bundle is automatically
39 | * generated by "Bundle React Native code and images" build step.
40 | */
41 |
42 | // jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
43 |
44 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
45 | moduleName:@"ChatClient"
46 | initialProperties:nil
47 | launchOptions:launchOptions];
48 |
49 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
50 | UIViewController *rootViewController = [UIViewController new];
51 | rootViewController.view = rootView;
52 | self.window.rootViewController = rootViewController;
53 | [self.window makeKeyAndVisible];
54 | return YES;
55 | }
56 |
57 | @end
58 |
--------------------------------------------------------------------------------
/ChatClient/ios/ChatClient/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/ChatClient/ios/ChatClient/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/ChatClient/ios/ChatClient/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UIViewControllerBasedStatusBarAppearance
38 |
39 | NSLocationWhenInUseUsageDescription
40 |
41 | NSAppTransportSecurity
42 |
43 |
44 | NSAllowsArbitraryLoads
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/ChatClient/ios/ChatClient/main.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 |
10 | #import
11 |
12 | #import "AppDelegate.h"
13 |
14 | int main(int argc, char * argv[]) {
15 | @autoreleasepool {
16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ChatClient/ios/ChatClientTests/ChatClientTests.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 |
10 | #import
11 | #import
12 |
13 | #import "RCTLog.h"
14 | #import "RCTRootView.h"
15 |
16 | #define TIMEOUT_SECONDS 240
17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!"
18 |
19 | @interface ChatClientTests : XCTestCase
20 |
21 | @end
22 |
23 | @implementation ChatClientTests
24 |
25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
26 | {
27 | if (test(view)) {
28 | return YES;
29 | }
30 | for (UIView *subview in [view subviews]) {
31 | if ([self findSubviewInView:subview matching:test]) {
32 | return YES;
33 | }
34 | }
35 | return NO;
36 | }
37 |
38 | - (void)testRendersWelcomeScreen
39 | {
40 | UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
42 | BOOL foundElement = NO;
43 |
44 | __block NSString *redboxError = nil;
45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
46 | if (level >= RCTLogLevelError) {
47 | redboxError = message;
48 | }
49 | });
50 |
51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
54 |
55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
57 | return YES;
58 | }
59 | return NO;
60 | }];
61 | }
62 |
63 | RCTSetLogFunction(RCTDefaultLogFunction);
64 |
65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
67 | }
68 |
69 |
70 | @end
71 |
--------------------------------------------------------------------------------
/ChatClient/ios/ChatClientTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/ChatClient/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ChatClient",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "start": "node node_modules/react-native/local-cli/cli.js start"
7 | },
8 | "dependencies": {
9 | "moment": "^2.11.2",
10 | "react-native": "^0.19.0",
11 | "react-native-gifted-messenger": "0.0.18"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ChatServer/.gitignore:
--------------------------------------------------------------------------------
1 | # Mix artifacts
2 | /_build
3 | /deps
4 | /*.ez
5 |
6 | # Generate on crash by the VM
7 | erl_crash.dump
8 |
9 | # Static artifacts
10 | /node_modules
11 |
12 | # Since we are building js and css from web/static,
13 | # we ignore priv/static/{css,js}. You may want to
14 | # comment this depending on your deployment strategy.
15 | # /priv/static/css
16 | # /priv/static/js
17 | .DS_Store
--------------------------------------------------------------------------------
/ChatServer/Procfile:
--------------------------------------------------------------------------------
1 | web: elixir -pa _build/prod/consolidated -S mix phoenix.server
--------------------------------------------------------------------------------
/ChatServer/README.md:
--------------------------------------------------------------------------------
1 | # Simple Chat Example
2 | > Built with the [Phoenix Framework](https://github.com/phoenixframework/phoenix)
3 |
4 | To start your new Phoenix application you have to:
5 |
6 | 1. Clone this repo, then cd to the new directory
7 | 2. Install dependencies with `mix deps.get`
8 | 3. (optional) Install npm dependencies to customize the ES6 js/Sass `npm install`
9 | 4. Start Phoenix router with `mix phoenix.server`
10 |
11 | Now you can visit `localhost:4000` from your browser.
12 |
13 | ## Live Demo
14 | http://phoenixchat.herokuapp.com
15 |
16 |
17 | ## Example Code
18 |
19 | #### JavaScript
20 | ```javascript
21 | import {Socket, LongPoller} from "phoenix"
22 |
23 | class App {
24 |
25 | static init(){
26 | let socket = new Socket("/socket", {
27 | logger: ((kind, msg, data) => { console.log(`${kind}: ${msg}`, data) })
28 | })
29 |
30 | socket.connect({user_id: "123"})
31 | var $status = $("#status")
32 | var $messages = $("#messages")
33 | var $input = $("#message-input")
34 | var $username = $("#username")
35 |
36 | socket.onOpen( ev => console.log("OPEN", ev) )
37 | socket.onError( ev => console.log("ERROR", ev) )
38 | socket.onClose( e => console.log("CLOSE", e))
39 |
40 | var chan = socket.channel("rooms:lobby", {})
41 | chan.join().receive("ignore", () => console.log("auth error"))
42 | .receive("ok", () => console.log("join ok"))
43 | .after(10000, () => console.log("Connection interruption"))
44 | chan.onError(e => console.log("something went wrong", e))
45 | chan.onClose(e => console.log("channel closed", e))
46 |
47 | $input.off("keypress").on("keypress", e => {
48 | if (e.keyCode == 13) {
49 | chan.push("new:msg", {user: $username.val(), body: $input.val()})
50 | $input.val("")
51 | }
52 | })
53 |
54 | chan.on("new:msg", msg => {
55 | $messages.append(this.messageTemplate(msg))
56 | scrollTo(0, document.body.scrollHeight)
57 | })
58 |
59 | chan.on("user:entered", msg => {
60 | var username = this.sanitize(msg.user || "anonymous")
61 | $messages.append(`[${username} entered] `)
62 | })
63 | }
64 |
65 | static sanitize(html){ return $("
").text(html).html() }
66 |
67 | static messageTemplate(msg){
68 | let username = this.sanitize(msg.user || "anonymous")
69 | let body = this.sanitize(msg.body)
70 |
71 | return(`[${username}] ${body}
`)
72 | }
73 |
74 | }
75 |
76 | $( () => App.init() )
77 |
78 | export default App
79 | ```
80 |
81 | #### Endpoint
82 | ```elixir
83 | # lib/chat/endpoint.ex
84 | defmodule Chat.Endpoint do
85 | use Phoenix.Endpoint
86 |
87 | socket "/socket", Chat.UserSocket
88 | ...
89 | end
90 | ```
91 |
92 | #### Socket
93 | ```elixir
94 | # web/channels/user_socket.ex
95 | defmodule Chat.UserSocket do
96 | use Phoenix.Socket
97 |
98 | channel "rooms:*", Chat.RoomChannel
99 |
100 | transport :websocket, Phoenix.Transports.WebSocket
101 | transport :longpoll, Phoenix.Transports.LongPoll
102 | ...
103 | end
104 | ```
105 |
106 | #### Channel
107 | ```elixir
108 | defmodule Chat.RoomChannel do
109 | use Phoenix.Channel
110 | require Logger
111 |
112 | def join("rooms:lobby", message, socket) do
113 | Process.flag(:trap_exit, true)
114 | :timer.send_interval(5000, :ping)
115 | send(self, {:after_join, message})
116 |
117 | {:ok, socket}
118 | end
119 |
120 | def join("rooms:" <> _private_subtopic, _message, _socket) do
121 | {:error, %{reason: "unauthorized"}}
122 | end
123 |
124 | def handle_info({:after_join, msg}, socket) do
125 | broadcast! socket, "user:entered", %{user: msg["user"]}
126 | push socket, "join", %{status: "connected"}
127 | {:noreply, socket}
128 | end
129 | def handle_info(:ping, socket) do
130 | push socket, "new:msg", %{user: "SYSTEM", body: "ping"}
131 | {:noreply, socket}
132 | end
133 |
134 | def terminate(reason, _socket) do
135 | Logger.debug"> leave #{inspect reason}"
136 | :ok
137 | end
138 |
139 | def handle_in("new:msg", msg, socket) do
140 | broadcast! socket, "new:msg", %{user: msg["user"], body: msg["body"]}
141 | {:reply, {:ok, %{msg: msg["body"]}}, assign(socket, :user, msg["user"])}
142 | end
143 | end
144 | ```
145 |
--------------------------------------------------------------------------------
/ChatServer/brunch-config.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | // See http://brunch.io/#documentation for docs.
3 | files: {
4 | javascripts: {
5 | joinTo: "js/app.js"
6 |
7 | // To use a separate vendor.js bundle, specify two files path
8 | // https://github.com/brunch/brunch/blob/stable/docs/config.md#files
9 | // joinTo: {
10 | // "js/app.js": /^(web\/static\/js)/,
11 | // "js/vendor.js": /^(web\/static\/vendor)|(deps)/
12 | // }
13 | //
14 | // To change the order of concatenation of files, explicitly mention here
15 | // https://github.com/brunch/brunch/tree/master/docs#concatenation
16 | // order: {
17 | // before: [
18 | // "web/static/vendor/js/jquery-2.1.1.js",
19 | // "web/static/vendor/js/bootstrap.min.js"
20 | // ]
21 | // }
22 | },
23 | stylesheets: {
24 | joinTo: "css/app.css"
25 | },
26 | templates: {
27 | joinTo: "js/app.js"
28 | }
29 | },
30 |
31 | conventions: {
32 | // This option sets where we should place non-css and non-js assets in.
33 | // By default, we set this to "/web/static/assets". Files in this directory
34 | // will be copied to `paths.public`, which is "priv/static" by default.
35 | assets: /^(web\/static\/assets)/
36 | },
37 |
38 | // Phoenix paths configuration
39 | paths: {
40 | // Dependencies and current project directories to watch
41 | watched: [
42 | "web/static",
43 | "test/static"
44 | ],
45 |
46 | // Where to compile files to
47 | public: "priv/static"
48 | },
49 |
50 | // Configure your plugins
51 | plugins: {
52 | babel: {
53 | // Do not use ES6 compiler in vendor code
54 | ignore: [/web\/static\/vendor/]
55 | }
56 | },
57 |
58 | modules: {
59 | autoRequire: {
60 | "js/app.js": ["web/static/js/app"]
61 | }
62 | },
63 |
64 | npm: {
65 | enabled: true,
66 | // Whitelist the npm deps to be pulled in as front-end assets.
67 | // All other deps in package.json will be excluded from the bundle.
68 | whitelist: ["phoenix", "phoenix_html"]
69 | }
70 | };
71 |
--------------------------------------------------------------------------------
/ChatServer/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 | use Mix.Config
7 |
8 | # Configures the endpoint
9 | config :chat, Chat.Endpoint,
10 | url: [host: "localhost"],
11 | root: Path.expand("..", __DIR__),
12 | secret_key_base: "/RjKJmMO6raXPRTq63qTqid1x6lVKTOP+FTxZHfX6Ogd+1xYmH6eZZFhBu1CIwtg",
13 | debug_errors: false,
14 | pubsub: [name: Chat.PubSub,
15 | adapter: Phoenix.PubSub.PG2]
16 |
17 | # Configures Elixir's Logger
18 | config :logger, :console,
19 | format: "$time $metadata[$level] $message\n",
20 | metadata: [:request_id]
21 |
22 | # Import environment specific config. This must remain at the bottom
23 | # of this file so it overrides the configuration defined above.
24 | import_config "#{Mix.env}.exs"
25 |
--------------------------------------------------------------------------------
/ChatServer/config/dev.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we use it
8 | # with brunch.io to recompile .js and .css sources.
9 | config :chat, Chat.Endpoint,
10 | http: [port: System.get_env("PORT") || 4000],
11 | debug_errors: true,
12 | cache_static_lookup: false,
13 | code_reloader: true,
14 | watchers: [node: ["node_modules/brunch/bin/brunch", "watch"]]
15 |
16 | # Watch static and templates for browser reloading.
17 | config :chat, Chat.Endpoint,
18 | live_reload: [
19 | patterns: [
20 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif)$},
21 | ~r{web/views/.*(ex)$},
22 | ~r{web/templates/.*(eex)$}
23 | ]
24 | ]
25 |
26 | # Do not include metadata nor timestamps in development logs
27 | config :logger, :console, format: "[$level] $message\n"
28 |
29 | # Configure your database
30 | config :chat, Chat.Repo,
31 | adapter: Ecto.Adapters.Postgres,
32 | username: "postgres",
33 | password: "postgres",
34 | database: "chat_dev"
35 |
--------------------------------------------------------------------------------
/ChatServer/config/prod.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For production, we configure the host to read the PORT
4 | # from the system environment. Therefore, you will need
5 | # to set PORT=80 before running your server.
6 | #
7 | # You should also configure the url host to something
8 | # meaningful, we use this information when generating URLs.
9 | config :chat, Chat.Endpoint,
10 | http: [port: {:system, "PORT"}],
11 | url: [host: "example.com"]
12 |
13 | # ## SSL Support
14 | #
15 | # To get SSL working, you will need to add the `https` key
16 | # to the previous section:
17 | #
18 | # config:chat, Chat.Endpoint,
19 | # ...
20 | # https: [port: 443,
21 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
22 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
23 | #
24 | # Where those two env variables point to a file on
25 | # disk for the key and cert.
26 |
27 | # Do not print debug messages in production
28 | config :logger, level: :info
29 |
30 | # ## Using releases
31 | #
32 | # If you are doing OTP releases, you need to instruct Phoenix
33 | # to start the server for all endpoints:
34 | #
35 | # config :phoenix, :serve_endpoints, true
36 | #
37 | # Alternatively, you can configure exactly which server to
38 | # start per endpoint:
39 | #
40 | # config :chat, Chat.Endpoint, server: true
41 | #
42 |
43 | # Finally import the config/prod.secret.exs
44 | # which should be versioned separately.
45 | import_config "prod.secret.exs"
46 |
--------------------------------------------------------------------------------
/ChatServer/config/prod.secret.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # In this file, we keep production configuration that
4 | # you likely want to automate and keep it away from
5 | # your version control system.
6 | config :chat, Chat.Endpoint,
7 | secret_key_base: "XR7e8rPXq2nIdBXqtPsyxPz1R1UF3w4HDBFGdxZ+9GDZCT6PpG4aJLpOzehOJVO5"
8 |
9 | # Configure your database
10 | config :chat, Chat.Repo,
11 | adapter: Ecto.Adapters.Postgres,
12 | username: "postgres",
13 | password: "postgres",
14 | database: "chat_prod"
15 |
--------------------------------------------------------------------------------
/ChatServer/config/test.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # We don't run a server during test. If one is required,
4 | # you can enable the server option below.
5 | config :chat, Chat.Endpoint,
6 | http: [port: 4001],
7 | server: false
8 |
9 | # Print only warnings and errors during test
10 | config :logger, level: :warn
11 |
12 | # Configure your database
13 | config :chat, Chat.Repo,
14 | adapter: Ecto.Adapters.Postgres,
15 | username: "postgres",
16 | password: "postgres",
17 | database: "chat_test",
18 | size: 1,
19 | max_overflow: false
20 |
--------------------------------------------------------------------------------
/ChatServer/elixir_buildpack.config:
--------------------------------------------------------------------------------
1 | # Erlang version
2 | erlang_version=17.0
3 |
4 | # Elixir version
5 | elixir_version=1.0.3
6 |
7 | # Rebar version
8 | rebar_version=(tag 2.2.0)
9 |
10 | # Do dependencies have to be built from scratch on every deploy?
11 | always_build_deps=false
12 |
--------------------------------------------------------------------------------
/ChatServer/lib/chat.ex:
--------------------------------------------------------------------------------
1 | defmodule Chat do
2 | use Application
3 |
4 | # See http://elixir-lang.org/docs/stable/elixir/Application.html
5 | # for more information on OTP Applications
6 | def start(_type, _args) do
7 | import Supervisor.Spec, warn: false
8 |
9 | children = [
10 | # Start the endpoint when the application starts
11 | supervisor(Chat.Endpoint, []),
12 | # Start the Ecto repository
13 | worker(Chat.Repo, []),
14 | # Here you could define other workers and supervisors as children
15 | # worker(Chat.Worker, [arg1, arg2, arg3]),
16 | ]
17 |
18 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
19 | # for other strategies and supported options
20 | opts = [strategy: :one_for_one, name: Chat.Supervisor]
21 | Supervisor.start_link(children, opts)
22 | end
23 |
24 | # Tell Phoenix to update the endpoint configuration
25 | # whenever the application is updated.
26 | def config_change(changed, _new, removed) do
27 | Chat.Endpoint.config_change(changed, removed)
28 | :ok
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/ChatServer/lib/chat/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule Chat.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :chat
3 |
4 | socket "/socket", Chat.UserSocket
5 |
6 |
7 | # Serve at "/" the given assets from "priv/static" directory
8 | plug Plug.Static,
9 | at: "/", from: :chat,
10 | only: ~w(css images js favicon.ico robots.txt)
11 |
12 | # Code reloading will only work if the :code_reloader key of
13 | # the :phoenix application is set to true in your config file.
14 | if code_reloading? do
15 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
16 | plug Phoenix.CodeReloader
17 | plug Phoenix.LiveReloader
18 | end
19 |
20 |
21 | plug Plug.Logger
22 |
23 | plug Plug.Parsers,
24 | parsers: [:urlencoded, :multipart, :json],
25 | pass: ["*/*"],
26 | json_decoder: Poison
27 |
28 | plug Plug.MethodOverride
29 | plug Plug.Head
30 |
31 | plug Plug.Session,
32 | store: :cookie,
33 | key: "_chat_key",
34 | signing_salt: "LH6XmqGb",
35 | encryption_salt: "CIPZg4Qo"
36 |
37 | plug Chat.Router
38 | end
39 |
--------------------------------------------------------------------------------
/ChatServer/lib/chat/repo.ex:
--------------------------------------------------------------------------------
1 | defmodule Chat.Repo do
2 | use Ecto.Repo, otp_app: :chat
3 | end
4 |
--------------------------------------------------------------------------------
/ChatServer/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule Chat.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [app: :chat,
6 | version: "0.0.1",
7 | elixir: "~> 1.0",
8 | elixirc_paths: ["lib", "web"],
9 | compilers: [:phoenix] ++ Mix.compilers,
10 | deps: deps]
11 | end
12 |
13 | # Configuration for the OTP application
14 | #
15 | # Type `mix help compile.app` for more information
16 | def application do
17 | [mod: {Chat, []},
18 | applications: [:phoenix, :phoenix_html, :cowboy, :logger]]
19 | end
20 |
21 | # Specifies your project dependencies
22 | #
23 | # Type `mix help deps` for examples and options
24 | defp deps do
25 | [{:phoenix, "~> 1.1.2"},
26 | {:phoenix_html, "~> 2.4"},
27 | {:phoenix_live_reload, "~> 1.0", only: :dev},
28 | {:phoenix_ecto, "~> 1.1"},
29 | {:postgrex, ">= 0.0.0"},
30 | {:cowboy, "~> 1.0"}]
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/ChatServer/mix.lock:
--------------------------------------------------------------------------------
1 | %{"cowboy": {:hex, :cowboy, "1.0.4"},
2 | "cowlib": {:hex, :cowlib, "1.0.2"},
3 | "decimal": {:hex, :decimal, "1.1.0"},
4 | "ecto": {:hex, :ecto, "1.0.1"},
5 | "fs": {:hex, :fs, "0.9.2"},
6 | "phoenix": {:hex, :phoenix, "1.1.4"},
7 | "phoenix_ecto": {:hex, :phoenix_ecto, "1.2.0"},
8 | "phoenix_html": {:hex, :phoenix_html, "2.5.0"},
9 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.0"},
10 | "plug": {:hex, :plug, "1.1.0"},
11 | "poison": {:hex, :poison, "1.5.2"},
12 | "poolboy": {:hex, :poolboy, "1.5.1"},
13 | "postgrex": {:hex, :postgrex, "0.9.1"},
14 | "ranch": {:hex, :ranch, "1.2.1"},
15 | "redo": {:hex, :redo, "2.0.1"}}
16 |
--------------------------------------------------------------------------------
/ChatServer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": {
3 | },
4 | "dependencies": {
5 | "babel-brunch": "~6.0.0",
6 | "brunch": "~2.1.3",
7 | "clean-css-brunch": "~1.8.0",
8 | "css-brunch": "~1.7.0",
9 | "javascript-brunch": "~1.8.0",
10 | "uglify-js-brunch": "~1.7.0",
11 | "phoenix": "file:deps/phoenix",
12 | "phoenix_html": "file:deps/phoenix_html"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ChatServer/priv/static/images/phoenix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skellock/phoenix-react-native-mashup/3c22b7ae060c0c2a5b18f459d6a65b7c6defbbe9/ChatServer/priv/static/images/phoenix.png
--------------------------------------------------------------------------------
/ChatServer/test/chat_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ChatTest do
2 | use ExUnit.Case
3 |
4 | test "the truth" do
5 | assert 1 + 1 == 2
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/ChatServer/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start
2 |
--------------------------------------------------------------------------------
/ChatServer/web/channels/room_channel.ex:
--------------------------------------------------------------------------------
1 | defmodule Chat.RoomChannel do
2 | use Phoenix.Channel
3 | require Logger
4 |
5 | @doc """
6 | Authorize socket to subscribe and broadcast events on this channel & topic
7 |
8 | Possible Return Values
9 |
10 | `{:ok, socket}` to authorize subscription for channel for requested topic
11 |
12 | `:ignore` to deny subscription/broadcast on this channel
13 | for the requested topic
14 | """
15 | def join("rooms:lobby", message, socket) do
16 | Process.flag(:trap_exit, true)
17 | # :timer.send_interval(5000, :ping)
18 | send(self, {:after_join, message})
19 |
20 | {:ok, socket}
21 | end
22 |
23 | def join("rooms:" <> _private_subtopic, _message, _socket) do
24 | {:error, %{reason: "unauthorized"}}
25 | end
26 |
27 | def handle_info({:after_join, msg}, socket) do
28 | broadcast! socket, "user:entered", %{user: msg["user"]}
29 | push socket, "join", %{status: "connected"}
30 | {:noreply, socket}
31 | end
32 | def handle_info(:ping, socket) do
33 | push socket, "new:msg", %{user: "SYSTEM", body: "ping"}
34 | {:noreply, socket}
35 | end
36 |
37 | def terminate(reason, _socket) do
38 | Logger.debug"> leave #{inspect reason}"
39 | :ok
40 | end
41 |
42 | def handle_in("new:msg", msg, socket) do
43 | broadcast! socket, "new:msg", %{user: msg["user"], body: msg["body"]}
44 | {:reply, {:ok, %{msg: msg["body"]}}, assign(socket, :user, msg["user"])}
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/ChatServer/web/channels/user_socket.ex:
--------------------------------------------------------------------------------
1 | defmodule Chat.UserSocket do
2 | use Phoenix.Socket
3 |
4 | channel "rooms:*", Chat.RoomChannel
5 |
6 | transport :websocket, Phoenix.Transports.WebSocket
7 | transport :longpoll, Phoenix.Transports.LongPoll
8 |
9 | def connect(_params, socket) do
10 | {:ok, socket}
11 | end
12 |
13 | def id(_socket), do: nil
14 | end
15 |
--------------------------------------------------------------------------------
/ChatServer/web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule Chat.PageController do
2 | use Chat.Web, :controller
3 |
4 | def index(conn, _params) do
5 | render conn, "index.html"
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/ChatServer/web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule Chat.Router do
2 | use Phoenix.Router
3 |
4 | pipeline :browser do
5 | plug :accepts, ["html"]
6 | plug :fetch_session
7 | plug :fetch_flash
8 | plug :protect_from_forgery
9 | end
10 |
11 | pipeline :api do
12 | plug :accepts, ["json"]
13 | end
14 |
15 | scope "/", Chat do
16 | pipe_through :browser # Use the default browser stack
17 |
18 | get "/", PageController, :index
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/ChatServer/web/static/css/app.css:
--------------------------------------------------------------------------------
1 | /* Sticky footer styles
2 | -------------------------------------------------- */
3 | html {
4 | position: relative;
5 | min-height: 100%;
6 | }
7 | body {
8 | /* Margin bottom by footer height */
9 | margin-top: 50px;
10 | margin-bottom: 60px;
11 | }
12 |
13 | #footer {
14 | position: fixed;
15 | bottom: 0px;
16 | width: 100%;
17 | /* Set the fixed height of the footer here */
18 | height: 60px;
19 | padding-top: 1em;
20 | background-color: #f5f5f5;
21 | }
22 |
23 | #messages {
24 | margin-top: 0px;
25 | }
26 | /* Custom page CSS
27 | -------------------------------------------------- */
28 | /* Not required for template or sticky footer method. */
29 |
30 | body > .container {
31 | padding: 40px 15px 0;
32 | }
33 | .container .text-muted {
34 | margin: 20px 0;
35 | }
36 |
37 | #footer > .container {
38 | padding-right: 15px;
39 | padding-left: 15px;
40 | }
41 |
42 | code {
43 | font-size: 80%;
44 | }
45 |
--------------------------------------------------------------------------------
/ChatServer/web/static/js/app.js:
--------------------------------------------------------------------------------
1 | import {Socket, LongPoller} from "phoenix"
2 |
3 | class App {
4 |
5 | static init(){
6 | let socket = new Socket("/socket", {
7 | logger: ((kind, msg, data) => { console.log(`${kind}: ${msg}`, data) })
8 | })
9 |
10 | socket.connect({user_id: "123"})
11 | var $status = $("#status")
12 | var $messages = $("#messages")
13 | var $input = $("#message-input")
14 | var $username = $("#username")
15 |
16 | socket.onOpen( ev => console.log("OPEN", ev) )
17 | socket.onError( ev => console.log("ERROR", ev) )
18 | socket.onClose( e => console.log("CLOSE", e))
19 |
20 | var chan = socket.channel("rooms:lobby", {})
21 | chan.join().receive("ignore", () => console.log("auth error"))
22 | .receive("ok", () => console.log("join ok"))
23 | .after(10000, () => console.log("Connection interruption"))
24 | chan.onError(e => console.log("something went wrong", e))
25 | chan.onClose(e => console.log("channel closed", e))
26 |
27 | $input.off("keypress").on("keypress", e => {
28 | if (e.keyCode == 13) {
29 | chan.push("new:msg", {user: $username.val(), body: $input.val()})
30 | $input.val("")
31 | }
32 | })
33 |
34 | chan.on("new:msg", msg => {
35 | $messages.append(this.messageTemplate(msg))
36 | scrollTo(0, document.body.scrollHeight)
37 | })
38 |
39 | chan.on("user:entered", msg => {
40 | var username = this.sanitize(msg.user || "anonymous")
41 | $messages.append(`[${username} entered] `)
42 | })
43 | }
44 |
45 | static sanitize(html){ return $("
").text(html).html() }
46 |
47 | static messageTemplate(msg){
48 | let username = this.sanitize(msg.user || "anonymous")
49 | let body = this.sanitize(msg.body)
50 |
51 | return(`[${username}] ${body}
`)
52 | }
53 |
54 | }
55 |
56 | $( () => App.init() )
57 |
58 | export default App
59 |
--------------------------------------------------------------------------------
/ChatServer/web/static/vendor/bootstrap-theme.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.1 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-default .badge,.btn-primary .badge,.btn-success .badge,.btn-info .badge,.btn-warning .badge,.btn-danger .badge{text-shadow:none}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:hover,.btn-primary:focus{background-color:#265a88;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#265a88;border-color:#245580}.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:hover .badge,.list-group-item.active:focus .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)}
--------------------------------------------------------------------------------
/ChatServer/web/static/vendor/phoenix.js:
--------------------------------------------------------------------------------
1 | (function(/*! Brunch !*/) {
2 | 'use strict';
3 |
4 | var globals = typeof window !== 'undefined' ? window : global;
5 | if (typeof globals.require === 'function') return;
6 |
7 | var modules = {};
8 | var cache = {};
9 |
10 | var has = function(object, name) {
11 | return ({}).hasOwnProperty.call(object, name);
12 | };
13 |
14 | var expand = function(root, name) {
15 | var results = [], parts, part;
16 | if (/^\.\.?(\/|$)/.test(name)) {
17 | parts = [root, name].join('/').split('/');
18 | } else {
19 | parts = name.split('/');
20 | }
21 | for (var i = 0, length = parts.length; i < length; i++) {
22 | part = parts[i];
23 | if (part === '..') {
24 | results.pop();
25 | } else if (part !== '.' && part !== '') {
26 | results.push(part);
27 | }
28 | }
29 | return results.join('/');
30 | };
31 |
32 | var dirname = function(path) {
33 | return path.split('/').slice(0, -1).join('/');
34 | };
35 |
36 | var localRequire = function(path) {
37 | return function(name) {
38 | var dir = dirname(path);
39 | var absolute = expand(dir, name);
40 | return globals.require(absolute, path);
41 | };
42 | };
43 |
44 | var initModule = function(name, definition) {
45 | var module = {id: name, exports: {}};
46 | cache[name] = module;
47 | definition(module.exports, localRequire(name), module);
48 | return module.exports;
49 | };
50 |
51 | var require = function(name, loaderPath) {
52 | var path = expand(name, '.');
53 | if (loaderPath == null) loaderPath = '/';
54 |
55 | if (has(cache, path)) return cache[path].exports;
56 | if (has(modules, path)) return initModule(path, modules[path]);
57 |
58 | var dirIndex = expand(path, './index');
59 | if (has(cache, dirIndex)) return cache[dirIndex].exports;
60 | if (has(modules, dirIndex)) return initModule(dirIndex, modules[dirIndex]);
61 |
62 | throw new Error('Cannot find module "' + name + '" from '+ '"' + loaderPath + '"');
63 | };
64 |
65 | var define = function(bundle, fn) {
66 | if (typeof bundle === 'object') {
67 | for (var key in bundle) {
68 | if (has(bundle, key)) {
69 | modules[key] = bundle[key];
70 | }
71 | }
72 | } else {
73 | modules[bundle] = fn;
74 | }
75 | };
76 |
77 | var list = function() {
78 | var result = [];
79 | for (var item in modules) {
80 | if (has(modules, item)) {
81 | result.push(item);
82 | }
83 | }
84 | return result;
85 | };
86 |
87 | globals.require = require;
88 | globals.require.define = define;
89 | globals.require.register = define;
90 | globals.require.list = list;
91 | globals.require.brunch = true;
92 | })();
93 | require.define({'phoenix': function(exports, require, module){ "use strict";
94 |
95 | var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); };
96 |
97 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
98 |
99 | // Phoenix Channels JavaScript client
100 | //
101 | // ## Socket Connection
102 | //
103 | // A single connection is established to the server and
104 | // channels are mulitplexed over the connection.
105 | // Connect to the server using the `Socket` class:
106 | //
107 | // let socket = new Socket("/ws")
108 | // socket.connect({userToken: "123"})
109 | //
110 | // The `Socket` constructor takes the mount point of the socket
111 | // as well as options that can be found in the Socket docs,
112 | // such as configuring the `LongPoll` transport, and heartbeat.
113 | // Socket params can also be passed as an object literal to `connect`.
114 | //
115 | // ## Channels
116 | //
117 | // Channels are isolated, concurrent processes on the server that
118 | // subscribe to topics and broker events between the client and server.
119 | // To join a channel, you must provide the topic, and channel params for
120 | // authorization. Here's an example chat room example where `"new_msg"`
121 | // events are listened for, messages are pushed to the server, and
122 | // the channel is joined with ok/error matches, and `after` hook:
123 | //
124 | // let channel = socket.channel("rooms:123", {token: roomToken})
125 | // channel.on("new_msg", msg => console.log("Got message", msg) )
126 | // $input.onEnter( e => {
127 | // channel.push("new_msg", {body: e.target.val})
128 | // .receive("ok", (msg) => console.log("created message", msg) )
129 | // .receive("error", (reasons) => console.log("create failed", reasons) )
130 | // .after(10000, () => console.log("Networking issue. Still waiting...") )
131 | // })
132 | // channel.join()
133 | // .receive("ok", ({messages}) => console.log("catching up", messages) )
134 | // .receive("error", ({reason}) => console.log("failed join", reason) )
135 | // .after(10000, () => console.log("Networking issue. Still waiting...") )
136 | //
137 | //
138 | // ## Joining
139 | //
140 | // Joining a channel with `channel.join(topic, params)`, binds the params to
141 | // `channel.params`. Subsequent rejoins will send up the modified params for
142 | // updating authorization params, or passing up last_message_id information.
143 | // Successful joins receive an "ok" status, while unsuccessful joins
144 | // receive "error".
145 | //
146 | //
147 | // ## Pushing Messages
148 | //
149 | // From the previous example, we can see that pushing messages to the server
150 | // can be done with `channel.push(eventName, payload)` and we can optionally
151 | // receive responses from the push. Additionally, we can use
152 | // `after(millsec, callback)` to abort waiting for our `receive` hooks and
153 | // take action after some period of waiting.
154 | //
155 | //
156 | // ## Socket Hooks
157 | //
158 | // Lifecycle events of the multiplexed connection can be hooked into via
159 | // `socket.onError()` and `socket.onClose()` events, ie:
160 | //
161 | // socket.onError( () => console.log("there was an error with the connection!") )
162 | // socket.onClose( () => console.log("the connection dropped") )
163 | //
164 | //
165 | // ## Channel Hooks
166 | //
167 | // For each joined channel, you can bind to `onError` and `onClose` events
168 | // to monitor the channel lifecycle, ie:
169 | //
170 | // channel.onError( () => console.log("there was an error!") )
171 | // channel.onClose( () => console.log("the channel has gone away gracefully") )
172 | //
173 | // ### onError hooks
174 | //
175 | // `onError` hooks are invoked if the socket connection drops, or the channel
176 | // crashes on the server. In either case, a channel rejoin is attemtped
177 | // automatically in an exponential backoff manner.
178 | //
179 | // ### onClose hooks
180 | //
181 | // `onClose` hooks are invoked only in two cases. 1) the channel explicitly
182 | // closed on the server, or 2). The client explicitly closed, by calling
183 | // `channel.leave()`
184 | //
185 |
186 | var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
187 | var CHANNEL_STATES = {
188 | closed: "closed",
189 | errored: "errored",
190 | joined: "joined",
191 | joining: "joining" };
192 | var CHANNEL_EVENTS = {
193 | close: "phx_close",
194 | error: "phx_error",
195 | join: "phx_join",
196 | reply: "phx_reply",
197 | leave: "phx_leave"
198 | };
199 | var TRANSPORTS = {
200 | longpoll: "longpoll",
201 | websocket: "websocket"
202 | };
203 |
204 | var Push = (function () {
205 |
206 | // Initializes the Push
207 | //
208 | // channel - The Channelnel
209 | // event - The event, for example `"phx_join"`
210 | // payload - The payload, for example `{user_id: 123}`
211 | //
212 |
213 | function Push(channel, event, payload) {
214 | _classCallCheck(this, Push);
215 |
216 | this.channel = channel;
217 | this.event = event;
218 | this.payload = payload || {};
219 | this.receivedResp = null;
220 | this.afterHook = null;
221 | this.recHooks = [];
222 | this.sent = false;
223 | }
224 |
225 | _prototypeProperties(Push, null, {
226 | send: {
227 | value: function send() {
228 | var _this = this;
229 |
230 | var ref = this.channel.socket.makeRef();
231 | this.refEvent = this.channel.replyEventName(ref);
232 | this.receivedResp = null;
233 | this.sent = false;
234 |
235 | this.channel.on(this.refEvent, function (payload) {
236 | _this.receivedResp = payload;
237 | _this.matchReceive(payload);
238 | _this.cancelRefEvent();
239 | _this.cancelAfter();
240 | });
241 |
242 | this.startAfter();
243 | this.sent = true;
244 | this.channel.socket.push({
245 | topic: this.channel.topic,
246 | event: this.event,
247 | payload: this.payload,
248 | ref: ref
249 | });
250 | },
251 | writable: true,
252 | configurable: true
253 | },
254 | receive: {
255 | value: function receive(status, callback) {
256 | if (this.receivedResp && this.receivedResp.status === status) {
257 | callback(this.receivedResp.response);
258 | }
259 |
260 | this.recHooks.push({ status: status, callback: callback });
261 | return this;
262 | },
263 | writable: true,
264 | configurable: true
265 | },
266 | after: {
267 | value: function after(ms, callback) {
268 | if (this.afterHook) {
269 | throw "only a single after hook can be applied to a push";
270 | }
271 | var timer = null;
272 | if (this.sent) {
273 | timer = setTimeout(callback, ms);
274 | }
275 | this.afterHook = { ms: ms, callback: callback, timer: timer };
276 | return this;
277 | },
278 | writable: true,
279 | configurable: true
280 | },
281 | matchReceive: {
282 |
283 | // private
284 |
285 | value: function matchReceive(_ref) {
286 | var status = _ref.status;
287 | var response = _ref.response;
288 | var ref = _ref.ref;
289 |
290 | this.recHooks.filter(function (h) {
291 | return h.status === status;
292 | }).forEach(function (h) {
293 | return h.callback(response);
294 | });
295 | },
296 | writable: true,
297 | configurable: true
298 | },
299 | cancelRefEvent: {
300 | value: function cancelRefEvent() {
301 | this.channel.off(this.refEvent);
302 | },
303 | writable: true,
304 | configurable: true
305 | },
306 | cancelAfter: {
307 | value: function cancelAfter() {
308 | if (!this.afterHook) {
309 | return;
310 | }
311 | clearTimeout(this.afterHook.timer);
312 | this.afterHook.timer = null;
313 | },
314 | writable: true,
315 | configurable: true
316 | },
317 | startAfter: {
318 | value: function startAfter() {
319 | var _this = this;
320 |
321 | if (!this.afterHook) {
322 | return;
323 | }
324 | var callback = function () {
325 | _this.cancelRefEvent();
326 | _this.afterHook.callback();
327 | };
328 | this.afterHook.timer = setTimeout(callback, this.afterHook.ms);
329 | },
330 | writable: true,
331 | configurable: true
332 | }
333 | });
334 |
335 | return Push;
336 | })();
337 |
338 | var Channel = exports.Channel = (function () {
339 | function Channel(topic, params, socket) {
340 | var _this = this;
341 |
342 | _classCallCheck(this, Channel);
343 |
344 | this.state = CHANNEL_STATES.closed;
345 | this.topic = topic;
346 | this.params = params || {};
347 | this.socket = socket;
348 | this.bindings = [];
349 | this.joinedOnce = false;
350 | this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params);
351 | this.pushBuffer = [];
352 | this.rejoinTimer = new Timer(function () {
353 | return _this.rejoinUntilConnected();
354 | }, this.socket.reconnectAfterMs);
355 | this.joinPush.receive("ok", function () {
356 | _this.state = CHANNEL_STATES.joined;
357 | _this.rejoinTimer.reset();
358 | });
359 | this.onClose(function () {
360 | _this.socket.log("channel", "close " + _this.topic);
361 | _this.state = CHANNEL_STATES.closed;
362 | _this.socket.remove(_this);
363 | });
364 | this.onError(function (reason) {
365 | _this.socket.log("channel", "error " + _this.topic, reason);
366 | _this.state = CHANNEL_STATES.errored;
367 | _this.rejoinTimer.setTimeout();
368 | });
369 | this.on(CHANNEL_EVENTS.reply, function (payload, ref) {
370 | _this.trigger(_this.replyEventName(ref), payload);
371 | });
372 | }
373 |
374 | _prototypeProperties(Channel, null, {
375 | rejoinUntilConnected: {
376 | value: function rejoinUntilConnected() {
377 | this.rejoinTimer.setTimeout();
378 | if (this.socket.isConnected()) {
379 | this.rejoin();
380 | }
381 | },
382 | writable: true,
383 | configurable: true
384 | },
385 | join: {
386 | value: function join() {
387 | if (this.joinedOnce) {
388 | throw "tried to join multiple times. 'join' can only be called a single time per channel instance";
389 | } else {
390 | this.joinedOnce = true;
391 | }
392 | this.sendJoin();
393 | return this.joinPush;
394 | },
395 | writable: true,
396 | configurable: true
397 | },
398 | onClose: {
399 | value: function onClose(callback) {
400 | this.on(CHANNEL_EVENTS.close, callback);
401 | },
402 | writable: true,
403 | configurable: true
404 | },
405 | onError: {
406 | value: function onError(callback) {
407 | this.on(CHANNEL_EVENTS.error, function (reason) {
408 | return callback(reason);
409 | });
410 | },
411 | writable: true,
412 | configurable: true
413 | },
414 | on: {
415 | value: function on(event, callback) {
416 | this.bindings.push({ event: event, callback: callback });
417 | },
418 | writable: true,
419 | configurable: true
420 | },
421 | off: {
422 | value: function off(event) {
423 | this.bindings = this.bindings.filter(function (bind) {
424 | return bind.event !== event;
425 | });
426 | },
427 | writable: true,
428 | configurable: true
429 | },
430 | canPush: {
431 | value: function canPush() {
432 | return this.socket.isConnected() && this.state === CHANNEL_STATES.joined;
433 | },
434 | writable: true,
435 | configurable: true
436 | },
437 | push: {
438 | value: function push(event, payload) {
439 | if (!this.joinedOnce) {
440 | throw "tried to push '" + event + "' to '" + this.topic + "' before joining. Use channel.join() before pushing events";
441 | }
442 | var pushEvent = new Push(this, event, payload);
443 | if (this.canPush()) {
444 | pushEvent.send();
445 | } else {
446 | this.pushBuffer.push(pushEvent);
447 | }
448 |
449 | return pushEvent;
450 | },
451 | writable: true,
452 | configurable: true
453 | },
454 | leave: {
455 |
456 | // Leaves the channel
457 | //
458 | // Unsubscribes from server events, and
459 | // instructs channel to terminate on server
460 | //
461 | // Triggers onClose() hooks
462 | //
463 | // To receive leave acknowledgements, use the a `receive`
464 | // hook to bind to the server ack, ie:
465 | //
466 | // channel.leave().receive("ok", () => alert("left!") )
467 | //
468 |
469 | value: function leave() {
470 | var _this = this;
471 |
472 | return this.push(CHANNEL_EVENTS.leave).receive("ok", function () {
473 | _this.socket.log("channel", "leave " + _this.topic);
474 | _this.trigger(CHANNEL_EVENTS.close, "leave");
475 | });
476 | },
477 | writable: true,
478 | configurable: true
479 | },
480 | onMessage: {
481 |
482 | // Overridable message hook
483 | //
484 | // Receives all events for specialized message handling
485 |
486 | value: function onMessage(event, payload, ref) {},
487 | writable: true,
488 | configurable: true
489 | },
490 | isMember: {
491 |
492 | // private
493 |
494 | value: function isMember(topic) {
495 | return this.topic === topic;
496 | },
497 | writable: true,
498 | configurable: true
499 | },
500 | sendJoin: {
501 | value: function sendJoin() {
502 | this.state = CHANNEL_STATES.joining;
503 | this.joinPush.send();
504 | },
505 | writable: true,
506 | configurable: true
507 | },
508 | rejoin: {
509 | value: function rejoin() {
510 | this.sendJoin();
511 | this.pushBuffer.forEach(function (pushEvent) {
512 | return pushEvent.send();
513 | });
514 | this.pushBuffer = [];
515 | },
516 | writable: true,
517 | configurable: true
518 | },
519 | trigger: {
520 | value: function trigger(triggerEvent, payload, ref) {
521 | this.onMessage(triggerEvent, payload, ref);
522 | this.bindings.filter(function (bind) {
523 | return bind.event === triggerEvent;
524 | }).map(function (bind) {
525 | return bind.callback(payload, ref);
526 | });
527 | },
528 | writable: true,
529 | configurable: true
530 | },
531 | replyEventName: {
532 | value: function replyEventName(ref) {
533 | return "chan_reply_" + ref;
534 | },
535 | writable: true,
536 | configurable: true
537 | }
538 | });
539 |
540 | return Channel;
541 | })();
542 |
543 | var Socket = exports.Socket = (function () {
544 |
545 | // Initializes the Socket
546 | //
547 | // endPoint - The string WebSocket endpoint, ie, "ws://example.com/ws",
548 | // "wss://example.com"
549 | // "/ws" (inherited host & protocol)
550 | // opts - Optional configuration
551 | // transport - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.
552 | // Defaults to WebSocket with automatic LongPoll fallback.
553 | // heartbeatIntervalMs - The millisec interval to send a heartbeat message
554 | // reconnectAfterMs - The optional function that returns the millsec
555 | // reconnect interval. Defaults to stepped backoff of:
556 | //
557 | // function(tries){
558 | // return [1000, 5000, 10000][tries - 1] || 10000
559 | // }
560 | //
561 | // logger - The optional function for specialized logging, ie:
562 | // `logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }
563 | //
564 | // longpollerTimeout - The maximum timeout of a long poll AJAX request.
565 | // Defaults to 20s (double the server long poll timer).
566 | //
567 | // For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)
568 | //
569 |
570 | function Socket(endPoint) {
571 | var _this = this;
572 |
573 | var opts = arguments[1] === undefined ? {} : arguments[1];
574 |
575 | _classCallCheck(this, Socket);
576 |
577 | this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };
578 | this.channels = [];
579 | this.sendBuffer = [];
580 | this.ref = 0;
581 | this.transport = opts.transport || window.WebSocket || LongPoll;
582 | this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000;
583 | this.reconnectAfterMs = opts.reconnectAfterMs || function (tries) {
584 | return [1000, 5000, 10000][tries - 1] || 10000;
585 | };
586 | this.logger = opts.logger || function () {}; // noop
587 | this.longpollerTimeout = opts.longpollerTimeout || 20000;
588 | this.params = {};
589 | this.reconnectTimer = new Timer(function () {
590 | return _this.connect(_this.params);
591 | }, this.reconnectAfterMs);
592 | this.endPoint = "" + endPoint + "/" + TRANSPORTS.websocket;
593 | }
594 |
595 | _prototypeProperties(Socket, null, {
596 | protocol: {
597 | value: function protocol() {
598 | return location.protocol.match(/^https/) ? "wss" : "ws";
599 | },
600 | writable: true,
601 | configurable: true
602 | },
603 | endPointURL: {
604 | value: function endPointURL() {
605 | var uri = Ajax.appendParams(this.endPoint, this.params);
606 | if (uri.charAt(0) !== "/") {
607 | return uri;
608 | }
609 | if (uri.charAt(1) === "/") {
610 | return "" + this.protocol() + ":" + uri;
611 | }
612 |
613 | return "" + this.protocol() + "://" + location.host + "" + uri;
614 | },
615 | writable: true,
616 | configurable: true
617 | },
618 | disconnect: {
619 | value: function disconnect(callback, code, reason) {
620 | if (this.conn) {
621 | this.conn.onclose = function () {}; // noop
622 | if (code) {
623 | this.conn.close(code, reason || "");
624 | } else {
625 | this.conn.close();
626 | }
627 | this.conn = null;
628 | }
629 | callback && callback();
630 | },
631 | writable: true,
632 | configurable: true
633 | },
634 | connect: {
635 |
636 | // params - The params to send when connecting, for example `{user_id: userToken}`
637 |
638 | value: function connect() {
639 | var _this = this;
640 |
641 | var params = arguments[0] === undefined ? {} : arguments[0];
642 | this.params = params;
643 | this.disconnect(function () {
644 | _this.conn = new _this.transport(_this.endPointURL());
645 | _this.conn.timeout = _this.longpollerTimeout;
646 | _this.conn.onopen = function () {
647 | return _this.onConnOpen();
648 | };
649 | _this.conn.onerror = function (error) {
650 | return _this.onConnError(error);
651 | };
652 | _this.conn.onmessage = function (event) {
653 | return _this.onConnMessage(event);
654 | };
655 | _this.conn.onclose = function (event) {
656 | return _this.onConnClose(event);
657 | };
658 | });
659 | },
660 | writable: true,
661 | configurable: true
662 | },
663 | log: {
664 |
665 | // Logs the message. Override `this.logger` for specialized logging. noops by default
666 |
667 | value: function log(kind, msg, data) {
668 | this.logger(kind, msg, data);
669 | },
670 | writable: true,
671 | configurable: true
672 | },
673 | onOpen: {
674 |
675 | // Registers callbacks for connection state change events
676 | //
677 | // Examples
678 | //
679 | // socket.onError(function(error){ alert("An error occurred") })
680 | //
681 |
682 | value: function onOpen(callback) {
683 | this.stateChangeCallbacks.open.push(callback);
684 | },
685 | writable: true,
686 | configurable: true
687 | },
688 | onClose: {
689 | value: function onClose(callback) {
690 | this.stateChangeCallbacks.close.push(callback);
691 | },
692 | writable: true,
693 | configurable: true
694 | },
695 | onError: {
696 | value: function onError(callback) {
697 | this.stateChangeCallbacks.error.push(callback);
698 | },
699 | writable: true,
700 | configurable: true
701 | },
702 | onMessage: {
703 | value: function onMessage(callback) {
704 | this.stateChangeCallbacks.message.push(callback);
705 | },
706 | writable: true,
707 | configurable: true
708 | },
709 | onConnOpen: {
710 | value: function onConnOpen() {
711 | var _this = this;
712 |
713 | this.log("transport", "connected to " + this.endPointURL(), this.transport.prototype);
714 | this.flushSendBuffer();
715 | this.reconnectTimer.reset();
716 | if (!this.conn.skipHeartbeat) {
717 | clearInterval(this.heartbeatTimer);
718 | this.heartbeatTimer = setInterval(function () {
719 | return _this.sendHeartbeat();
720 | }, this.heartbeatIntervalMs);
721 | }
722 | this.stateChangeCallbacks.open.forEach(function (callback) {
723 | return callback();
724 | });
725 | },
726 | writable: true,
727 | configurable: true
728 | },
729 | onConnClose: {
730 | value: function onConnClose(event) {
731 | this.log("transport", "close", event);
732 | this.triggerChanError();
733 | clearInterval(this.heartbeatTimer);
734 | this.reconnectTimer.setTimeout();
735 | this.stateChangeCallbacks.close.forEach(function (callback) {
736 | return callback(event);
737 | });
738 | },
739 | writable: true,
740 | configurable: true
741 | },
742 | onConnError: {
743 | value: function onConnError(error) {
744 | this.log("transport", error);
745 | this.triggerChanError();
746 | this.stateChangeCallbacks.error.forEach(function (callback) {
747 | return callback(error);
748 | });
749 | },
750 | writable: true,
751 | configurable: true
752 | },
753 | triggerChanError: {
754 | value: function triggerChanError() {
755 | this.channels.forEach(function (channel) {
756 | return channel.trigger(CHANNEL_EVENTS.error);
757 | });
758 | },
759 | writable: true,
760 | configurable: true
761 | },
762 | connectionState: {
763 | value: function connectionState() {
764 | switch (this.conn && this.conn.readyState) {
765 | case SOCKET_STATES.connecting:
766 | return "connecting";
767 | case SOCKET_STATES.open:
768 | return "open";
769 | case SOCKET_STATES.closing:
770 | return "closing";
771 | default:
772 | return "closed";
773 | }
774 | },
775 | writable: true,
776 | configurable: true
777 | },
778 | isConnected: {
779 | value: function isConnected() {
780 | return this.connectionState() === "open";
781 | },
782 | writable: true,
783 | configurable: true
784 | },
785 | remove: {
786 | value: function remove(channel) {
787 | this.channels = this.channels.filter(function (c) {
788 | return !c.isMember(channel.topic);
789 | });
790 | },
791 | writable: true,
792 | configurable: true
793 | },
794 | channel: {
795 | value: function channel(topic) {
796 | var chanParams = arguments[1] === undefined ? {} : arguments[1];
797 |
798 | var channel = new Channel(topic, chanParams, this);
799 | this.channels.push(channel);
800 | return channel;
801 | },
802 | writable: true,
803 | configurable: true
804 | },
805 | push: {
806 | value: function push(data) {
807 | var _this = this;
808 |
809 | var topic = data.topic;
810 | var event = data.event;
811 | var payload = data.payload;
812 | var ref = data.ref;
813 |
814 | var callback = function () {
815 | return _this.conn.send(JSON.stringify(data));
816 | };
817 | this.log("push", "" + topic + " " + event + " (" + ref + ")", payload);
818 | if (this.isConnected()) {
819 | callback();
820 | } else {
821 | this.sendBuffer.push(callback);
822 | }
823 | },
824 | writable: true,
825 | configurable: true
826 | },
827 | makeRef: {
828 |
829 | // Return the next message ref, accounting for overflows
830 |
831 | value: function makeRef() {
832 | var newRef = this.ref + 1;
833 | if (newRef === this.ref) {
834 | this.ref = 0;
835 | } else {
836 | this.ref = newRef;
837 | }
838 |
839 | return this.ref.toString();
840 | },
841 | writable: true,
842 | configurable: true
843 | },
844 | sendHeartbeat: {
845 | value: function sendHeartbeat() {
846 | this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.makeRef() });
847 | },
848 | writable: true,
849 | configurable: true
850 | },
851 | flushSendBuffer: {
852 | value: function flushSendBuffer() {
853 | if (this.isConnected() && this.sendBuffer.length > 0) {
854 | this.sendBuffer.forEach(function (callback) {
855 | return callback();
856 | });
857 | this.sendBuffer = [];
858 | }
859 | },
860 | writable: true,
861 | configurable: true
862 | },
863 | onConnMessage: {
864 | value: function onConnMessage(rawMessage) {
865 | var msg = JSON.parse(rawMessage.data);
866 | var topic = msg.topic;
867 | var event = msg.event;
868 | var payload = msg.payload;
869 | var ref = msg.ref;
870 |
871 | this.log("receive", "" + (payload.status || "") + " " + topic + " " + event + " " + (ref && "(" + ref + ")" || ""), payload);
872 | this.channels.filter(function (channel) {
873 | return channel.isMember(topic);
874 | }).forEach(function (channel) {
875 | return channel.trigger(event, payload, ref);
876 | });
877 | this.stateChangeCallbacks.message.forEach(function (callback) {
878 | return callback(msg);
879 | });
880 | },
881 | writable: true,
882 | configurable: true
883 | }
884 | });
885 |
886 | return Socket;
887 | })();
888 |
889 | var LongPoll = exports.LongPoll = (function () {
890 | function LongPoll(endPoint) {
891 | _classCallCheck(this, LongPoll);
892 |
893 | this.endPoint = null;
894 | this.token = null;
895 | this.skipHeartbeat = true;
896 | this.onopen = function () {}; // noop
897 | this.onerror = function () {}; // noop
898 | this.onmessage = function () {}; // noop
899 | this.onclose = function () {}; // noop
900 | this.pollEndpoint = this.normalizeEndpoint(endPoint);
901 | this.readyState = SOCKET_STATES.connecting;
902 |
903 | this.poll();
904 | }
905 |
906 | _prototypeProperties(LongPoll, null, {
907 | normalizeEndpoint: {
908 | value: function normalizeEndpoint(endPoint) {
909 | return endPoint.replace("ws://", "http://").replace("wss://", "https://").replace(new RegExp("(.*)/" + TRANSPORTS.websocket), "$1/" + TRANSPORTS.longpoll);
910 | },
911 | writable: true,
912 | configurable: true
913 | },
914 | endpointURL: {
915 | value: function endpointURL() {
916 | return Ajax.appendParams(this.pollEndpoint, {
917 | token: this.token,
918 | format: "json"
919 | });
920 | },
921 | writable: true,
922 | configurable: true
923 | },
924 | closeAndRetry: {
925 | value: function closeAndRetry() {
926 | this.close();
927 | this.readyState = SOCKET_STATES.connecting;
928 | },
929 | writable: true,
930 | configurable: true
931 | },
932 | ontimeout: {
933 | value: function ontimeout() {
934 | this.onerror("timeout");
935 | this.closeAndRetry();
936 | },
937 | writable: true,
938 | configurable: true
939 | },
940 | poll: {
941 | value: function poll() {
942 | var _this = this;
943 |
944 | if (!(this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting)) {
945 | return;
946 | }
947 |
948 | Ajax.request("GET", this.endpointURL(), "application/json", null, this.timeout, this.ontimeout.bind(this), function (resp) {
949 | if (resp) {
950 | var status = resp.status;
951 | var token = resp.token;
952 | var messages = resp.messages;
953 |
954 | _this.token = token;
955 | } else {
956 | var status = 0;
957 | }
958 |
959 | switch (status) {
960 | case 200:
961 | messages.forEach(function (msg) {
962 | return _this.onmessage({ data: JSON.stringify(msg) });
963 | });
964 | _this.poll();
965 | break;
966 | case 204:
967 | _this.poll();
968 | break;
969 | case 410:
970 | _this.readyState = SOCKET_STATES.open;
971 | _this.onopen();
972 | _this.poll();
973 | break;
974 | case 0:
975 | case 500:
976 | _this.onerror();
977 | _this.closeAndRetry();
978 | break;
979 | default:
980 | throw "unhandled poll status " + status;
981 | }
982 | });
983 | },
984 | writable: true,
985 | configurable: true
986 | },
987 | send: {
988 | value: function send(body) {
989 | var _this = this;
990 |
991 | Ajax.request("POST", this.endpointURL(), "application/json", body, this.timeout, this.onerror.bind(this, "timeout"), function (resp) {
992 | if (!resp || resp.status !== 200) {
993 | _this.onerror(status);
994 | _this.closeAndRetry();
995 | }
996 | });
997 | },
998 | writable: true,
999 | configurable: true
1000 | },
1001 | close: {
1002 | value: function close(code, reason) {
1003 | this.readyState = SOCKET_STATES.closed;
1004 | this.onclose();
1005 | },
1006 | writable: true,
1007 | configurable: true
1008 | }
1009 | });
1010 |
1011 | return LongPoll;
1012 | })();
1013 |
1014 | var Ajax = exports.Ajax = (function () {
1015 | function Ajax() {
1016 | _classCallCheck(this, Ajax);
1017 | }
1018 |
1019 | _prototypeProperties(Ajax, {
1020 | request: {
1021 | value: function request(method, endPoint, accept, body, timeout, ontimeout, callback) {
1022 | if (window.XDomainRequest) {
1023 | var req = new XDomainRequest(); // IE8, IE9
1024 | this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
1025 | } else {
1026 | var req = window.XMLHttpRequest ? new XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
1027 | new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5
1028 | this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback);
1029 | }
1030 | },
1031 | writable: true,
1032 | configurable: true
1033 | },
1034 | xdomainRequest: {
1035 | value: function xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {
1036 | var _this = this;
1037 |
1038 | req.timeout = timeout;
1039 | req.open(method, endPoint);
1040 | req.onload = function () {
1041 | var response = _this.parseJSON(req.responseText);
1042 | callback && callback(response);
1043 | };
1044 | if (ontimeout) {
1045 | req.ontimeout = ontimeout;
1046 | }
1047 |
1048 | // Work around bug in IE9 that requires an attached onprogress handler
1049 | req.onprogress = function () {};
1050 |
1051 | req.send(body);
1052 | },
1053 | writable: true,
1054 | configurable: true
1055 | },
1056 | xhrRequest: {
1057 | value: function xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) {
1058 | var _this = this;
1059 |
1060 | req.timeout = timeout;
1061 | req.open(method, endPoint, true);
1062 | req.setRequestHeader("Content-Type", accept);
1063 | req.onerror = function () {
1064 | callback && callback(null);
1065 | };
1066 | req.onreadystatechange = function () {
1067 | if (req.readyState === _this.states.complete && callback) {
1068 | var response = _this.parseJSON(req.responseText);
1069 | callback(response);
1070 | }
1071 | };
1072 | if (ontimeout) {
1073 | req.ontimeout = ontimeout;
1074 | }
1075 |
1076 | req.send(body);
1077 | },
1078 | writable: true,
1079 | configurable: true
1080 | },
1081 | parseJSON: {
1082 | value: function parseJSON(resp) {
1083 | return resp && resp !== "" ? JSON.parse(resp) : null;
1084 | },
1085 | writable: true,
1086 | configurable: true
1087 | },
1088 | serialize: {
1089 | value: function serialize(obj, parentKey) {
1090 | var queryStr = [];
1091 | for (var key in obj) {
1092 | if (!obj.hasOwnProperty(key)) {
1093 | continue;
1094 | }
1095 | var paramKey = parentKey ? "" + parentKey + "[" + key + "]" : key;
1096 | var paramVal = obj[key];
1097 | if (typeof paramVal === "object") {
1098 | queryStr.push(this.serialize(paramVal, paramKey));
1099 | } else {
1100 | queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal));
1101 | }
1102 | }
1103 | return queryStr.join("&");
1104 | },
1105 | writable: true,
1106 | configurable: true
1107 | },
1108 | appendParams: {
1109 | value: function appendParams(url, params) {
1110 | if (Object.keys(params).length === 0) {
1111 | return url;
1112 | }
1113 |
1114 | var prefix = url.match(/\?/) ? "&" : "?";
1115 | return "" + url + "" + prefix + "" + this.serialize(params);
1116 | },
1117 | writable: true,
1118 | configurable: true
1119 | }
1120 | });
1121 |
1122 | return Ajax;
1123 | })();
1124 |
1125 | Ajax.states = { complete: 4 };
1126 |
1127 | // Creates a timer that accepts a `timerCalc` function to perform
1128 | // calculated timeout retries, such as exponential backoff.
1129 | //
1130 | // ## Examples
1131 | //
1132 | // let reconnectTimer = new Timer(() => this.connect(), function(tries){
1133 | // return [1000, 5000, 10000][tries - 1] || 10000
1134 | // })
1135 | // reconnectTimer.setTimeout() // fires after 1000
1136 | // reconnectTimer.setTimeout() // fires after 5000
1137 | // reconnectTimer.reset()
1138 | // reconnectTimer.setTimeout() // fires after 1000
1139 | //
1140 |
1141 | var Timer = (function () {
1142 | function Timer(callback, timerCalc) {
1143 | _classCallCheck(this, Timer);
1144 |
1145 | this.callback = callback;
1146 | this.timerCalc = timerCalc;
1147 | this.timer = null;
1148 | this.tries = 0;
1149 | }
1150 |
1151 | _prototypeProperties(Timer, null, {
1152 | reset: {
1153 | value: function reset() {
1154 | this.tries = 0;
1155 | clearTimeout(this.timer);
1156 | },
1157 | writable: true,
1158 | configurable: true
1159 | },
1160 | setTimeout: {
1161 |
1162 | // Cancels any previous setTimeout and schedules callback
1163 |
1164 | value: (function (_setTimeout) {
1165 | var _setTimeoutWrapper = function setTimeout() {
1166 | return _setTimeout.apply(this, arguments);
1167 | };
1168 |
1169 | _setTimeoutWrapper.toString = function () {
1170 | return _setTimeout.toString();
1171 | };
1172 |
1173 | return _setTimeoutWrapper;
1174 | })(function () {
1175 | var _this = this;
1176 |
1177 | clearTimeout(this.timer);
1178 |
1179 | this.timer = setTimeout(function () {
1180 | _this.tries = _this.tries + 1;
1181 | _this.callback();
1182 | }, this.timerCalc(this.tries + 1));
1183 | }),
1184 | writable: true,
1185 | configurable: true
1186 | }
1187 | });
1188 |
1189 | return Timer;
1190 | })();
1191 |
1192 | Object.defineProperty(exports, "__esModule", {
1193 | value: true
1194 | });
1195 | }});
1196 | if(typeof(window) === 'object' && !window.Phoenix){ window.Phoenix = require('phoenix') };
--------------------------------------------------------------------------------
/ChatServer/web/templates/layout/app.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Hello Phoenix!
11 | ">
12 |
13 |
14 |
15 |
16 |
35 |
36 |
37 |
38 | <%= render @view_module, @view_template, assigns %>
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/ChatServer/web/templates/page/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
--------------------------------------------------------------------------------
/ChatServer/web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule Chat.ErrorView do
2 | use Chat.Web, :view
3 |
4 | def render("404.html", _assigns) do
5 | "Page not found - 404"
6 | end
7 |
8 | def render("500.html", _assigns) do
9 | "Server internal error - 500"
10 | end
11 |
12 | # In case no render clause matches or no
13 | # template is found, let's render it as 500
14 | def template_not_found(_, assigns) do
15 | render "500.html", assigns
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/ChatServer/web/views/layout_view.ex:
--------------------------------------------------------------------------------
1 | defmodule Chat.LayoutView do
2 | use Chat.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/ChatServer/web/views/page_view.ex:
--------------------------------------------------------------------------------
1 | defmodule Chat.PageView do
2 | use Chat.Web, :view
3 | end
4 |
--------------------------------------------------------------------------------
/ChatServer/web/web.ex:
--------------------------------------------------------------------------------
1 | defmodule Chat.Web do
2 | @moduledoc """
3 | A module that keeps using definitions for controllers,
4 | views and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use Chat.Web, :controller
9 | use Chat.Web, :view
10 |
11 | Keep the definitions in this module short and clean,
12 | mostly focused on imports, uses and aliases.
13 | """
14 |
15 | def view do
16 | quote do
17 | use Phoenix.View, root: "web/templates"
18 |
19 | # Import URL helpers from the router
20 | import Chat.Router.Helpers
21 |
22 | # Import all HTML functions (forms, tags, etc)
23 | use Phoenix.HTML
24 | end
25 | end
26 |
27 | def controller do
28 | quote do
29 | use Phoenix.Controller
30 |
31 | # Alias the data repository as a convenience
32 | alias Chat.Repo
33 |
34 | # Import URL helpers from the router
35 | import Chat.Router.Helpers
36 | end
37 | end
38 |
39 | def model do
40 | quote do
41 | use Ecto.Model
42 | end
43 | end
44 |
45 | @doc """
46 | When used, dispatch to the appropriate controller/view/etc.
47 | """
48 | defmacro __using__(which) when is_atom(which) do
49 | apply(__MODULE__, which, [])
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Steve Kellock
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # What is this Foul Sorcery?
2 |
3 | Here's an example of using React Native to talk to a Phoenix server with WebSockets.
4 |
5 | 
6 |
7 | # The Blog Post
8 |
9 | For more background and some more in-depth commentary, check out my blog post that
10 | goes with this repo.
11 |
12 | https://medium.com/@skellock/prototyping-a-chat-app-with-react-native-and-phoenix-5e65677a8217
13 |
14 |
15 | # About The Client
16 |
17 | Since this is just a sample, I'm going to pretend errors and timeouts don't exist.
18 |
19 | In the code:
20 |
21 | * `Phoenix.js` - the official JS client that ships with Phoenix framework.
22 | * `Chat.js` - a silly wrapper around the Phoenix sockets and channel interfaces.
23 | * `Root.jsx` - the user interface
24 |
25 | The UI revolves around a hilariously fantastic 3rd party component called
26 | Gifted Messenger:
27 |
28 | https://github.com/FaridSafi/react-native-gifted-messenger
29 |
30 |
31 | # About The Server
32 |
33 | The server is Chris McCord's example. The only change I made was to turn off the
34 | server-generated PING message every 5 seconds.
35 |
36 | https://github.com/chrismccord/phoenix_chat_example
37 |
38 | I just included it here for convenience. I ghetto-forked it as of
39 | `02bbbc8a295542146aef4e347dcbdc5fd0aadd69` on Feb 13, 2016. He does a great job of
40 | keeping it up to date, and you should use his for your own adventures.
41 |
42 |
43 | # Running The Server
44 |
45 | Make sure you've installed Elixir 1.2.2+.
46 |
47 | * `cd ChatServer`
48 | * `mix deps get`
49 | * `npm install` (recommended: for a web version of the client)
50 | * `grab a coffee`
51 | * `mix phoenix.server`
52 |
53 | After a quick 1-time compile, your server is now up & running. If you installed
54 | the web client then browser to http://localhost:4000
55 |
56 |
57 | # Running The Client
58 |
59 | Make sure you have React Native 0.19+ installed on your ride.
60 |
61 | * `cd ChatClient`
62 | * `npm install`
63 | * `grab a coffee`
64 | * `react-native run-ios` or `react-native run-android`
65 |
66 | If you're running android, you might need to reverse map some ports to get to the chat server
67 | by running
68 |
69 | * `$ANDROID_HOME/platform-tools/adb reverse tcp:4000 tcp:4000`
70 |
71 | \o/
72 |
--------------------------------------------------------------------------------
/images/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skellock/phoenix-react-native-mashup/3c22b7ae060c0c2a5b18f459d6a65b7c6defbbe9/images/screenshot.png
--------------------------------------------------------------------------------