1 |
|
2 | unless Function::bind
|
3 | Function::bind = (oThis) ->
|
4 |
|
5 |
|
6 |
|
7 | throw new TypeError('Function.prototype.bind - what is trying to be bound
|
8 | is not callable') if typeof this isnt 'function'
|
9 | aArgs = Array::slice.call(arguments, 1)
|
10 | fToBind = this
|
11 | fNOP = -> null
|
12 |
|
13 | fBound = ->
|
14 | fToBind.apply (if this instanceof fNOP and oThis then this else oThis),
|
15 | aArgs.concat(Array::slice.call(arguments))
|
16 |
|
17 | fNOP.prototype = this.prototype
|
18 | fBound:: = new fNOP()
|
19 | fBound
|
20 |
|
21 | deferredFactory = ->
|
22 | resolve = null
|
23 | reject = null
|
24 | promise = new Promise (_resolve, _reject) ->
|
25 | resolve = _resolve
|
26 | reject = _reject
|
27 | promise.resolve = resolve
|
28 | promise.reject = reject
|
29 |
|
30 | return promise
|
31 |
|
32 | should = require('clay-chai').should()
|
33 | Promise = require 'bluebird'
|
34 | Promiz = require 'promiz'
|
35 | _ = require 'lodash'
|
36 | rewire = require 'rewire'
|
37 |
|
38 | packageConfig = require '../package.json'
|
39 | ClayRoot = rewire 'index'
|
40 | Clay = ClayRoot.__get__ 'Clay'
|
41 |
|
42 | TRUSTED_DOMAIN = process.env.TRUSTED_DOMAIN or 'clay.io'
|
43 |
|
44 | postRoutes = {}
|
45 |
|
46 | ClayRoot.__set__ 'window.parent.postMessage', (messageString, targetOrigin) ->
|
47 | targetOrigin.should.be '*'
|
48 | message = JSON.parse messageString
|
49 | _.isNumber(message.id).should.be true
|
50 | message._portal.should.be true
|
51 | message.jsonrpc.should.be '2.0'
|
52 |
|
53 | postRoutes[message.method].should.exist
|
54 |
|
55 | if postRoutes[message.method].timeout
|
56 | return
|
57 |
|
58 | result = postRoutes[message.method].data
|
59 |
|
60 | if _.isFunction result
|
61 | result = result(message)
|
62 |
|
63 | e = document.createEvent 'Event'
|
64 | e.initEvent 'message', true, true
|
65 |
|
66 | e.origin = postRoutes[message.method].origin or ('http://' + TRUSTED_DOMAIN)
|
67 | e.data = JSON.stringify _.defaults(
|
68 | {id: message.id, _portal: true}
|
69 | result
|
70 | )
|
71 |
|
72 | window.dispatchEvent e
|
73 |
|
74 | routePost = (method, {origin, data, timeout}) ->
|
75 | postRoutes[method] = {origin, data, timeout}
|
76 |
|
77 | routePost 'ping', {data: result: 'pong'}
|
78 | routePost 'auth.getStatus',
|
79 | data:
|
80 | result: {accessToken: 1}
|
81 |
|
82 | describe 'sdk', ->
|
83 | @timeout 200
|
84 |
|
85 | describe 'version', ->
|
86 | it 'has version', (done) ->
|
87 | Clay 'version', (err, v) ->
|
88 | v.should.be 'v' + packageConfig.version
|
89 |
|
90 | done(err)
|
91 |
|
92 | describe 'signature', ->
|
93 | it 'errors on invalid root method', (done) ->
|
94 | Clay 'something', (err) ->
|
95 | err.message.should.be 'Method not found'
|
96 | done()
|
97 |
|
98 | describe 'init()', ->
|
99 | describe 'status', ->
|
100 | it 'returns access token', (done) ->
|
101 | Clay 'init', {gameId: '1'}, (err, status) ->
|
102 | status.accessToken.should.be.a.Number
|
103 | done(err)
|
104 |
|
105 | describe 'config', ->
|
106 | it 'sets gameId', (done) ->
|
107 | cfg = deferredFactory()
|
108 | ClayRoot.__set__ 'config', cfg
|
109 |
|
110 | routePost 'auth.getStatus',
|
111 | data:
|
112 | result: {accessToken: 1}
|
113 |
|
114 | Clay 'init', {gameId: '2'}
|
115 |
|
116 | cfg.then (config) ->
|
117 | config.gameId.should.be '2'
|
118 | .then (x) -> done()
|
119 | .catch done
|
120 |
|
121 | describe 'signature', ->
|
122 | it 'gameId type checks undefined', (done) ->
|
123 | Clay 'init', {}, (err) ->
|
124 | err.message.should.be 'Missing or invalid gameId'
|
125 | done()
|
126 |
|
127 | it 'gameId type checks number', (done) ->
|
128 | Clay 'init', {gameId: 1}, (err) ->
|
129 | err.message.should.be 'Missing or invalid gameId'
|
130 | done()
|
131 |
|
132 | describe 'domain verification when framed', ->
|
133 | it 'dissallows invalid domains', (done) ->
|
134 |
|
135 | routePost 'auth.getStatus', origin: 'http://evil.io', data: result: '1'
|
136 |
|
137 | Clay 'init', {gameId: '1'}, (err) ->
|
138 | err.should.exist
|
139 | done()
|
140 |
|
141 | it 'allows invalid domains in debug mode', (done) ->
|
142 | routePost 'auth.getStatus', origin: 'http://evil.io', data: result: '1'
|
143 | Clay 'init', {gameId: '1', debug: true}, done
|
144 |
|
145 | describe 'client()', ->
|
146 | describe 'state errors', ->
|
147 | before ->
|
148 | ClayRoot.__set__ 'initHasBeenCalled', false
|
149 |
|
150 | it 'errors if init hasn\'t been called', (done) ->
|
151 | Clay 'client.kik.send', (err) ->
|
152 | err.message.should.be 'Must call Clay(\'init\') first'
|
153 | done()
|
154 |
|
155 | describe 'signature', ->
|
156 | before ->
|
157 | ClayRoot.__set__ 'initHasBeenCalled', true
|
158 | ClayRoot.__set__ 'config', Promiz.resolve {gameId: '1'}
|
159 |
|
160 | it 'errors if method missing', (done) ->
|
161 | Clay 'client', (err) ->
|
162 | err.message.should.be 'Missing or invalid method'
|
163 | done()
|
164 |
|
165 | it 'succeeds if params is an object', (done) ->
|
166 | routePost 'share.any',
|
167 | origin: 'http://clay.io'
|
168 | data:
|
169 | result: {test: true}
|
170 |
|
171 | Clay 'client.share.any', {text: 'test'}, (err) ->
|
172 | done(err)
|
173 |
|
174 | it 'errors if params is a string', (done) ->
|
175 | Clay 'client.kik.send', 'param', (err) ->
|
176 | err.message.should.be 'Params must be an array'
|
177 | done()
|
178 |
|
179 | it 'errors if params is a number', (done) ->
|
180 | Clay 'client.kik.send', 123, (err) ->
|
181 | err.message.should.be 'Params must be an array'
|
182 | done()
|
183 |
|
184 | describe 'Posting', ->
|
185 | before ->
|
186 | ClayRoot.__set__ 'initHasBeenCalled', true
|
187 | ClayRoot.__set__ 'config', Promiz.resolve {gameId: '1'}
|
188 |
|
189 | it 'posts to parent frame', (done) ->
|
190 | routePost 'kik.getUser',
|
191 | origin: 'http://clay.io'
|
192 | data:
|
193 | result: {test: true}
|
194 |
|
195 | Clay 'client.kik.getUser', (err, user) ->
|
196 | should.not.exist err
|
197 | user.test.should.be true
|
198 | done()
|
199 |
|
200 | it 'recieved errors', (done) ->
|
201 | routePost 'kik.getUser',
|
202 | origin: 'http://clay.io'
|
203 | data:
|
204 | error: {message: 'abc'}
|
205 |
|
206 | Clay 'client.kik.getUser', (err) ->
|
207 | err.message.should.be 'abc'
|
208 | done()
|
209 |
|
210 | it 'times out', (done) ->
|
211 | portal = ClayRoot.__get__ 'portal'
|
212 | routePost 'infinite.loop', timeout: true
|
213 |
|
214 | portal.down()
|
215 | portal.up timeout: 1
|
216 |
|
217 | Clay 'client.infinite.loop', (err) ->
|
218 | err.message.should.be 'Message Timeout'
|
219 | done()
|
220 |
|
221 | describe 'share.any', ->
|
222 | describe 'framed', ->
|
223 | before ->
|
224 | ClayRoot.__set__ 'initHasBeenCalled', true
|
225 | ClayRoot.__set__ 'config', Promiz.resolve {gameId: '1'}
|
226 |
|
227 | it 'posts to parent', (done) ->
|
228 | routePost 'share.any',
|
229 | origin: 'http://clay.io'
|
230 | data: (message) ->
|
231 | message.params[0].gameId.should.be '1'
|
232 | result: {test: true}
|
233 |
|
234 | Clay 'client.share.any', [{text: 'Hello World'}], (err, res) ->
|
235 | res.test.should.be true
|
236 | done(err)
|
237 |
|
238 | it 'falls back to local if parent fails', (done) ->
|
239 | routePost 'share.any',
|
240 | origin: 'http://clay.io'
|
241 | data:
|
242 | error: {message: 'something went wrong'}
|
243 |
|
244 | openCnt = 0
|
245 | ClayRoot.__set__ 'window.open', (url, name) ->
|
246 | openCnt += 1
|
247 | url.should.be 'https://twitter.com/intent/tweet?text=Hello%20World'
|
248 | name.should.be '_system'
|
249 |
|
250 | Clay 'client.share.any', [{text: 'Hello World'}], (err) ->
|
251 | if err
|
252 | done(err)
|
253 |
|
254 | setTimeout ->
|
255 | openCnt.should.be 1
|
256 | done()
|
257 | , 100
|
258 |
|
259 | it 'errors if missing text', (done) ->
|
260 | routePost 'share.any',
|
261 | origin: 'http://clay.io'
|
262 | data:
|
263 | error: {message: 'something went wrong'}
|
264 |
|
265 | Clay 'client.share.any', (err) ->
|
266 | err.message.should.be 'text parameter is missing or invalid'
|
267 | done()
|
268 |
|
269 | describe 'local', ->
|
270 | before ->
|
271 | ClayRoot.__set__ 'initHasBeenCalled', true
|
272 | ClayRoot.__set__ 'config', Promiz.resolve {gameId: '1'}
|
273 |
|
274 | it 'tweets', (done) ->
|
275 | openCnt = 0
|
276 | ClayRoot.__set__ 'window.open', (url) ->
|
277 | openCnt += 1
|
278 | url.should.be 'https://twitter.com/intent/tweet?text=Hello%20World'
|
279 |
|
280 | Clay 'client.share.any', [{text: 'Hello World'}], (err, res) ->
|
281 | openCnt.should.be 1
|
282 | done(err)
|
283 |
|
284 | describe 'register', ->
|
285 | before ->
|
286 | ClayRoot.__set__ 'initHasBeenCalled', false
|
287 |
|
288 | it 'registers function', (done) ->
|
289 | rootConfig = deferredFactory()
|
290 | ClayRoot.__set__ 'config', rootConfig
|
291 |
|
292 | Clay 'register', {method: 'ui', fn: (config) ->
|
293 | (method, params, cb) ->
|
294 | config.then ({gameId}) ->
|
295 | {
|
296 | test: true
|
297 | hello: params[0].hello
|
298 | gameId: gameId
|
299 | }
|
300 | .then (x) -> cb null, x
|
301 | .catch cb
|
302 | }
|
303 |
|
304 | rootConfig.resolve {gameId: '1'}
|
305 |
|
306 | Clay 'ui.test', {hello: 'world'}, (err, res) ->
|
307 | res.test.should.be true
|
308 | res.hello.should.be 'world'
|
309 | res.gameId.should.be '1'
|
310 | done(err)
|
311 |
|
312 | describe 'domain verification', ->
|
313 | @timeout 1000
|
314 | before ->
|
315 | Clay 'init', {gameId: '1'}
|
316 | ClayRoot.__set__ 'initHasBeenCalled', true
|
317 | ClayRoot.__set__ 'config', Promiz.resolve {gameId: '1'}
|
318 |
|
319 | it 'Succeeds on valid domains', (done) ->
|
320 | domains = [
|
321 | "http://#{TRUSTED_DOMAIN}/"
|
322 | "https://#{TRUSTED_DOMAIN}/"
|
323 | "http://#{TRUSTED_DOMAIN}"
|
324 | "https://#{TRUSTED_DOMAIN}"
|
325 |
|
326 |
|
327 | "http://sub.#{TRUSTED_DOMAIN}/"
|
328 | "https://sub.#{TRUSTED_DOMAIN}/"
|
329 | "http://sub.#{TRUSTED_DOMAIN}"
|
330 | "https://sub.#{TRUSTED_DOMAIN}"
|
331 |
|
332 | "http://sub.sub.#{TRUSTED_DOMAIN}/"
|
333 | "https://sub.sub.#{TRUSTED_DOMAIN}/"
|
334 | "http://sub.sub.#{TRUSTED_DOMAIN}"
|
335 | "https://sub.sub.#{TRUSTED_DOMAIN}"
|
336 | ]
|
337 |
|
338 | Promise.map domains, (domain) ->
|
339 | routePost 'domain.test',
|
340 | origin: domain
|
341 | data:
|
342 | result: {test: true}
|
343 |
|
344 | Clay 'client.domain.test', (err, user) ->
|
345 | if err
|
346 | throw err
|
347 | user.test.should.be true
|
348 | .then -> done()
|
349 | .catch done
|
350 |
|
351 | it 'Errors on invalid domains', (done) ->
|
352 |
|
353 | domains = [
|
354 | 'http://evil.io/'
|
355 | 'http://sub.evil.io/'
|
356 | 'http://sub.sub.evil.io/'
|
357 | "http://evil.io/http://#{TRUSTED_DOMAIN}/"
|
358 | ]
|
359 |
|
360 | Promise.each domains, (domain) ->
|
361 | new Promise (resolve, reject) ->
|
362 | routePost 'kik.getUser',
|
363 | origin: domain
|
364 | data:
|
365 | result: {test: true}
|
366 |
|
367 | Clay 'client.kik.getUser', (err) ->
|
368 | unless err
|
369 | reject new Error 'Missing error'
|
370 |
|
371 | resolve()
|
372 | .then -> done()
|
373 | .catch done
|