· 8 years ago · Aug 05, 2018, 04:46 PM
1if (typeof (_pio) == 'undefined') { _pio = {} }
2(function () {
3 _pio.channel = function () { };
4 _pio.channel.prototype.call = function (method, args, successCallback, errorCallback, converter) {
5 var url = typeof(PLAYERIO_API_HOST) != 'undefined' ? PLAYERIO_API_HOST : ((PlayerIO.useSecureApiRequests ? 'https' : 'http') + '://api.playerio.com/json/');
6
7 var webrequest = new XMLHttpRequest();
8 if ("withCredentials" in webrequest) { // Both Safari 4 and Firefox 3.5 provide the withCredentials property on XMLHttpRequest
9 //webrequest.withCredentials = true;
10 webrequest.open("post", url, true);
11 } else if (typeof XDomainRequest != "undefined") { // IE uses XDomainRequest for some reason (?)
12 webrequest = new XDomainRequest();
13 webrequest.open("post", url);
14 } else {
15 // USE FLASH
16 webrequest = new _pio.flashWebRequest("post",url );
17 }
18
19 var originalStack = new Error();
20
21 if (webrequest != null) {
22 webrequest.send("[" +method + "|" + (this.token||"") + "]" + JSON.stringify(args));
23 webrequest.onload = function () {
24 var result = null;
25 try{
26 var response = webrequest.response || webrequest.responseText;
27 if( response[0] == "[" ){
28 var end = response.indexOf("]");
29 this.token = response.substring(1,end);
30 response = response.substring(end+1)
31 }
32 result = JSON.parse(response)
33 }catch(e){
34 _pio.handleError(originalStack, errorCallback, PlayerIOErrorCode.GeneralError, "Error decoding response from webservice: " + e)
35 return;
36 }
37
38 // success or error?
39 if( typeof(result.errorcode) == 'undefined' && typeof(result.message) == 'undefined' ){
40 var value = result;
41 if( converter ){
42 try{
43 value = converter(result);
44 } catch (e) {
45 _pio.handleError(originalStack, errorCallback, e.code, e.message)
46 }
47 }
48 if( successCallback ){
49 successCallback(value)
50 }
51 }else{
52 _pio.handleError(originalStack, errorCallback, result.errorcode, result.message)
53 }
54 }
55 webrequest.onerror = function (req) {
56 _pio.handleError(originalStack, errorCallback, PlayerIOErrorCode.GeneralError, "Error talking to webservice: " + JSON.stringify(req))
57 }
58 } else {
59 _pio.handleError(originalStack, errorCallback, PlayerIOErrorCode.GeneralError, "Need to implement flash calling")
60 }
61 };
62 _pio.runCallback = function(callback, callbackArg, originalStackError){
63 try{
64 if( callback ){
65 callback(callbackArg);
66 }
67 } catch (e) {
68 var message = "Unhandled error in callback: " + e.message;
69 message += "\nStack:\n"
70 message += (e.stack||e.stacktrace||e.StackTrace);
71 if( originalStackError ){
72 message += "\nCallsite stack:\n"
73 message += (originalStackError.stack||originalStackError.stacktrace||originalStackError.StackTrace);
74 }
75 console.log(message)
76 }
77 }
78 _pio.handleError = function(originalStackError,errorCallback,code,message){
79 var err = _pio.error(code, message)
80 if(originalStackError){
81 if(originalStackError.stack) err.stack = originalStackError.stack;
82 if(originalStackError.stacktrace) err.stacktrace = originalStackError.stacktrace;
83 if(originalStackError.StackTrace) err.StackTrace = originalStackError.StackTrace
84 }
85
86 if( errorCallback ){
87 _pio.runCallback(errorCallback, err, originalStackError)
88 }else if(typeof(console) != 'undefined'){
89 console.log("No error callback specified for: "+err.code + ": " + err.message + "\n" + (err.stack||err.stacktrace||err.StackTrace));
90 }else{
91 alert("No error callback specified for: "+err.code + ": " + err.message + "\n" + (err.stack||err.stacktrace||err.StackTrace));
92 }
93 }
94 _pio.error = function(code,message){
95 if(arguments.length == 1 ){
96 message = code;
97 code = PlayerIOErrorCode.GeneralError;
98 }
99
100 if( typeof(code) == 'number' ){
101 code = PlayerIOErrorCode.codes[code]
102 }
103 if( typeof(code) != 'string' ){
104 console.log(code, message, new Error().stack)
105 throw "Code must be a string!"
106 }
107 var e = new Error()
108 return new PlayerIOError(code, message, (e.stack || e.stacktrace || e.StackTrace));
109 }
110 _pio.debugLog = function(message){
111 if (typeof (console) != 'undefined') {
112 console.log(message);
113 }
114 }
115
116 _pio.convertToKVArray = function(object){
117 var result = []
118 if( object ){
119 for(var k in object){
120 result.push({key:(''+k),value:(''+object[k])})
121 }
122 }
123 return result;
124 }
125
126 _pio.convertFromKVArray = function(arr){
127 var result = {}
128 if( arr && arr.length){
129 for(var k in arr){
130 result[ arr[k].key ] = arr[k].value
131 }
132 }
133 return result;
134 }
135
136 _pio.convertToSegmentArray = function (object) {
137 var result = [];
138 if (object) {
139 for (var k in object) {
140 result.push(k + ':' + object[k]);
141 }
142 }
143 return result;
144 };
145})();
146/**
147* @class Main class for authenticating a user and getting a client.
148* @example Here is an example of using the class to authenticate:
149* <listing>
150* PlayerIO.authenticate(
151* '[Enter your game id here]', //Game id
152* 'public', //Connection id
153* { userId:'user-id' }, //Authentication arguments
154* { campaign:'2017' }, //Optional PlayerInsight segments
155* function(client) {
156* //Success!
157* //You can now use the client object to make API calls.
158* },
159* function(error) {
160* if (error.code == PlayerIOErrorCode.UnknownGame) {
161* //Unknown game id used
162* } else {
163* //Another error
164* }
165* }
166* );
167* </listing>
168*/
169PlayerIO = {
170 /**
171 * If set to true, all API Requests will be encrypted using TLS/SSL. Be aware that this will cause a performance degradation by introducting secure connection negotiation latency for all requests.
172 * @type bool
173 */
174 useSecureApiRequests: false,
175
176 /**
177 * Authenticates a user to Player.IO. See the Authentication documentation on which authenticationArguments that are needed for each authentication provider.
178 * @param {string} gameId The game id of the game you wish to connect to. This value can be found in the admin panel.
179 * @param {string} connectionId The id of the connection, as given in the settings section of the admin panel. 'public' should be used as the default
180 * @param {object} authenticationArguments A dictionary of arguments for the given connection.
181 * @param {object} playerInsightSegments Custom segments for the user in PlayerInsight.
182 * @param {function(client)} successCallback Callback function that will be called with a client when succesfully connected
183 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
184 */
185 authenticate: function (gameId, connectionId, authenticationArguments, playerInsightSegments, successCallback, errorCallback) {
186 if (authenticationArguments.publishingnetworklogin == 'auto') {
187 if (typeof (window.PublishingNetwork) == 'undefined') {
188 errorCallback(new PlayerIOError(PlayerIOErrorCode.GeneralError, "Could not find the PublishingNetwork object on the current page. Did you include the PublishingNetwork.js script?"));
189 return
190 }
191 PublishingNetwork.dialog("login", { gameId: gameId, connectionId: connectionId, __use_usertoken__: true }, function (r) {
192 if (r.error) {
193 errorCallback(new PlayerIOError(PlayerIOErrorCode.GeneralError, r.error));
194 } else if (typeof(r.userToken) == 'undefined') {
195 errorCallback(new PlayerIOError(PlayerIOErrorCode.GeneralError, "Missing userToken value in result, but no error message given."));
196 } else {
197 PlayerIO.authenticate(gameId, connectionId, {userToken:r.userToken}, playerInsightSegments, successCallback, errorCallback)
198 }
199 })
200 return
201 }
202
203 var channel = new _pio.channel();
204 channel.authenticate(gameId, connectionId, _pio.convertToKVArray(authenticationArguments), _pio.convertToSegmentArray(playerInsightSegments), "javascript", _pio.convertToKVArray({}), null, successCallback, errorCallback, function (result) {
205 channel.token = result.token;
206 return new _pio.client(channel, gameId, result.gamefsredirectmap, result.userid);
207 });
208 },
209
210 /**
211 * Access the QuickConnect service
212 * @type {quickConnect}
213 */
214 quickConnect: null, // gets overwritten at runtime.
215
216 /**
217 * Get a gameFS instance for a specific game (only use this method when you don't have a valid client).
218 * @param {string} gameId The game id of the game you wish to access.
219 */
220 gameFS: function (gameId) {
221 return new _pio.gameFS(gameId);
222 }
223};
224var JSON;
225if (!JSON) {
226 JSON = {};
227}
228
229(function () {
230 'use strict';
231
232 function f(n) {
233 // Format integers to have at least two digits.
234 return n < 10 ? '0' + n : n;
235 }
236
237 if (typeof Date.prototype.toJSON !== 'function') {
238
239 Date.prototype.toJSON = function (key) {
240
241 return isFinite(this.valueOf())
242 ? this.getUTCFullYear() + '-' +
243 f(this.getUTCMonth() + 1) + '-' +
244 f(this.getUTCDate()) + 'T' +
245 f(this.getUTCHours()) + ':' +
246 f(this.getUTCMinutes()) + ':' +
247 f(this.getUTCSeconds()) + 'Z'
248 : null;
249 };
250
251 String.prototype.toJSON =
252 Number.prototype.toJSON =
253 Boolean.prototype.toJSON = function (key) {
254 return this.valueOf();
255 };
256 }
257
258 var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
259 escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
260 gap,
261 indent,
262 meta = { // table of character substitutions
263 '\b': '\\b',
264 '\t': '\\t',
265 '\n': '\\n',
266 '\f': '\\f',
267 '\r': '\\r',
268 '"': '\\"',
269 '\\': '\\\\'
270 },
271 rep;
272
273
274 function quote(string) {
275
276 // If the string contains no control characters, no quote characters, and no
277 // backslash characters, then we can safely slap some quotes around it.
278 // Otherwise we must also replace the offending characters with safe escape
279 // sequences.
280
281 escapable.lastIndex = 0;
282 return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
283 var c = meta[a];
284 return typeof c === 'string'
285 ? c
286 : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
287 }) + '"' : '"' + string + '"';
288 }
289
290
291 function str(key, holder) {
292
293 // Produce a string from holder[key].
294
295 var i, // The loop counter.
296 k, // The member key.
297 v, // The member value.
298 length,
299 mind = gap,
300 partial,
301 value = holder[key];
302
303 // If the value has a toJSON method, call it to obtain a replacement value.
304
305 if (value && typeof value === 'object' &&
306 typeof value.toJSON === 'function') {
307 value = value.toJSON(key);
308 }
309
310 // If we were called with a replacer function, then call the replacer to
311 // obtain a replacement value.
312
313 if (typeof rep === 'function') {
314 value = rep.call(holder, key, value);
315 }
316
317 // What happens next depends on the value's type.
318
319 switch (typeof value) {
320 case 'string':
321 return quote(value);
322
323 case 'number':
324
325 // JSON numbers must be finite. Encode non-finite numbers as null.
326
327 return isFinite(value) ? String(value) : 'null';
328
329 case 'boolean':
330 case 'null':
331
332 // If the value is a boolean or null, convert it to a string. Note:
333 // typeof null does not produce 'null'. The case is included here in
334 // the remote chance that this gets fixed someday.
335
336 return String(value);
337
338 // If the type is 'object', we might be dealing with an object or an array or
339 // null.
340
341 case 'object':
342
343 // Due to a specification blunder in ECMAScript, typeof null is 'object',
344 // so watch out for that case.
345
346 if (!value) {
347 return 'null';
348 }
349
350 // Make an array to hold the partial results of stringifying this object value.
351
352 gap += indent;
353 partial = [];
354
355 // Is the value an array?
356
357 if (Object.prototype.toString.apply(value) === '[object Array]') {
358
359 // The value is an array. Stringify every element. Use null as a placeholder
360 // for non-JSON values.
361
362 length = value.length;
363 for (i = 0; i < length; i += 1) {
364 partial[i] = str(i, value) || 'null';
365 }
366
367 // Join all of the elements together, separated with commas, and wrap them in
368 // brackets.
369
370 v = partial.length === 0
371 ? '[]'
372 : gap
373 ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
374 : '[' + partial.join(',') + ']';
375 gap = mind;
376 return v;
377 }
378
379 // If the replacer is an array, use it to select the members to be stringified.
380
381 if (rep && typeof rep === 'object') {
382 length = rep.length;
383 for (i = 0; i < length; i += 1) {
384 if (typeof rep[i] === 'string') {
385 k = rep[i];
386 v = str(k, value);
387 if (v) {
388 partial.push(quote(k) + (gap ? ': ' : ':') + v);
389 }
390 }
391 }
392 } else {
393
394 // Otherwise, iterate through all of the keys in the object.
395
396 for (k in value) {
397 if (Object.prototype.hasOwnProperty.call(value, k)) {
398 v = str(k, value);
399 if (v) {
400 partial.push(quote(k) + (gap ? ': ' : ':') + v);
401 }
402 }
403 }
404 }
405
406 // Join all of the member texts together, separated with commas,
407 // and wrap them in braces.
408
409 v = partial.length === 0
410 ? '{}'
411 : gap
412 ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
413 : '{' + partial.join(',') + '}';
414 gap = mind;
415 return v;
416 }
417 }
418
419 // If the JSON object does not yet have a stringify method, give it one.
420
421 if (typeof JSON.stringify !== 'function') {
422 JSON.stringify = function (value, replacer, space) {
423
424 // The stringify method takes a value and an optional replacer, and an optional
425 // space parameter, and returns a JSON text. The replacer can be a function
426 // that can replace values, or an array of strings that will select the keys.
427 // A default replacer method can be provided. Use of the space parameter can
428 // produce text that is more easily readable.
429
430 var i;
431 gap = '';
432 indent = '';
433
434 // If the space parameter is a number, make an indent string containing that
435 // many spaces.
436
437 if (typeof space === 'number') {
438 for (i = 0; i < space; i += 1) {
439 indent += ' ';
440 }
441
442 // If the space parameter is a string, it will be used as the indent string.
443
444 } else if (typeof space === 'string') {
445 indent = space;
446 }
447
448 // If there is a replacer, it must be a function or an array.
449 // Otherwise, throw an error.
450
451 rep = replacer;
452 if (replacer && typeof replacer !== 'function' &&
453 (typeof replacer !== 'object' ||
454 typeof replacer.length !== 'number')) {
455 throw new Error('JSON.stringify');
456 }
457
458 // Make a fake root object containing our value under the key of ''.
459 // Return the result of stringifying the value.
460
461 return str('', { '': value });
462 };
463 }
464
465
466 // If the JSON object does not yet have a parse method, give it one.
467
468 if (typeof JSON.parse !== 'function') {
469 JSON.parse = function (text, reviver) {
470
471 // The parse method takes a text and an optional reviver function, and returns
472 // a JavaScript value if the text is a valid JSON text.
473
474 var j;
475
476 function walk(holder, key) {
477
478 // The walk method is used to recursively walk the resulting structure so
479 // that modifications can be made.
480
481 var k, v, value = holder[key];
482 if (value && typeof value === 'object') {
483 for (k in value) {
484 if (Object.prototype.hasOwnProperty.call(value, k)) {
485 v = walk(value, k);
486 if (v !== undefined) {
487 value[k] = v;
488 } else {
489 delete value[k];
490 }
491 }
492 }
493 }
494 return reviver.call(holder, key, value);
495 }
496
497
498 // Parsing happens in four stages. In the first stage, we replace certain
499 // Unicode characters with escape sequences. JavaScript handles many characters
500 // incorrectly, either silently deleting them, or treating them as line endings.
501
502 text = String(text);
503 cx.lastIndex = 0;
504 if (cx.test(text)) {
505 text = text.replace(cx, function (a) {
506 return '\\u' +
507 ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
508 });
509 }
510
511 // In the second stage, we run the text against regular expressions that look
512 // for non-JSON patterns. We are especially concerned with '()' and 'new'
513 // because they can cause invocation, and '=' because it can cause mutation.
514 // But just to be safe, we want to reject all unexpected forms.
515
516 // We split the second stage into 4 regexp operations in order to work around
517 // crippling inefficiencies in IE's and Safari's regexp engines. First we
518 // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
519 // replace all simple value tokens with ']' characters. Third, we delete all
520 // open brackets that follow a colon or comma or that begin the text. Finally,
521 // we look to see that the remaining characters are only whitespace or ']' or
522 // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
523
524 if (/^[\],:{}\s]*$/
525 .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
526 .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
527 .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
528
529 // In the third stage we use the eval function to compile the text into a
530 // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
531 // in JavaScript: it can begin a block or an object literal. We wrap the text
532 // in parens to eliminate the ambiguity.
533
534 j = eval('(' + text + ')');
535
536 // In the optional fourth stage, we recursively walk the new structure, passing
537 // each name/value pair to a reviver function for possible transformation.
538
539 return typeof reviver === 'function'
540 ? walk({ '': j }, '')
541 : j;
542 }
543
544 // If the text is not JSON parseable, then a SyntaxError is thrown.
545
546 throw new SyntaxError('JSON.parse');
547 };
548 }
549}());
550(function () {
551 var callbacks = {};
552 var idCounter = 0;
553 var flashUrl = "http://192.168.30.154/html5client/FlashFallback/bin-debug/FlashFallback.swf"
554
555 // this method gets called back from flash
556 __pio_flashfallback_callback__ = function () {
557 var callback = callbacks[arguments[0]];
558 if (callback) {
559 var args = []
560 for (var i = 1; i != arguments.length; i++) {
561 args[i - 1] = arguments[i];
562 }
563 callback.apply(null, args);
564 }
565 }
566
567 _pio.flashWebRequest = function (method, url) {
568 var self = this;
569
570 this.response = null;
571 this.onload = function () { }
572 this.onerror = function () { }
573
574 this.send = function (data) {
575 getFlashFallbackObj(function (obj) {
576 if (obj == null) {
577 self.onerror("Browser does not support Cross-Origin (CORS) webrequest or Flash as a fallback method");
578 } else {
579 var id = "cb" + (idCounter++)
580
581 callbacks[id] = function (success, response) {
582 delete callbacks[id];
583 if (success) {
584 self.response = response;
585 self.onload();
586 } else {
587 self.onerror(response);
588 }
589 }
590
591 // start the web request via flash
592 obj.webrequest(id, method, url, data)
593 }
594 })
595 }
596 }
597
598 _pio.flashSocketConnection = function (endpoint, connectTimeout, onConnectResult, onDisconnect, onMessage) {
599 var id = "cb" + (idCounter++)
600 var self = this;
601 var serializer = new _pio.messageSerializer();
602 var connectComplete = false;
603 var connected = false;
604 var timeout = setTimeout(function () {
605 if (!connectComplete) {
606 connectComplete = true;
607 onConnectResult(false, "Connect attempt timed out");
608 }
609 }, connectTimeout);
610
611 this.disconnect = function () { console.log("... this shouldn't happen"); }
612 this.sendMessage = function (message) { console.log("... send msg. this shouldn't happen"); }
613
614 getFlashFallbackObj(function (obj) {
615 if (obj == null) {
616 connectComplete = true;
617 onConnectResult(false, "Browser does not support WebSocket connections and the Flash fallback failed.");
618 } else {
619 callbacks[id] = function (evt, data) {
620 switch (evt) {
621 case 'onopen':
622 if (!connectComplete) {
623 clearTimeout(timeout)
624 connectComplete = true;
625 connected = true;
626 obj.socketSend(id, [0]); // protocol selection
627 onConnectResult(connected);
628 }
629 break;
630 case 'onclose':
631 self.disconnect();
632 break;
633 case 'onerror':
634 self.disconnect();
635 break;
636 case 'onmessage':
637 onMessage(serializer.deserializeMessage(data, 0, data.length))
638 break;
639 }
640 }
641
642 self.disconnect = function () {
643 if (connected) {
644 connected = false;
645 onDisconnect();
646 try {
647 obj.socketClose(id);
648 } catch (e) { _pio.debugLog(e) }
649 }
650 }
651
652 self.sendMessage = function (message) {
653 var serialized = serializer.serializeMessage(message);
654 obj.socketSend(id, serialized);
655 }
656
657 // start the web request via flash
658 obj.socketConnection(id, endpoint)
659 }
660 })
661 }
662
663 _pio.isFlashFallbackEnabled = function (callback) {
664 getFlashFallbackObj(function (obj) {
665 callback(obj != null);
666 })
667 }
668
669 var fallbackObj = null;
670 var fallbackFailed = false;
671 var waitingGetFallbacks = null;
672
673 function getFlashFallbackObj(callback) {
674 if (fallbackObj != null) {
675 callback(fallbackObj);
676 } else if (fallbackFailed) {
677 callback(null);
678 } else {
679 if (waitingGetFallbacks == null) {
680 // create the list for future listners
681 waitingGetFallbacks = [callback];
682
683 // try to create the flash object
684 var tryInterval = setInterval(function () {
685 var obj = createFallbackObj();
686 if (obj != null) {
687 done(obj);
688 }
689 }, 50);
690
691 // set a timeout to timeout the request if it does not work
692 setTimeout(function () {
693 if (fallbackObj == null) {
694 done(null);
695 }
696 }, 30000);
697
698 var done = function (obj) {
699 fallbackObj = obj;
700 fallbackFailed = obj == null;
701 clearInterval(tryInterval);
702 for (var i = 0; i != waitingGetFallbacks.length; i++) {
703 waitingGetFallbacks[i](obj);
704 }
705 }
706 } else {
707 waitingGetFallbacks.push(callback);
708 }
709 }
710 }
711
712 function createFallbackObj() {
713 var id = "__pio_flashfallback__";
714 var containerId = "__pio_flashfallback_container__";
715 var html = ""
716 html += '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="10" height="10" style="$style$" id="$id$">'
717 html += ' <param name="movie" value="$src$" />'
718 html += ' <param name="allowNetworking" value="all" />'
719 html += ' <param name="allowScriptAccess" value="always" />'
720 html += ' <!--[if !IE]>-->'
721 html += ' <object type="application/x-shockwave-flash" data="$src$" width="10" height="10" style="$style$">'
722 html += ' <param name="allowNetworking" value="all" />'
723 html += ' <param name="allowScriptAccess" value="always" />'
724 html += ' </object>'
725 html += ' <!--<![endif]-->'
726 html += '</object>'
727 html = html.replace(/\$id\$/gi, id);
728 html = html.replace(/\$src\$/gi, flashUrl);
729 html = html.replace(/\$style\$/gi, "width:10px;height:10px");
730
731 var container = document.getElementById("containerId");
732 if (!container) {
733 var div = document.createElement("div");
734 div.setAttribute("id", container)
735 div.setAttribute("style", "position:absolute;top:-20px;left:-20px")
736 div.innerHTML = html;
737 try {
738 document.body.appendChild(div);
739 } catch (e) { }
740 }
741
742 var find = function (tag) {
743 var list = document.getElementsByTagName(tag);
744 for (var i = 0; i != list.length; i++) {
745 if (list[i].ping && list[i].ping() == "pong") {
746 return list[i]
747 }
748 }
749 };
750
751 return find("embed") || find("object");
752 }
753})();
754
755(function () {
756 var c = _pio.channel.prototype;
757 c.connect = function(gameId, connectionId, userId, auth, partnerId, playerInsightSegments, clientAPI, clientInfo, successCallback, errorCallback, converter){this.call(10, {gameid:gameId, connectionid:connectionId, userid:userId, auth:auth, partnerid:partnerId, playerinsightsegments:playerInsightSegments, clientapi:clientAPI, clientinfo:clientInfo}, successCallback, errorCallback, converter)}
758 _pio.ApiSecurityRule = {RespectClientSetting:0,UseHttp:1,UseHttps:2}
759 c.authenticate = function(gameId, connectionId, authenticationArguments, playerInsightSegments, clientAPI, clientInfo, playCodes, successCallback, errorCallback, converter){this.call(13, {gameid:gameId, connectionid:connectionId, authenticationarguments:authenticationArguments, playerinsightsegments:playerInsightSegments, clientapi:clientAPI, clientinfo:clientInfo, playcodes:playCodes}, successCallback, errorCallback, converter)}
760 c.createRoom = function(roomId, roomType, visible, roomData, isDevRoom, successCallback, errorCallback, converter){this.call(21, {roomid:roomId, roomtype:roomType, visible:visible, roomdata:roomData, isdevroom:isDevRoom}, successCallback, errorCallback, converter)}
761 c.joinRoom = function(roomId, joinData, isDevRoom, successCallback, errorCallback, converter){this.call(24, {roomid:roomId, joindata:joinData, isdevroom:isDevRoom}, successCallback, errorCallback, converter)}
762 c.createJoinRoom = function(roomId, roomType, visible, roomData, joinData, isDevRoom, successCallback, errorCallback, converter){this.call(27, {roomid:roomId, roomtype:roomType, visible:visible, roomdata:roomData, joindata:joinData, isdevroom:isDevRoom}, successCallback, errorCallback, converter)}
763 c.listRooms = function(roomType, searchCriteria, resultLimit, resultOffset, onlyDevRooms, successCallback, errorCallback, converter){this.call(30, {roomtype:roomType, searchcriteria:searchCriteria, resultlimit:resultLimit, resultoffset:resultOffset, onlydevrooms:onlyDevRooms}, successCallback, errorCallback, converter)}
764 c.userLeftRoom = function(extendedRoomId, newPlayerCount, closed, successCallback, errorCallback, converter){this.call(40, {extendedroomid:extendedRoomId, newplayercount:newPlayerCount, closed:closed}, successCallback, errorCallback, converter)}
765 c.writeError = function(source, error, details, stacktrace, extraData, successCallback, errorCallback, converter){this.call(50, {source:source, error:error, details:details, stacktrace:stacktrace, extradata:extraData}, successCallback, errorCallback, converter)}
766 c.updateRoom = function(extendedRoomId, visible, roomData, successCallback, errorCallback, converter){this.call(53, {extendedroomid:extendedRoomId, visible:visible, roomdata:roomData}, successCallback, errorCallback, converter)}
767 _pio.ValueType = {String:0,Int:1,UInt:2,Long:3,Bool:4,Float:5,Double:6,ByteArray:7,DateTime:8,Array:9,Obj:10}
768 c.createObjects = function(objects, loadExisting, successCallback, errorCallback, converter){this.call(82, {objects:objects, loadexisting:loadExisting}, successCallback, errorCallback, converter)}
769 c.loadObjects = function(objectIds, successCallback, errorCallback, converter){this.call(85, {objectids:objectIds}, successCallback, errorCallback, converter)}
770 _pio.LockType = {NoLocks:0,LockIndividual:1,LockAll:2}
771 c.saveObjectChanges = function(lockType, changesets, createIfMissing, successCallback, errorCallback, converter){this.call(88, {locktype:lockType, changesets:changesets, createifmissing:createIfMissing}, successCallback, errorCallback, converter)}
772 c.deleteObjects = function(objectIds, successCallback, errorCallback, converter){this.call(91, {objectids:objectIds}, successCallback, errorCallback, converter)}
773 c.loadMatchingObjects = function(table, index, indexValue, limit, successCallback, errorCallback, converter){this.call(94, {table:table, index:index, indexvalue:indexValue, limit:limit}, successCallback, errorCallback, converter)}
774 c.loadIndexRange = function(table, index, startIndexValue, stopIndexValue, limit, successCallback, errorCallback, converter){this.call(97, {table:table, index:index, startindexvalue:startIndexValue, stopindexvalue:stopIndexValue, limit:limit}, successCallback, errorCallback, converter)}
775 c.deleteIndexRange = function(table, index, startIndexValue, stopIndexValue, successCallback, errorCallback, converter){this.call(100, {table:table, index:index, startindexvalue:startIndexValue, stopindexvalue:stopIndexValue}, successCallback, errorCallback, converter)}
776 c.loadMyPlayerObject = function(successCallback, errorCallback, converter){this.call(103, {}, successCallback, errorCallback, converter)}
777 c.payVaultReadHistory = function(page, pageSize, targetUserId, successCallback, errorCallback, converter){this.call(160, {page:page, pagesize:pageSize, targetuserid:targetUserId}, successCallback, errorCallback, converter)}
778 c.payVaultRefresh = function(lastVersion, targetUserId, successCallback, errorCallback, converter){this.call(163, {lastversion:lastVersion, targetuserid:targetUserId}, successCallback, errorCallback, converter)}
779 c.payVaultConsume = function(ids, targetUserId, successCallback, errorCallback, converter){this.call(166, {ids:ids, targetuserid:targetUserId}, successCallback, errorCallback, converter)}
780 c.payVaultCredit = function(amount, reason, targetUserId, successCallback, errorCallback, converter){this.call(169, {amount:amount, reason:reason, targetuserid:targetUserId}, successCallback, errorCallback, converter)}
781 c.payVaultDebit = function(amount, reason, targetUserId, successCallback, errorCallback, converter){this.call(172, {amount:amount, reason:reason, targetuserid:targetUserId}, successCallback, errorCallback, converter)}
782 c.payVaultBuy = function(items, storeItems, targetUserId, successCallback, errorCallback, converter){this.call(175, {items:items, storeitems:storeItems, targetuserid:targetUserId}, successCallback, errorCallback, converter)}
783 c.payVaultGive = function(items, targetUserId, successCallback, errorCallback, converter){this.call(178, {items:items, targetuserid:targetUserId}, successCallback, errorCallback, converter)}
784 c.payVaultPaymentInfo = function(provider, purchaseArguments, items, successCallback, errorCallback, converter){this.call(181, {provider:provider, purchasearguments:purchaseArguments, items:items}, successCallback, errorCallback, converter)}
785 c.payVaultUsePaymentInfo = function(provider, providerArguments, successCallback, errorCallback, converter){this.call(184, {provider:provider, providerarguments:providerArguments}, successCallback, errorCallback, converter)}
786 c.partnerPayTrigger = function(key, count, successCallback, errorCallback, converter){this.call(200, {key:key, count:count}, successCallback, errorCallback, converter)}
787 c.partnerPaySetTag = function(partnerId, successCallback, errorCallback, converter){this.call(203, {partnerid:partnerId}, successCallback, errorCallback, converter)}
788 c.notificationsRefresh = function(lastVersion, successCallback, errorCallback, converter){this.call(213, {lastversion:lastVersion}, successCallback, errorCallback, converter)}
789 c.notificationsRegisterEndpoints = function(lastVersion, endpoints, successCallback, errorCallback, converter){this.call(216, {lastversion:lastVersion, endpoints:endpoints}, successCallback, errorCallback, converter)}
790 c.notificationsSend = function(notifications, successCallback, errorCallback, converter){this.call(219, {notifications:notifications}, successCallback, errorCallback, converter)}
791 c.notificationsToggleEndpoints = function(lastVersion, endpoints, enabled, successCallback, errorCallback, converter){this.call(222, {lastversion:lastVersion, endpoints:endpoints, enabled:enabled}, successCallback, errorCallback, converter)}
792 c.notificationsDeleteEndpoints = function(lastVersion, endpoints, successCallback, errorCallback, converter){this.call(225, {lastversion:lastVersion, endpoints:endpoints}, successCallback, errorCallback, converter)}
793 c.gameRequestsSend = function(requestType, requestData, requestRecipients, successCallback, errorCallback, converter){this.call(241, {requesttype:requestType, requestdata:requestData, requestrecipients:requestRecipients}, successCallback, errorCallback, converter)}
794 c.gameRequestsRefresh = function(playCodes, successCallback, errorCallback, converter){this.call(244, {playcodes:playCodes}, successCallback, errorCallback, converter)}
795 c.gameRequestsDelete = function(requestIds, successCallback, errorCallback, converter){this.call(247, {requestids:requestIds}, successCallback, errorCallback, converter)}
796 c.achievementsRefresh = function(lastVersion, successCallback, errorCallback, converter){this.call(271, {lastversion:lastVersion}, successCallback, errorCallback, converter)}
797 c.achievementsLoad = function(userIds, successCallback, errorCallback, converter){this.call(274, {userids:userIds}, successCallback, errorCallback, converter)}
798 c.achievementsProgressSet = function(achievementId, progress, successCallback, errorCallback, converter){this.call(277, {achievementid:achievementId, progress:progress}, successCallback, errorCallback, converter)}
799 c.achievementsProgressAdd = function(achievementId, progressDelta, successCallback, errorCallback, converter){this.call(280, {achievementid:achievementId, progressdelta:progressDelta}, successCallback, errorCallback, converter)}
800 c.achievementsProgressMax = function(achievementId, progress, successCallback, errorCallback, converter){this.call(283, {achievementid:achievementId, progress:progress}, successCallback, errorCallback, converter)}
801 c.achievementsProgressComplete = function(achievementId, successCallback, errorCallback, converter){this.call(286, {achievementid:achievementId}, successCallback, errorCallback, converter)}
802 c.playerInsightRefresh = function(successCallback, errorCallback, converter){this.call(301, {}, successCallback, errorCallback, converter)}
803 c.playerInsightSetSegments = function(segments, successCallback, errorCallback, converter){this.call(304, {segments:segments}, successCallback, errorCallback, converter)}
804 c.playerInsightTrackInvitedBy = function(invitingUserId, invitationChannel, successCallback, errorCallback, converter){this.call(307, {invitinguserid:invitingUserId, invitationchannel:invitationChannel}, successCallback, errorCallback, converter)}
805 c.playerInsightTrackEvents = function(events, successCallback, errorCallback, converter){this.call(311, {events:events}, successCallback, errorCallback, converter)}
806 c.playerInsightTrackExternalPayment = function(currency, amount, successCallback, errorCallback, converter){this.call(314, {currency:currency, amount:amount}, successCallback, errorCallback, converter)}
807 c.playerInsightSessionKeepAlive = function(successCallback, errorCallback, converter){this.call(317, {}, successCallback, errorCallback, converter)}
808 c.playerInsightSessionStop = function(successCallback, errorCallback, converter){this.call(320, {}, successCallback, errorCallback, converter)}
809 c.oneScoreLoad = function(userIds, successCallback, errorCallback, converter){this.call(351, {userids:userIds}, successCallback, errorCallback, converter)}
810 c.oneScoreSet = function(score, successCallback, errorCallback, converter){this.call(354, {score:score}, successCallback, errorCallback, converter)}
811 c.oneScoreAdd = function(score, successCallback, errorCallback, converter){this.call(357, {score:score}, successCallback, errorCallback, converter)}
812 c.oneScoreRefresh = function(successCallback, errorCallback, converter){this.call(360, {}, successCallback, errorCallback, converter)}
813 c.simpleConnect = function(gameId, usernameOrEmail, password, playerInsightSegments, clientAPI, clientInfo, successCallback, errorCallback, converter){this.call(400, {gameid:gameId, usernameoremail:usernameOrEmail, password:password, playerinsightsegments:playerInsightSegments, clientapi:clientAPI, clientinfo:clientInfo}, successCallback, errorCallback, converter)}
814 c.simpleRegister = function(gameId, username, password, email, captchaKey, captchaValue, extraData, partnerId, playerInsightSegments, clientAPI, clientInfo, successCallback, errorCallback, converter){this.call(403, {gameid:gameId, username:username, password:password, email:email, captchakey:captchaKey, captchavalue:captchaValue, extradata:extraData, partnerid:partnerId, playerinsightsegments:playerInsightSegments, clientapi:clientAPI, clientinfo:clientInfo}, successCallback, errorCallback, converter)}
815 c.simpleRecoverPassword = function(gameId, usernameOrEmail, successCallback, errorCallback, converter){this.call(406, {gameid:gameId, usernameoremail:usernameOrEmail}, successCallback, errorCallback, converter)}
816 c.kongregateConnect = function(gameId, userId, gameAuthToken, playerInsightSegments, clientAPI, clientInfo, successCallback, errorCallback, converter){this.call(412, {gameid:gameId, userid:userId, gameauthtoken:gameAuthToken, playerinsightsegments:playerInsightSegments, clientapi:clientAPI, clientinfo:clientInfo}, successCallback, errorCallback, converter)}
817 c.simpleGetCaptcha = function(gameId, width, height, successCallback, errorCallback, converter){this.call(415, {gameid:gameId, width:width, height:height}, successCallback, errorCallback, converter)}
818 c.facebookOAuthConnect = function(gameId, accessToken, partnerId, playerInsightSegments, clientAPI, clientInfo, successCallback, errorCallback, converter){this.call(418, {gameid:gameId, accesstoken:accessToken, partnerid:partnerId, playerinsightsegments:playerInsightSegments, clientapi:clientAPI, clientinfo:clientInfo}, successCallback, errorCallback, converter)}
819 c.steamConnect = function(gameId, steamAppId, steamSessionTicket, playerInsightSegments, clientAPI, clientInfo, successCallback, errorCallback, converter){this.call(421, {gameid:gameId, steamappid:steamAppId, steamsessionticket:steamSessionTicket, playerinsightsegments:playerInsightSegments, clientapi:clientAPI, clientinfo:clientInfo}, successCallback, errorCallback, converter)}
820 c.simpleUserGetSecureLoginInfo = function(successCallback, errorCallback, converter){this.call(424, {}, successCallback, errorCallback, converter)}
821 c.joinCluster = function(clusterAccessKey, isDevelopmentServer, ports, machineName, version, machineId, os, cpu, cpuCores, cpuLogicalCores, cpuAddressWidth, cpuMaxClockSpeed, ramMegabytes, ramSpeed, successCallback, errorCallback, converter){this.call(504, {clusteraccesskey:clusterAccessKey, isdevelopmentserver:isDevelopmentServer, ports:ports, machinename:machineName, version:version, machineid:machineId, os:os, cpu:cpu, cpucores:cpuCores, cpulogicalcores:cpuLogicalCores, cpuaddresswidth:cpuAddressWidth, cpumaxclockspeed:cpuMaxClockSpeed, rammegabytes:ramMegabytes, ramspeed:ramSpeed}, successCallback, errorCallback, converter)}
822 c.serverHeartbeat = function(serverId, appDomains, serverTypes, machineCPU, processCPU, memoryUsage, avaliableMemory, freeMemory, runningRooms, usedResources, aPIRequests, aPIRequestsError, aPIRequestsFailed, aPIRequestsExecuting, aPIRequestsQueued, aPIRequestsTime, serverUnixTimeUtc, successCallback, errorCallback, converter){this.call(510, {serverid:serverId, appdomains:appDomains, servertypes:serverTypes, machinecpu:machineCPU, processcpu:processCPU, memoryusage:memoryUsage, avaliablememory:avaliableMemory, freememory:freeMemory, runningrooms:runningRooms, usedresources:usedResources, apirequests:aPIRequests, apirequestserror:aPIRequestsError, apirequestsfailed:aPIRequestsFailed, apirequestsexecuting:aPIRequestsExecuting, apirequestsqueued:aPIRequestsQueued, apirequeststime:aPIRequestsTime, serverunixtimeutc:serverUnixTimeUtc}, successCallback, errorCallback, converter)}
823 c.getGameAssemblyUrl = function(clusterAccessKey, gameCodeId, machineId, successCallback, errorCallback, converter){this.call(513, {clusteraccesskey:clusterAccessKey, gamecodeid:gameCodeId, machineid:machineId}, successCallback, errorCallback, converter)}
824 c.devServerLogin = function(username, password, successCallback, errorCallback, converter){this.call(524, {username:username, password:password}, successCallback, errorCallback, converter)}
825 c.webserviceOnlineTest = function(successCallback, errorCallback, converter){this.call(533, {}, successCallback, errorCallback, converter)}
826 c.getServerInfo = function(machineId, successCallback, errorCallback, converter){this.call(540, {machineid:machineId}, successCallback, errorCallback, converter)}
827 c.socialRefresh = function(successCallback, errorCallback, converter){this.call(601, {}, successCallback, errorCallback, converter)}
828 c.socialLoadProfiles = function(userIds, successCallback, errorCallback, converter){this.call(604, {userids:userIds}, successCallback, errorCallback, converter)}
829})();
830
831/**
832* @class Instances of this class are returned in all error callbacks and contain information about the error that occurred.
833* @example Here is an example of authenticating and then handling an UnknownGame error:
834* <listing>
835* PlayerIO.authenticate(
836* '[Enter your game id here]', //Game id
837* 'public', //Connection id
838* { userId: 'user-id' }, //Authentication arguments
839* { campaign: '2017' }, //Optional PlayerInsight segments
840* function(client) {
841* //Success!
842* //You can now use the client object to make API calls.
843* },
844* function(error) {
845* if (error.code == PlayerIOErrorCode.UnknownGame) {
846* //Unknown game id used
847* } else {
848* //Another error
849* }
850* }
851* );
852* </listing>
853*/
854PlayerIOError = function (code, message, stack) {
855 /** The PlayerIO error code for this error
856 * @type string
857 */
858 this.code = code
859
860 /** The error message for this error
861 * @type string
862 */
863 this.message = message
864
865 /** The stack for this error, if any. The type depends on the current browser.
866 * @type object
867 */
868 this.stack = stack
869 if (!this.stack) {
870 var e = new Error()
871 this.stack = e.stack || e.stacktrace || e.StackTrace
872 }
873
874 /** Get a string representation of error
875 * @return string */
876 this.toString = function () {
877 return "PlayerIOError[" + code + "]: " + message;
878 }
879};
880PlayerIOError.prototype = new Error();
881
882/**
883* @class This class contains a list of all the error codes that can be returned from PlayerIO calls
884* @example Here is an example of authenticating and then handling an UnknownGame error:
885* <listing>
886* PlayerIO.authenticate(
887* '[Enter your game id here]', //Game id
888* 'public', //Connection id
889* { userId: 'user-id' }, //Authentication arguments
890* { campaign: '2017' }, //Optional PlayerInsight segments
891* function(client) {
892* //Success!
893* //You can now use the client object to make API calls.
894* },
895* function(error) {
896* if (error.code == PlayerIOErrorCode.UnknownGame) {
897* //Unknown game id used
898* } else {
899* //Another error
900* }
901* }
902* );
903* </listing>
904*/
905PlayerIOErrorCode = {
906 /** The method requested is not supported */
907 UnsupportedMethod:"UnsupportedMethod",
908 /** A general error occurred */
909 GeneralError:"GeneralError",
910 /** An unexpected error occurred inside the Player.IO webservice. Please try again. */
911 InternalError:"InternalError",
912 /** Access is denied */
913 AccessDenied:"AccessDenied",
914 /** The message is malformatted */
915 InvalidMessageFormat:"InvalidMessageFormat",
916 /** A value is missing */
917 MissingValue:"MissingValue",
918 /** A game is required to do this action */
919 GameRequired:"GameRequired",
920 /** An error occurred while contacting an external service */
921 ExternalError:"ExternalError",
922 /** The given argument value is outside the range of allowed values. */
923 ArgumentOutOfRange:"ArgumentOutOfRange",
924 /** The game has been disabled, most likely because of missing payment. */
925 GameDisabled:"GameDisabled",
926 /** The game requested is not known by the server */
927 UnknownGame:"UnknownGame",
928 /** The connection requested is not known by the server */
929 UnknownConnection:"UnknownConnection",
930 /** The auth given is invalid or malformatted */
931 InvalidAuth:"InvalidAuth",
932 /** There is no server in any of the selected server clusters for the game that are eligible to start a new room in (they're all at full capacity or there are no servers in any of the clusters). Either change the selected clusters for your game in the admin panel, try again later or start some more servers for one of your clusters. */
933 NoServersAvailable:"NoServersAvailable",
934 /** The room data for the room was over the allowed size limit */
935 RoomDataTooLarge:"RoomDataTooLarge",
936 /** You are unable to create room because there is already a room with the specified id */
937 RoomAlreadyExists:"RoomAlreadyExists",
938 /** The game you're connected to does not have a room type with the specified name */
939 UnknownRoomType:"UnknownRoomType",
940 /** There is no room running with that id */
941 UnknownRoom:"UnknownRoom",
942 /** You can't join the room when the RoomID is null or the empty string */
943 MissingRoomId:"MissingRoomId",
944 /** The room already has the maxmium amount of users in it. */
945 RoomIsFull:"RoomIsFull",
946 /** The key you specified is not set as searchable. You can change the searchable keys in the admin panel for the server type */
947 NotASearchColumn:"NotASearchColumn",
948 /** The QuickConnect method (simple, facebook, kongregate...) is not enabled for the game. You can enable the various methods in the admin panel for the game */
949 QuickConnectMethodNotEnabled:"QuickConnectMethodNotEnabled",
950 /** The user is unknown */
951 UnknownUser:"UnknownUser",
952 /** The password supplied is incorrect */
953 InvalidPassword:"InvalidPassword",
954 /** The supplied data is incorrect */
955 InvalidRegistrationData:"InvalidRegistrationData",
956 /** The key given for the BigDB object is not a valid BigDB key. Keys must be between 1 and 50 characters. Only letters, numbers, hyphens, underbars, and spaces are allowed. */
957 InvalidBigDBKey:"InvalidBigDBKey",
958 /** The object exceeds the maximum allowed size for BigDB objects. */
959 BigDBObjectTooLarge:"BigDBObjectTooLarge",
960 /** Could not locate the database object. */
961 BigDBObjectDoesNotExist:"BigDBObjectDoesNotExist",
962 /** The specified table does not exist. */
963 UnknownTable:"UnknownTable",
964 /** The specified index does not exist. */
965 UnknownIndex:"UnknownIndex",
966 /** The value given for the index, does not match the expected type. */
967 InvalidIndexValue:"InvalidIndexValue",
968 /** The operation was aborted because the user attempting the operation was not the original creator of the object accessed. */
969 NotObjectCreator:"NotObjectCreator",
970 /** The key is in use by another database object */
971 KeyAlreadyUsed:"KeyAlreadyUsed",
972 /** BigDB object could not be saved using optimistic locks as it's out of date. */
973 StaleVersion:"StaleVersion",
974 /** Cannot create circular references inside database objects */
975 CircularReference:"CircularReference",
976 /** The server could not complete the heartbeat */
977 HeartbeatFailed:"HeartbeatFailed",
978 /** The game code is invalid */
979 InvalidGameCode:"InvalidGameCode",
980 /** Cannot access coins or items before vault has been loaded. Please refresh the vault first. */
981 VaultNotLoaded:"VaultNotLoaded",
982 /** There is no PayVault provider with the specified id */
983 UnknownPayVaultProvider:"UnknownPayVaultProvider",
984 /** The specified PayVault provider does not support direct purchase */
985 DirectPurchaseNotSupportedByProvider:"DirectPurchaseNotSupportedByProvider",
986 /** The specified PayVault provider does not support buying coins */
987 BuyingCoinsNotSupportedByProvider:"BuyingCoinsNotSupportedByProvider",
988 /** The user does not have enough coins in the PayVault to complete the purchase or debit. */
989 NotEnoughCoins:"NotEnoughCoins",
990 /** The item does not exist in the vault. */
991 ItemNotInVault:"ItemNotInVault",
992 /** The chosen provider rejected one or more of the purchase arguments */
993 InvalidPurchaseArguments:"InvalidPurchaseArguments",
994 /** The chosen provider is not configured correctly in the admin panel */
995 InvalidPayVaultProviderSetup:"InvalidPayVaultProviderSetup",
996 /** Unable to locate the custom PartnerPay action with the given key */
997 UnknownPartnerPayAction:"UnknownPartnerPayAction",
998 /** The given type was invalid */
999 InvalidType:"InvalidType",
1000 /** The index was out of bounds from the range of acceptable values */
1001 IndexOutOfBounds:"IndexOutOfBounds",
1002 /** The given identifier does not match the expected format */
1003 InvalidIdentifier:"InvalidIdentifier",
1004 /** The given argument did not have the expected value */
1005 InvalidArgument:"InvalidArgument",
1006 /** This client has been logged out */
1007 LoggedOut:"LoggedOut",
1008 /** The given segment was invalid. */
1009 InvalidSegment:"InvalidSegment",
1010 /** Cannot access requests before Refresh() has been called. */
1011 GameRequestsNotLoaded:"GameRequestsNotLoaded",
1012 /** Cannot access achievements before Refresh() has been called. */
1013 AchievementsNotLoaded:"AchievementsNotLoaded",
1014 /** Cannot find the achievement with the specified id. */
1015 UnknownAchievement:"UnknownAchievement",
1016 /** Cannot access notification endpoints before Refresh() has been called. */
1017 NotificationsNotLoaded:"NotificationsNotLoaded",
1018 /** The given notifications endpoint is invalid */
1019 InvalidNotificationsEndpoint:"InvalidNotificationsEndpoint",
1020 /** There is an issue with the network */
1021 NetworkIssue:"NetworkIssue",
1022 /** Cannot access OneScore before Refresh() has been called. */
1023 OneScoreNotLoaded:"OneScoreNotLoaded",
1024 /** The Publishing Network features are only avaliable when authenticated to PlayerIO using Publishing Network authentication. Authentication methods are managed in the connections setting of your game in the admin panel on PlayerIO. */
1025 PublishingNetworkNotAvailable:"PublishingNetworkNotAvailable",
1026 /** Cannot access profile, friends, ignored before Publishing Network has been loaded. Please refresh Publishing Network first. */
1027 PublishingNetworkNotLoaded:"PublishingNetworkNotLoaded",
1028 /** The dialog was closed by the user */
1029 DialogClosed:"DialogClosed",
1030 /** Check cookie required. */
1031 AdTrackCheckCookie:"AdTrackCheckCookie",
1032 codes:{0:"UnsupportedMethod",1:"GeneralError",2:"InternalError",3:"AccessDenied",4:"InvalidMessageFormat",5:"MissingValue",6:"GameRequired",7:"ExternalError",8:"ArgumentOutOfRange",9:"GameDisabled",10:"UnknownGame",11:"UnknownConnection",12:"InvalidAuth",13:"NoServersAvailable",14:"RoomDataTooLarge",15:"RoomAlreadyExists",16:"UnknownRoomType",17:"UnknownRoom",18:"MissingRoomId",19:"RoomIsFull",20:"NotASearchColumn",21:"QuickConnectMethodNotEnabled",22:"UnknownUser",23:"InvalidPassword",24:"InvalidRegistrationData",25:"InvalidBigDBKey",26:"BigDBObjectTooLarge",27:"BigDBObjectDoesNotExist",28:"UnknownTable",29:"UnknownIndex",30:"InvalidIndexValue",31:"NotObjectCreator",32:"KeyAlreadyUsed",33:"StaleVersion",34:"CircularReference",40:"HeartbeatFailed",41:"InvalidGameCode",50:"VaultNotLoaded",51:"UnknownPayVaultProvider",52:"DirectPurchaseNotSupportedByProvider",54:"BuyingCoinsNotSupportedByProvider",55:"NotEnoughCoins",56:"ItemNotInVault",57:"InvalidPurchaseArguments",58:"InvalidPayVaultProviderSetup",70:"UnknownPartnerPayAction",80:"InvalidType",81:"IndexOutOfBounds",82:"InvalidIdentifier",83:"InvalidArgument",84:"LoggedOut",90:"InvalidSegment",100:"GameRequestsNotLoaded",110:"AchievementsNotLoaded",111:"UnknownAchievement",120:"NotificationsNotLoaded",121:"InvalidNotificationsEndpoint",130:"NetworkIssue",131:"OneScoreNotLoaded",200:"PublishingNetworkNotAvailable",201:"PublishingNetworkNotLoaded",301:"DialogClosed",302:"AdTrackCheckCookie"}
1033};
1034
1035(function () {
1036 /**
1037 * @class
1038 * An instance of this class is returned after successfully authenticating a user to PlayerIO.
1039 * It contains the id of the current user, and methods for making all API calls on behalf of that user.
1040 * @example Authenticate and create a BigDB Object
1041 * <listing>
1042 * PlayerIO.authenticate(
1043 * "[Enter your game id here]",
1044 * "public", //Default Basic connection
1045 * { userId:'user-id' }, //Basic auth only requires a user id
1046 * {}, //Optional PlayerInsight segments
1047 * function(client) {
1048 * //The user was successfully authenticated, we can now access the various PlayerIO services:
1049 * var obj = { name:"Charlie", age:20 };
1050 * client.bigDB.createObject("Users", client.connectUserId, obj, function(dbObj) {
1051 * dbObj.location = 'Paris';
1052 * dbObj.save();
1053 * });
1054 * },
1055 * function(error){
1056 * //Something went wrong while authenticating.
1057 * console.log(error);
1058 * }
1059 * );
1060 * </listing>
1061 */
1062 _pio.client = function (channel, gameId, gameFsRedirectMap, userId) {
1063 /**
1064 * User id of the currently connected user
1065 * @type string
1066 */
1067 this.connectUserId = userId;
1068
1069 /**
1070 * The game id of the client
1071 * @type string
1072 */
1073 this.gameId = gameId;
1074
1075 /**
1076 * The GameFS service
1077 * @type gameFS
1078 */
1079 this.gameFS = new _pio.gameFS(gameId, gameFsRedirectMap);
1080
1081 /**
1082 * The ErrorLog service
1083 * @type errorLog
1084 */
1085 this.errorLog = new _pio.errorLog(channel);
1086
1087 /**
1088 * The PayVault service
1089 * @type payVault
1090 */
1091 this.payVault = new _pio.payVault(channel);
1092
1093 /**
1094 * The BigDB service
1095 * @type bigDB
1096 */
1097 this.bigDB = new _pio.bigDB(channel);
1098
1099 /**
1100 * The Multiplayer service
1101 * @type multiplayer
1102 */
1103 this.multiplayer = new _pio.multiplayer(channel);
1104
1105 /**
1106 * The GameRequests service
1107 * @type gameRequests
1108 */
1109 this.gameRequests = new _pio.gameRequests(channel);
1110
1111 /**
1112 * The Achievements service
1113 * @type achievements
1114 */
1115 this.achievements = new _pio.achievements(channel);
1116
1117 /**
1118 * The PlayerInsight service
1119 * @type playerInsight
1120 */
1121 this.playerInsight = new _pio.playerInsight(channel);
1122
1123 /**
1124 * The OneScore service
1125 * @type oneScore
1126 */
1127 this.oneScore = new _pio.oneScore(channel);
1128
1129 /**
1130 * The Notifications service
1131 * @type notifications
1132 */
1133 this.notifications = new _pio.notifications(channel);
1134
1135 /**
1136 * The PlayerIO Publishing Network service
1137 * @type publishingNetwork
1138 */
1139 this.publishingNetwork = new _pio.publishingNetwork(channel, this.connectUserId);
1140 }
1141})();
1142(function () {
1143 var maps = {}
1144 /**
1145 * @class The GameFS service. This class is used to get an absolute URL for assets you have stored in GameFS.
1146 * @example Example of how to request the file game.swf from your games GameFS via PlayerIO
1147 * <listing>
1148 * var url = PlayerIO.gameFS("[Enter your game id here]").getURL("game.swf");
1149 * </listing>
1150 *
1151 * @example Example of how to request the file game.swf from your games GameFS via client
1152 * <listing>
1153 * var url = client.gameFS.getURL("game.swf");
1154 * </listing>
1155 */
1156 _pio.gameFS = function(gameId, redirectMap){
1157 if( typeof(redirectMap) == 'string' && redirectMap.length>0 ){
1158 var parts = (redirectMap||"").split("|");
1159 if( parts.length >= 1 ){
1160 var map = maps[gameId.toLowerCase()] = {}
1161 for(var i=0;i!=parts.length;i++){
1162 var part = parts[i];
1163 if(part =="alltoredirect" || part == "cdnmap"){
1164 map.baseUrl = parts[i+1];
1165 }else if( part == "alltoredirectsecure" || part == "cdnmapsecure" ){
1166 map.secureBaseUrl = parts[i+1];
1167 }else{
1168 map["."+part] = parts[i+1];
1169 }
1170 }
1171 }
1172 }
1173
1174 /**
1175 * Converts a GameFS path (like '/mygame.swf') into a full url, that can be downloaded over the internet.
1176 * Important! Do not save or otherwise persist (bigdb, cookies, etc) the returned url, since the url will change over time.
1177 * @param {string} path The path of the file in the GameFS, including the initial slash. Examples: '/mygame.swf' or '/characters/bob.jpg'
1178 * @param {boolean} secure If true, this method returns a secure (https) url.
1179 * @return {string} An url that can be used to download the resource over the internet.
1180 */
1181 this.getUrl = function (path, secure) {
1182 if(!path[0] == "/") {
1183 throw _pio.error("The path given to getUrl must start with a slash, like: '/myfile.swf' or '/folder/file.jpg'");
1184 }
1185 var map = maps[gameId];
1186 if( map ){
1187 return (secure ? map.secureBaseUrl : map.baseUrl) + (map["."+path] || path);
1188 }else{
1189 return (secure ? "https" : "http") + "://r.playerio.com/r/" + gameId + path;
1190 }
1191 }
1192 }
1193})();
1194(function () {
1195 /**
1196 * @class The GameRequest service. This class has methods for sending game requests to other users, and for
1197 * handling the requests received by the current user. All game request types have to be defined in the admin panel first.
1198 * @example Refresh local requests
1199 * <listing>
1200 * client.gameRequests.refresh(function() {
1201 * for (var i = 0; i != client.gameRequests.waitingRequests.length; i++) {
1202 * var request = client.gameRequests.waitingRequests[i];
1203 * console.log(request.id)
1204 * console.log(request.type)
1205 * console.log(request.senderUserId)
1206 * console.log(request.created)
1207 * console.log(request.data)
1208 * }
1209 * }, function(error) {
1210 * console.log(error);
1211 * });
1212 * </listing>
1213
1214 * @example Send a game request to another player
1215 * <listing>
1216 * client.gameRequests.send(
1217 * 'invite', //Type
1218 * { game: "poker", bonus: 5 }, //Extra data
1219 * ['userid1', 'userid2'], //Recipients
1220 * function () {
1221 * //Success
1222 * }, function(error) {
1223 * console.log(error);
1224 * }
1225 * );
1226 * </listing>
1227
1228 * @example Delete all waiting requests
1229 * <listing>
1230 * client.gameRequests.delete(client.gameRequests.waitingRequests, function () {
1231 * //Success, all waiting requests have been deleted.
1232 * }, function(error) {
1233 * console.log(error);
1234 * });
1235 * </listing>
1236 */
1237 _pio.gameRequests = function (channel) {
1238 var _playCodes = [];
1239
1240 /** The list of waiting requests for the current user. You must call refresh() first to initialize this list.
1241 * @type gameRequest[]
1242 */
1243 this.waitingRequests = "[ERROR: You tried to access gameRequests.waitingRequests before loading waiting requests. You have to call the refresh method first.]";
1244 var self = this;
1245
1246 /**
1247 * Send a GameRequest to the specified recipients.
1248 * @param {number} requestType The request type of the request to send.
1249 * @param {object} requestData Data that will be available to the recipient of the request with information about the request. Useful for passing any kind of data to the recipient.
1250 * @param {string[]} requestRecipients The recipients to send this request to.
1251 * @param {function()} successCallback Callback function that will be called when the request has been sent.
1252 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
1253 */
1254 this.send = function (requestType, requestData, requestRecipients, successCallback, errorCallback) {
1255 channel.gameRequestsSend(requestType, _pio.convertToKVArray(requestData), requestRecipients, successCallback, errorCallback, function (result) {
1256 //...
1257 });
1258 }
1259
1260 /**
1261 * Refresh the list of received requests.
1262 * @param {function()} successCallback Callback function that will be called when the refresh is complete
1263 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
1264 */
1265 this.refresh = function (successCallback, errorCallback) {
1266 channel.gameRequestsRefresh(_playCodes, successCallback, errorCallback, function (result) {
1267 self._playCodes = result.newplaycodes;
1268 self.waitingRequests = readRequests(result.requests);
1269 var foo = result.morerequestswaiting;
1270 });
1271 }
1272
1273 /**
1274 * Delete the given requests. Will also update the waitingRequests property after deletion is complete.
1275 * @param {gameRequest[]} requests The list of requests to delete.
1276 * @param {function()} successCallback Callback function that will be called when the delete has been completed.
1277 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
1278 */
1279 this.delete = function (requests, successCallback, errorCallback) {
1280 if (typeof (requests) != 'object' && !requests.length) {
1281 var e = _pio.error("The first argument to delete should be an array: client.gameRequests.delete([requests], ...)");
1282 _pio.handleError(e, errorCallback, e.code, e.message);
1283 return;
1284 }
1285
1286 var ids = [];
1287 for (var i = 0; i != requests.length; i++) {
1288 var id = requests[i].id;
1289 if (id) {
1290 ids.push(id);
1291 } else {
1292 var e = _pio.error("No GameRequest id found on item#" + i + ". You have to use requests from the gameRequests.waitingRequests array. For instance: client.gameRequests.delete(client.gameRequests.waitingRequests, ...)");
1293 _pio.handleError(e, errorCallback, e.code, e.message);
1294 return;
1295 }
1296 }
1297 channel.gameRequestsDelete(ids, successCallback, errorCallback, function (result) {
1298 self.waitingRequests = readRequests(result.requests);
1299 var foo = result.morerequestswaiting;
1300 });
1301 }
1302
1303 function readRequests(requests) {
1304 if (requests == null || requests.length == 0) { return []; }
1305 var arr = [];
1306 for (var i = 0; i != requests.length; i++) {
1307 var item = requests[i];
1308 arr.push(new _pio.gameRequest(item.id, item.type, item.senderuserid, item.created, item.data));
1309 }
1310 return arr;
1311 }
1312 }
1313
1314 /**
1315 * @class This class encapsulates all the data of a received game request.
1316 */
1317 _pio.gameRequest = function (id, type, senderUserId, created, data) {
1318 /** The id of this request.
1319 * @type string
1320 */
1321 this.id = id;
1322 /** The type of this request.
1323 * @type string
1324 */
1325 this.type = type;
1326 /** The sender user id of this request.
1327 * @type string
1328 */
1329 this.senderUserId = senderUserId;
1330 /** When this request was created.
1331 * @type Date
1332 */
1333 this.created = new Date(created);
1334 /** The custom data that was passed in when this request was created
1335 * @type object
1336 */
1337 this.data = _pio.convertFromKVArray(data);
1338 }
1339})();
1340(function () {
1341 /**
1342 * @class The ErrorLog service. This class has methods for writing custom errors to your game's error log.
1343 * You can browse your game's error log in the PlayerIO admin panel.
1344 * @example Writing an error to the error log
1345 * <listing>
1346 * client.errorLog.writeError(
1347 * "Could not perform action",
1348 * "time: "+(new Date()).getTime(),
1349 * err.stack,
1350 * {name:'john'}
1351 * );
1352 * </listing>
1353 */
1354 _pio.errorLog = function (channel) {
1355 /**
1356 * Writes an entry to the error log
1357 * @param {string} error A short string describing the error without details. Example 'Object not set to instance of an object'
1358 * @param {string} details The message describing the error in detail. Example 'couldn't find the user 'bob' in the current game'
1359 * @param {string} stacktrace The stacktrace (if available) of the error
1360 * @param {object} extraData Any extra data you'd like to associate with the error log entry. Example: {score:200, level:'skyland'}
1361 */
1362 this.writeError = function(error, details, stacktrace, extradata){
1363 channel.writeError("Javascript", error, details, stacktrace, _pio.convertToKVArray(extradata));
1364 }
1365 }
1366})();
1367
1368(function () {
1369 /**
1370 * @class The QuickConnect service. This class is used for support methods for users created and authenticated
1371 * through the Simple Users feature.
1372 * @example Here's an example on how to fetch a captcha for display, and submitting it when registering a Simple User.
1373 * <listing>
1374 * //Fetch a captcha
1375 * PlayerIO.quickConnect.simpleGetCaptcha(
1376 * "[your-game-id-here]",
1377 * 200, //Image width
1378 * 100, //Image height
1379 * function (captcha) {
1380 * var url = captcha.captchaImageUrl; //Display this in the user registration form
1381 * var key = captcha.captchaKey; //Save this for later
1382 * }, function (error) {
1383 * console.log(error);
1384 * }
1385 * );
1386 *
1387 *
1388 * //After the user has submitted the registration form, register a new Simple User:
1389 * PlayerIO.authenticate(
1390 * "[Enter your game id here]",
1391 * "public", //A connection with the authentication type SimpleUsers
1392 * {
1393 * register: "true",
1394 * username: "[The username]",
1395 * password: "[The password]",
1396 * email: "[The email address]",
1397 * captchaKey: key, //The captcha key we got earlier
1398 * captchaValue:"[The captcha value]", //What the user entered
1399 * },
1400 * {},
1401 * function (client) {
1402 * //Success!
1403 * //The user is now registered and connected.
1404 * },
1405 * function (error) {
1406 * //Error registering.
1407 * //Check error.message to find out in what way it failed,
1408 * //if any registration data was missing or invalid, or if
1409 * //the entered captcha value didn't match the captcha image.
1410 * }
1411 * );
1412 * </listing>
1413 */
1414 _pio.quickConnect = function () {
1415 /**
1416 * Creates a Captcha image and key, to be used for registrations where the added security of Captcha is required.
1417 * @param {string} gameId The game id of the game you wish to make a captcha for. This value can be found in the admin panel.
1418 * @param {number} width The width of the Captcha image.
1419 * @param {number} height The height of the Captcha image.
1420 * @param {function(simpleGetCaptchaOutput)} successCallback Callback function that will be called with the captcha information
1421 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
1422 */
1423 this.simpleGetCaptcha = function (gameId, width, height, successCallback, errorCallback) {
1424 var channel = new _pio.channel();
1425 channel.simpleGetCaptcha(gameId, width, height, successCallback, errorCallback, function (result) {
1426 return new _pio.simpleGetCaptchaOutput(result.captchakey, result.captchaimageurl);
1427 })
1428 }
1429
1430 /**
1431 * Initiates the password recovery process for a user by sending him an email. The user must have supplied an email address during registration.
1432 * @param {string} gameId The game id of the game the user is registered in.
1433 * @param {string} usernameOrEmail The username or email address of the user that wishes to recover his password.
1434 * @param {function()} successCallback Callback function that will be called when the recovery e-mail has been sent
1435 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
1436 */
1437 this.simpleRecoverPassword = function (gameId, usernameOrEmail, successCallback, errorCallback) {
1438 var channel = new _pio.channel();
1439 channel.simpleRecoverPassword(gameId, usernameOrEmail, successCallback, errorCallback, function (result) {})
1440 }
1441 }
1442
1443 /**
1444 * @class Captcha information for the QuickConnect simple users feature.
1445 */
1446 _pio.simpleGetCaptchaOutput = function (captchaKey, captchaImageUrl) {
1447 /** The key for this captcha image. This value must be kept and sent to the PlayerIO.authenticate() method along with the input from the user.
1448 * @type string
1449 */
1450 this.captchaKey = captchaKey;
1451
1452 /** An url for the captcha image. You must show the image to the user, and ask what text is shown in the image
1453 * @type string
1454 */
1455 this.captchaImageUrl = captchaImageUrl;
1456 }
1457
1458 PlayerIO.quickConnect = new _pio.quickConnect();
1459})();
1460(function () {
1461 /**
1462 * @class The PayVault service.
1463 * Instances of this class represent a specific user's Vault, and contains methods and properties both for inspecting and manipulating the contents.
1464 * All properties and methods that inspect the Vault requires that it is up-to-date first. This can be achieved explicitly by calling the refresh() method
1465 * or implicitly by calling any method which modifies the Vault such as credit() or debit().
1466 * @example Here is how to read the Coins balance:
1467 * <listing>
1468 * client.payVault.refresh(function() {
1469 * var coins = client.payVault.coins;
1470 * }, function(error) { console.log(error); });
1471 * </listing>
1472 *
1473 * @example This is how to list all items in a vault:
1474 * <listing>
1475 * client.payVault.refresh(function() {
1476 * for (var i = 0; i != client.payVault.items.length; i++) {
1477 * var item = client.payVault.items[i];
1478 * console.log(item.itemkey);
1479 * console.log(item.purchaseDate);
1480 * console.log(item.id);
1481 * }
1482 * }, function(error) { console.log(error); });
1483 * </listing>
1484 *
1485 * @example This is how you check if an item exists:
1486 * <listing>
1487 * client.payVault.refresh(function() {
1488 * if (client.payVault.has('simplecar')) {
1489 * //Vault contains at least one item of type 'simplecar'.
1490 * }
1491 * }, function(error) { console.log(error); });
1492 * </listing>
1493 *
1494 * @example Credit and Debit can be used like this:
1495 * <listing>
1496 * client.payVault.credit(100, 'New player bonus', function() {
1497 * var newcoins = client.payVault.coins;
1498 * //Show new amount to user...
1499 * }, function(error) { console.log(error); });
1500 *
1501 * client.payVault.debit(10, 'Race starting fee', function() {
1502 * //Let player start race...
1503 * }, function(error) {
1504 * //Something went wrong, or the user doesn't have enough coins in his Vault.
1505 * console.log(error);
1506 * });
1507 * </listing>
1508 *
1509 * @example Buying items with Coins is really easy. This requires that you have created an item in the PayVaultItems table in BigDB with the key "speedboost", and a property "PriceCoins" containing the price.
1510 * <listing>
1511 * client.payVault.buy(true, [{itemkey:'speedboost'}], function() {
1512 * var boosts = client.payVault.count('speedboost');
1513 * //Show new number of boosts to user.
1514 * }, function(error) {
1515 * //Something went wrong, or the user couldn't afford the item.
1516 * console.log(error);
1517 * });
1518 * </listing>
1519 *
1520 * @example And here's how to consume an item:
1521 * <listing>
1522 * client.payVault.refresh(function() {
1523 * var boost = client.payVault.first('speedboost');
1524 * client.payVault.consume([boost], function(){
1525 * //Boost the player's car
1526 * })
1527 * }, function(error) { console.log(error); });
1528 * </listing>
1529 *
1530 * @example When it's time for a user to add more Coins, you can do it like this:
1531 * <listing>
1532 * client.payVault.getBuyCoinsInfo(
1533 * 'paypal', //PayVault provider
1534 * {
1535 * coinamount: 1000, //Provider-specific arguments
1536 * currency:'USD',
1537 * item_name:'1000 Coins'
1538 * },
1539 * function(result){
1540 * var url = result.paypalurl; //Provider-specific result
1541 * //Show url to user
1542 * }, function(error) {
1543 * console.log(error);
1544 * }
1545 * );
1546 * </listing>
1547 *
1548 * @example And this is how to let the user buy an item directly. This requires that you have created an item in the PayVaultItems table in BigDB with the key "supercar", and a property "PriceUSD" containing the price.
1549 * <listing>
1550 * client.payVault.getBuyDirectInfo(
1551 * 'paypal', //PayVault provider
1552 * {
1553 * currency:'USD', //Provider-specific arguments
1554 * item_name:'Red Supercar'
1555 * },
1556 * [{ //Array of items to buy.
1557 * itemkey:'supercar', //Item key
1558 * color:'red' //Optional payload
1559 * }],
1560 * function(result){ //Provider-specific result
1561 * var url = result.paypalurl;
1562 * // show url to player...
1563 * }, function(error) {
1564 * console.log(error);
1565 * }
1566 * );
1567 * </listing>
1568 *
1569 * @example Finally, there are methods for retrieving the payment history of a user:
1570 * <listing>
1571 * client.payVault.readHistory(1, 10, function(historyEntries){
1572 * if (historyEntries.length > 0) {
1573 * var lastprice = historyEntries[0].providerPrice;
1574 * }
1575 * }, function(error) { console.log(error); });
1576 * </listing>
1577 */
1578 _pio.payVault = function (channel) {
1579 var currentVersion = null;
1580
1581 /** The number of coins in this user's Vault. You must call refresh() first to initialize this value.
1582 * @type Number
1583 */
1584 this.coins = "[ERROR: you tried to access payVault.coins before the vault was loaded. You have to refresh the vault before the .coins property is set to the right value]";
1585 /** The list of items in this user's Vault. You must call refresh() first to initialize this value.
1586 * @type Number
1587 */
1588 this.items = "[ERROR: you tried to access payVault.items before the vault was loaded. You have to refresh the vault before the .items property is set to the right value]";
1589
1590 /**
1591 * This method checks if the Vault contains at least one item of the given itemKey. This method can only be called on an up-to-date vault.
1592 * @param {string} itemKey The itemKey to check for.
1593 * @return {boolean} True if the user has at least one item of the given type (itemKey).
1594 */
1595 this.has = function (itemKey) {
1596 if (currentVersion == null) { throw new PlayerIOError(PlayerIOErrorCode.VaultNotLoaded, "Cannot access items before vault has been loaded. Please refresh the vault first"); }
1597 for (var i = 0; i != this.items.length; i++) {
1598 if (this.items[i].itemKey == itemKey) {
1599 return true;
1600 }
1601 }
1602 return false;
1603 }
1604
1605 /**
1606 * Returns the first item of the given itemKey from this Vault. This method can only be called on an up-to-date vault.
1607 * @param {string} itemKey The itemKey of the item to get.
1608 * @return {number} A VaultItem if one was found, or null if not.
1609 */
1610 this.first = function (itemKey) {
1611 if (currentVersion == null) { throw new PlayerIOError(PlayerIOErrorCode.VaultNotLoaded, "Cannot access items before vault has been loaded. Please refresh the vault first"); }
1612 for (var i = 0; i != this.items.length; i++) {
1613 if (this.items[i].itemKey == itemKey) {
1614 return this.items[i];
1615 }
1616 }
1617 return null;
1618 }
1619 /**
1620 * Returns the number of items of a given itemKey is in this Vault. This method can only be called on an up-to-date vault.
1621 * @param string itemKey The itemKey of the items to count.
1622 * @return number The number of items of the given type that the user has in the vault.
1623 */
1624 this.count = function (itemKey) {
1625 if (currentVersion == null) { throw new PlayerIOError(PlayerIOErrorCode.VaultNotLoaded, "Cannot access items before vault has been loaded. Please refresh the vault first"); }
1626 var result = 0;
1627 for (var i = 0; i != this.items.length; i++) {
1628 if (this.items[i].itemKey == itemKey) {
1629 result++;
1630 }
1631 }
1632 return result;
1633 }
1634
1635 /**
1636 * Refreshes this Vault, making sure the Items and Coins are up-to-date.
1637 * @param {function()} successCallback Callback function that will be called when the refresh is complete
1638 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
1639 */
1640 this.refresh = function (successCallback, errorCallback) {
1641 channel.payVaultRefresh(currentVersion, null, successCallback, errorCallback, function (result) {
1642 readContent(result.vaultcontents);
1643 });
1644 }
1645
1646 /**
1647 * Loads a page of entries from this Vaults history, in reverse chronological order, i.e. newest first.
1648 * @param {number} page The page of entries to load. The first page has number 0.
1649 * @param {number} pageSize The number of entries per page.
1650 * @param {function(payVaultHistoryEntries)} successCallback Callback function that will be called with the history entries found
1651 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
1652 */
1653 this.readHistory = function (page, pageSize, successCallback, errorCallback) {
1654 channel.payVaultReadHistory(page, pageSize, null, successCallback, errorCallback, function (result) {
1655 var arr = [];
1656 for (var i = 0; i != result.entries.length; i++) {
1657 var item = result.entries[i];
1658 arr.push(new _pio.payVaultHistoryEntry(item.type, item.amount, item.timestamp, item.itemkeys || [], item.reason, item.providertransactionid, item.providerprice))
1659 }
1660 return arr;
1661 })
1662 }
1663
1664 /**
1665 * Give coins to this Vault
1666 * @param {number} coinAmount The amount of coins to give.
1667 * @param {string} reason Your reason for giving the coins to this user. This will show up in the vault history, and in the PayVault admin panel for this user.
1668 * @param {function()} successCallback Callback function that will be called when the credit has been completed. You don't need to call refresh after this call.
1669 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
1670 */
1671 this.credit = function (coinAmount, reason, successCallback, errorCallback) {
1672 channel.payVaultCredit(coinAmount, reason, null, successCallback, errorCallback, function (result) {
1673 readContent(result.vaultcontents);
1674 })
1675 }
1676
1677 /**
1678 * Take coins from this Vault
1679 * @param {number} coinAmount The amount of coins to take.
1680 * @param {string} reason Your reason for taking the coins from this user. This will show up in the vault history, and in the PayVault admin panel for this user.
1681 * @param {function()} successCallback Callback function that will be called when the credit has been completed. You don't need to call refresh after this call.
1682 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
1683 */
1684 this.debit = function (coinAmount, reason, successCallback, errorCallback) {
1685 channel.payVaultDebit(coinAmount, reason, null, successCallback, errorCallback, function (result) {
1686 readContent(result.vaultcontents);
1687 })
1688 }
1689
1690 /**
1691 * Consume items in this Vault. This will cause them to be removed, but this action will not show up in the vault history.
1692 * @param {vaultItem[]} items The VaultItems to use from the users vault - this should be instances of items in this Vault.
1693 * @param {function()} successCallback Callback function that will be called when the item(s) has been consumed. You don't need to call refresh after this call.
1694 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
1695 */
1696 this.consume = function (items, successCallback, errorCallback) {
1697 if (typeof (items) != 'object' && !items.length) {
1698 var e = _pio.error("The first argument to consume should be an array: client.payVault.consume([item], ...)");
1699 _pio.handleError(e, errorCallback, e.code, e.message)
1700 return;
1701 }
1702
1703 var ids = [];
1704 for (var i = 0; i != items.length; i++) {
1705 var id = items[i].id;
1706 if (id) {
1707 ids.push(id);
1708 } else {
1709 var e = _pio.error("No PayVault item id found on item#" + i + ". You have to use items from the payVault.items array. For instance: client.payVault.consume([client.payVault.first('sportscar')], ...)")
1710 _pio.handleError(e, errorCallback, e.code, e.message)
1711 return;
1712 }
1713 }
1714
1715 channel.payVaultConsume(ids, null, successCallback, errorCallback, function (result) {
1716 readContent(result.vaultcontents);
1717 })
1718 }
1719
1720
1721 /**
1722 * Buy items with Coins.
1723 * @param {object[]} items A list of items to buy. Each item must have a property called 'itemkey' with the item key. Any additional properties will be converted to item payload.
1724 * @param {boolean} storeItems Whether or not to store the items in the vault after purchase
1725 * @param {function()} successCallback Callback function that will be called when the item(s) have been bought and added to the vault. You don't need to call refresh after this call.
1726 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
1727 */
1728 this.buy = function (items, storeItems, successCallback, errorCallback) {
1729 channel.payVaultBuy(_pio.convertBuyItems(items), storeItems, null, successCallback, errorCallback, function (result) {
1730 readContent(result.vaultcontents);
1731 })
1732 }
1733
1734 /**
1735 * Give the user items without taking any of his coins from the vault.
1736 * @param {object[]} items A list of items to give. Each item must have a property called 'itemkey' with the item key. Any additional properties will be converted to item payload.
1737 * @param {function()} successCallback Callback function that will be called when the item(s) have been added to the vault. You don't need to call refresh after this call.
1738 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
1739 */
1740 this.give = function (items, successCallback, errorCallback) {
1741 channel.payVaultGive(_pio.convertBuyItems(items), null, successCallback, errorCallback, function (result) {
1742 readContent(result.vaultcontents);
1743 })
1744 }
1745
1746 /**
1747 * Gets information about how to make a coin purchase with the specified PayVault provider.
1748 * @param {string} provider The name of the PayVault provider to use for the coin purchase.
1749 * @param {object} purchaseArguments Any additional information that will be given to the PayVault provider to configure this purchase.
1750 * @param {function(object)} successCallback Callback function that will be called with provider specifics about how to complete the purchase.
1751 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
1752 */
1753 this.getBuyCoinsInfo = function (provider, purchaseArguments, successCallback, errorCallback) {
1754 channel.payVaultPaymentInfo(provider, _pio.convertToKVArray(purchaseArguments), null, successCallback, errorCallback, function (result) {
1755 return _pio.convertFromKVArray(result.providerarguments)
1756 })
1757 }
1758
1759 /**
1760 * Gets information about how to make a direct item purchase with the specified PayVault provider.
1761 * @param {string} provider The name of the PayVault provider to use for the item purchase.
1762 * @param {object} purchaseArguments Any additional information that will be given to the PayVault provider to configure this purchase.
1763 * @param {object[]} items A list of items to buy. Each item must have a property called 'itemkey' with the item key. Any additional properties will be converted to item payload.
1764 * @param {function(object)} successCallback Callback function that will be called with provider specifics about how to complete the purchase.
1765 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
1766 */
1767 this.getBuyDirectInfo = function (provider, purchaseArguments, items, successCallback, errorCallback) {
1768 channel.payVaultPaymentInfo(provider, _pio.convertToKVArray(purchaseArguments), _pio.convertBuyItems(items), successCallback, errorCallback, function (result) {
1769 return _pio.convertFromKVArray(result.providerarguments)
1770 })
1771 }
1772
1773 var self = this;
1774 function readContent(content) {
1775 if (content != null) {
1776 currentVersion = content.version;
1777 self.coins = content.coins || 0;
1778 self.items = [];
1779 if (content.items && content.items.length) {
1780 for (var i = 0; i != content.items.length; i++) {
1781 var item = content.items[i];
1782 var obj = self.items[i] = new _pio.vaultItem(item.id, item.itemkey, (new Date()).setTime(item.purchasedate))
1783
1784 _pio.bigDBDeserialize(item.properties, obj, true);
1785 }
1786 }
1787 }
1788 }
1789
1790 }
1791
1792 _pio.convertBuyItems = function(items) {
1793 if (items == null) return [];
1794 var results = [];
1795 for (var i = 0; i != items.length; i++) {
1796 var itemKey = items[i].itemkey
1797
1798 if (!itemKey) {
1799 throw _pio.error("You have to specify an itemkey for the payvault item. Example: {itemkey:'car'}")
1800 }
1801
1802 results.push({
1803 itemkey: itemKey,
1804 payload: _pio.compareForChanges({ itemkey: itemKey }, items[i], true, true)
1805 })
1806 }
1807 return results;
1808 }
1809
1810 /**
1811 * @class This class represents an item in a user's Vault
1812 */
1813 _pio.vaultItem = function (id, itemKey, purchaseDate) {
1814 /** The unique id of this particular vault item in the user's vault
1815 * @type string
1816 */
1817 this.id = id;
1818
1819 /** The key of the underlying item in the PayVaultItems BigDB table
1820 * @type string
1821 */
1822 this.itemKey = itemKey;
1823
1824 /** The time when the vault item was originally purchased
1825 * @type Date
1826 */
1827 this.purchaseDate = purchaseDate;
1828 }
1829
1830 /**
1831 * @class This class represents an entry in a user's PayVault history.
1832 */
1833 _pio.payVaultHistoryEntry = function (type, amount, timestamp, itemkeys, reason, providerTransactionId, providerPrice) {
1834 /** The type of this entry, for example 'buy','credit','debit'...
1835 * @type string
1836 */
1837 this.type = type;
1838 /** The coin amount of this entry.
1839 * @type number
1840 */
1841 this.amount = amount;
1842 /** When this entry was created.
1843 * @type Date
1844 */
1845 this.timestamp = new Date().setTime(timestamp);
1846 /** The item keys related to this entry (entries with type 'buy').
1847 * @type string[]
1848 */
1849 this.itemKeys = itemkeys;
1850 /** The developer supplied reason for entries of type 'credit' and 'debit'.
1851 * @type string
1852 */
1853 this.reason = reason;
1854 /** The transaction id from the PayVault provider corresponding to this entry.
1855 * @type string
1856 */
1857 this.providerTransactionId = providerTransactionId;
1858 /** The price in real currency of this entry formatted as a human readable currency string, e.g. $10.00 USD
1859 * @type string
1860 */
1861 this.providerPrice = providerPrice;
1862 }
1863})();
1864(function () {
1865 /**
1866 * @class The BigDB service.
1867 * <p>This class is used to create, load, and delete database objects. All database objects are stored in tables and have a unique key.
1868 * You can set up tables in your admin panel, and you can also set up indexes there for when you want to load objects by properties
1869 * or ranges of properties.</p>
1870 * @example Here's how to store and update an object:
1871 * <listing>
1872 * //Make new object and set some properties
1873 * var obj = {username:"Adam",location:"London", age:20};
1874 *
1875 * //Create object in table Users with ConnectUserId as key
1876 * client.bigDB.createObject("Users", connectUserId, obj, function(dbObj) {
1877 * dbObj.location = 'Paris';
1878 * dbObj.save();
1879 * }, function(error) { console.log(error); });
1880 * </listing>
1881 * @example This is how you load an object:
1882 * <listing>
1883 * client.bigDB.load("Users", connectUserId, function(obj) {
1884 * if (obj != null) {
1885 * obj.location = 'Paris';
1886 * obj.save();
1887 * }
1888 * }, function(error) { console.log(error); });
1889 * </listing>
1890 * @example In case you always want to modify an object, you can use the LoadOrCreate method to ensure you get an object back:
1891 * <listing>
1892 * client.bigDB.loadOrCreate("Users", connectUserId, function(obj) {
1893 * if(!obj.username) {
1894 * dbObj.username = 'Charlie';
1895 * dbObj.age = 20;
1896 * }
1897 * obj.location = 'London';
1898 * obj.save();
1899 * }, function(error) { console.log(error); });
1900 * </listing>
1901 * @example
1902 * <p>
1903 * BigDB also supports indexes for retrieving objects by a specific property, a range of properties,
1904 * or to sort objects by properties. Indexes need to be set up in the admin panel for each table,
1905 * each index needs a name, and a list of properties, and for each property you also need to specify a
1906 * sort order.
1907 * </p>
1908 * <p>Imagine that we have objects that look like this:</p>
1909 * <listing>
1910 * {
1911 * username:"Adam",
1912 * created:2010-05-12 15:28,
1913 * location:"London",
1914 * age:20,
1915 * }
1916 * </listing>
1917 * <p>That we have defined an index called "ByUsername" that looks like this:
1918 * <ul>
1919 * <li>{Property:"username", Type:String, Order:Ascending}</li>
1920 * </ul>
1921 * </p>
1922 * <p>And an index called "ByCreated" that looks like this:
1923 * <ul>
1924 * <li>{Property:"created", Type:Datetime, Order:Descending}</li>
1925 * </ul>
1926 *</p>
1927 * <p>Then we can do lookups like these:</p>
1928 * <listing>
1929 * //Get the object where username="Adam"
1930 * client.bigDB.loadSingle("Users", "ByUsername", ["Adam"], function(obj) {
1931 * //...
1932 * }, function(error) { console.log(error); });
1933 *
1934 * //Get all users with usernames between "Adam" and "Charlie".
1935 * //This would retrieve users named "Adamsson" and "Barney",
1936 * //but not users named "Abel" or "Charlotte".
1937 * client.bigDB.loadRange("Users", "ByUsername", null, "Adam", "Charlie", 100, function(objects) {
1938 * //objects is an array of found objects...
1939 * }, function(error) { console.log(error); });
1940 *
1941 * //Get all users up to and including "Adam". This would retrieve
1942 * //users named "Aaron" and "Ackerman", but not "Adamsson" or "Barney".
1943 * client.bigDB.loadRange("Users", "ByUsername", null, null, "Adam", 100, function(objects) {
1944 * //objects is an array of found objects...
1945 * }, function(error) { console.log(error); });
1946 *
1947 * //Get all users from "Xerxes". This would retrieve users named
1948 * //"Yngwie" and "Zed", but not "Charlie" or "Xantippa".
1949 * client.bigDB.loadRange("Users", "ByUsername", null, "Xerxes", null, 100, function(objects) {
1950 * //objects is an array of found objects...
1951 * }, function(error) { console.log(error); });
1952 *
1953 * //Retrieve the ten first objects by the ByCreated index.
1954 * //Since that index is sorted in descending order, this will actually
1955 * //retrieve the 10 latest created users.
1956 * client.bigDB.loadRange("Users", "ByCreated", null, null, null, 10, function(objects) {
1957 * //objects is an array of found objects...
1958 * }, function(error) { console.log(error); });
1959 *
1960 * //Get the 10 latest users that were created more than 7 days ago.
1961 * var weekago = new Date();
1962 * weekago.setDate(weekago.getDate() - 7);
1963 *
1964 * client.bigDB.loadRange("Users", "ByCreated", null, weekago, null, 10, function(objects) {
1965 * //objects is an array of found objects...
1966 * }, function(error) { console.log(error); });
1967 * </listing>
1968 * @example
1969 * <p>
1970 * BigDB also supports compound indexes, that is indexes with more than one property. Given our example object above, we can create an index called "ByLocationAgeCreated" that looks like this:
1971 * <ul>
1972 * <li>{Property:"location", Type:String, Order:Ascending}</li>
1973 * <li>{Property:"age", Type:Integer, Order:Ascending}</li>
1974 * <li>{Property:"created", Type:Datetime, Order:Descending}</li>
1975 * </ul>
1976 * </p>
1977 * <p>
1978 * With this index, we can then lookup on either location, or location and age, or location and age and created. If we use more than one property in the lookup, we can only specify the range for the last one, the preceding ones have to be fixed and are sent in via the path parameter.
1979 * </p>
1980 * <listing>
1981 * //Load all users where location is "London"
1982 * client.bigDB.loadRange("Users", "ByLocationAgeCreated", null, "London", "London", 100, function(objects) {
1983 * //objects is an array of found objects...
1984 * }, function(error) { console.log(error); });
1985 *
1986 * //Load all users from London between 20 and 30 years of age
1987 * client.bigDB.loadRange("Users", "ByLocationAgeCreated", ["London"], 20, 30, 100, function(objects) {
1988 * //objects is an array of found objects...
1989 * }, function(error) { console.log(error); });
1990 *
1991 * //Load all users from London that are above 50
1992 * client.bigDB.loadRange("Users", "ByLocationAgeCreated", ["London"], 50, null, 100, function(objects) {
1993 * //objects is an array of found objects...
1994 * }, function(error) { console.log(error); });
1995 *
1996 * //Load all users from Paris that are 30 years old, and were created in April
1997 * client.bigDB.loadRange("Users", "ByLocationAgeCreated", ["Paris", 30], new Date(2010, 4, 1), new Date(2010, 4, 30), 100, function(objects) {
1998 * //objects is an array of found objects...
1999 * }, function(error) { console.log(error); });
2000 *
2001 * //Load the 10 latest 20-year old users from Amsterdam
2002 * client.bigDB.loadRange("Users", "ByLocationAgeCreated", ["Amsterdam", 20], null, null, 10, function(objects) {
2003 * //objects is an array of found objects...
2004 * }, function(error) { console.log(error); });
2005 * </listing>
2006 *
2007 * @example
2008 * <p>Finally, deleting objects is as easy as calling the DeleteKeys method, or DeleteRange if you want to delete by an index.</p>
2009 * <listing>
2010 * //Delete current Users object.
2011 * client.bigDB.deleteKeys("Users", connectUserId, function(){
2012 * //success
2013 * }, function(error) { console.log(error); });
2014 *
2015 * //Delete all objects with usernames between "Adam" and "Charlie"
2016 * client.bigDB.deleteRange("Users", "ByUsername", null, "Adam", "Charlie", function(){
2017 * //success
2018 * }, function(error) { console.log(error); });
2019 *
2020 * //Delete all objects with created older than 1st Jan 2011
2021 * client.bigDB.deleteRange("Users", "ByUsername", null, new Date(2011, 1, 1), null, function(){
2022 * //success
2023 * }, function(error) { console.log(error); });
2024 * </listing>
2025 */
2026 _pio.bigDB = function (channel) {
2027 var self = this;
2028 var queuedSaves = [];
2029 /**
2030 * Creates a new database object in the given table with the specified key. If no key is specified (null), the object will receive an autogenerated id.
2031 * @param {string} table The name of the table to create the database object in.
2032 * @param {string} key The key to assign to the database object.
2033 * @param {object} obj The database object to create in the table.
2034 * @param {function(databaseobject)} successCallback Callback function that will be called with the newly created databaseobject instance
2035 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2036 */
2037 this.createObject = function (table, key, obj, successCallback, errorCallback) {
2038 var properties = _pio.compareForChanges({}, obj || {}, true, true);
2039 channel.createObjects([{ key: key, table: table, properties: properties}], false, successCallback, errorCallback, function (results) {
2040 if (results.objects.length == 1) {
2041 return new _pio.databaseobject(self, table, results.objects[0].key, results.objects[0].version, false, properties);
2042 } else {
2043 throw new PlayerIOError(PlayerIOErrorCode.GeneralError, "Error creating object");
2044 }
2045 })
2046 }
2047
2048 /**
2049 * Loads the database object corresponding to the player from the PlayerObjects table.
2050 * @param {function(databaseobject)} successCallback Callback function that will be called with the databaseobject
2051 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2052 */
2053 this.loadMyPlayerObject = function (successCallback, errorCallback) {
2054 channel.loadMyPlayerObject(successCallback, errorCallback, function (result) {
2055 return new _pio.databaseobject(self, "PlayerObjects", result.playerobject.key, result.playerobject.version, true, result.playerobject.properties);
2056 })
2057 }
2058
2059 /**
2060 * Load the database object with the given key from the given table.
2061 * @param {string} table The table to load the database object from.
2062 * @param {string} key The key of the database object to load.
2063 * @param {function(databaseobject)} successCallback Callback function that will be called with the databaseobject found, or null if no object exists for the given key
2064 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2065 */
2066 this.load = function (table, key, successCallback, errorCallback) {
2067 channel.loadObjects([{ table: table, keys: [key]}], successCallback, errorCallback, function (results) {
2068 if (results.objects.length == 1) {
2069 return results.objects[0] == null ? null : new _pio.databaseobject(self, table, results.objects[0].key, results.objects[0].version, false, results.objects[0].properties);
2070 } else {
2071 throw new PlayerIOError(PlayerIOErrorCode.GeneralError, "Error loading object");
2072 }
2073 })
2074 }
2075
2076 /**
2077 * Loads the database objects with the given keys from the given table.
2078 * @param {string} table The table to load the database objects from.
2079 * @param {string[]} keys The keys of the database objects to load.
2080 * @param {function(databaseobjects)} successCallback Callback function that will be called with an array of databaseobjects found, or an empty array if no objects exist for the given keys
2081 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2082 */
2083 this.loadKeys = function (table, keys, successCallback, errorCallback) {
2084 channel.loadObjects([{ table: table, keys: keys}], successCallback, errorCallback, function (results) {
2085 var ret = [];
2086 for (var i = 0; i != results.objects.length; i++) {
2087 var obj = results.objects[i];
2088 ret[i] = obj == null ? null : new _pio.databaseobject(self, table, obj.key, obj.version, false, obj.properties);
2089 }
2090 return ret;
2091 })
2092 }
2093
2094 /**
2095 * Loads or creates the database object with the given key from the given table.
2096 * @param {string} table The table from which to load or create the database object
2097 * @param {string} key The key of the database object to load or create
2098 * @param {function(databaseobject)} successCallback Callback function that will be called with the databaseobject found or created
2099 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2100 */
2101 this.loadOrCreate = function (table, key, successCallback, errorCallback) {
2102 channel.createObjects([{ key: key, table: table, properties: []}], true, successCallback, errorCallback, function (results) {
2103 if (results.objects.length == 1) {
2104 return new _pio.databaseobject(self, table, results.objects[0].key, results.objects[0].version, false, results.objects[0].properties);
2105 } else {
2106 throw new PlayerIOError(PlayerIOErrorCode.GeneralError, "Error creating object");
2107 }
2108 })
2109 }
2110
2111 /**
2112 * Delete a set of database objects from a table.
2113 * @param {string} table The table to delete the database objects from.
2114 * @param {string[]} keys The keys of the database objects to delete.
2115 * @param {function()} successCallback Callback function that will be called when the objects have been successfully deleted
2116 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2117 */
2118 this.deleteKeys = function (table, keys, successCallback, errorCallback) {
2119 channel.deleteObjects([{ table: table, keys: keys}], successCallback, errorCallback, function (result) { return null })
2120 }
2121 /**
2122 * Loads or creates database objects with the given keys from the given table. New objects are created if there are no existing objects for the given keys.
2123 * @param {string} table The table from which to load or create the database objects.
2124 * @param {string[]} keys The keys of the database objects to load or create.
2125 * @param {function(databaseobjects)} successCallback Callback function that will be called with an array of databaseobjects found or created
2126 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2127 */
2128 this.loadKeysOrCreate = function (table, keys, successCallback, errorCallback) {
2129 var objectIds = [];
2130 for (var i = 0; i != keys.length; i++) {
2131 objectIds.push({ key: keys[i], table: table, properties: [] })
2132 }
2133
2134 channel.createObjects(objectIds, true, successCallback, errorCallback, function (results) {
2135 var ret = [];
2136 for (var i = 0; i != results.objects.length; i++) {
2137 var obj = results.objects[i];
2138 ret[i] = new _pio.databaseobject(self, table, obj.key, obj.version, false, obj.properties);
2139 }
2140 return ret;
2141 })
2142 }
2143
2144 /**
2145 * Load a database object from a table using the specified index.
2146 * @param {string} table The table to load the database object from.
2147 * @param {string} index The name of the index to query for the database object.
2148 * @param {array} indexValue An array of objects of the same types as the index properties, specifying which object to load.
2149 * @param {function(databaseobject)} successCallback Callback function that will be called with the databaseobject found, or null if no object was found
2150 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2151 */
2152 this.loadSingle = function (table, index, indexValue, successCallback, errorCallback) {
2153 channel.loadMatchingObjects(table, index, getIndexValue(indexValue), 1, successCallback, errorCallback, function (results) {
2154 return results.objects.length == 0 ? null : new _pio.databaseobject(self, table, results.objects[0].key, results.objects[0].version, false, results.objects[0].properties);
2155 })
2156 }
2157
2158 /**
2159 * Load a range of database objects from a table using the specified index.
2160 * @param {string} table The table to load the database objects from.
2161 * @param {string} index The name of the index to query for the database objects.
2162 * @param {array} indexPath Where in the index to start the range search: An array of objects of the same types as the index properties, specifying where in the index to start loading database objects from. For instance, in the index [Mode,Map,Score] you might use new object[]{"expert","skyland"} as the indexPath and use the start and stop arguments to determine the range of scores you wish to return. IndexPath can be set to null if there is only one property in the index.
2163 * @param {object} start Where to start the range search. For instance, if the index is [Mode,Map,Score] and indexPath is ["expert","skyland"], then start defines the minimum score to include in the results.
2164 * @param {object} stop Where to stop the range search. For instance, if the index is [Mode,Map,Score] and indexPath is ["expert","skyland"], then stop defines the maximum score to include in the results.
2165 * @param {int} limit The max amount of objects to return.
2166 * @param {function(databaseobjects)} successCallback Callback function that will be called with an array of databaseobjects found, or an empty array if no objects are found.
2167 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2168 */
2169 this.loadRange = function (table, index, indexPath, start, stop, limit, successCallback, errorCallback) {
2170 channel.loadIndexRange(table, index, getIndexValue(indexPath, start), getIndexValue(indexPath, stop), limit, successCallback, errorCallback, function (results) {
2171 var ret = [];
2172 for (var i = 0; i != results.objects.length; i++) {
2173 var obj = results.objects[i];
2174 ret[i] = obj == null ? null : new _pio.databaseobject(self, table, obj.key, obj.version, false, obj.properties);
2175 }
2176 return ret;
2177 })
2178 }
2179
2180 /**
2181 * Delete a range of database objects from a table using an index.
2182 * @param {string} table The table to delete the database object from.
2183 * @param {string} index The name of the index to query for the database objects to delete.
2184 * @param {array} indexPath Where in the index to start the range delete: An array of objects of the same types as the index properties, specifying where in the index to start loading database objects from. For instance, in the index [Mode,Map,Score] you might use new object[]{"expert","skyland"} as the indexPath and use the start and stop arguments to determine the range of scores you wish to delete. IndexPath can be set to null instead of an empty array.
2185 * @param {object} start Where to start the range delete. For instance, if the index is [Mode,Map,Score] and indexPath is ["expert","skyland"], then start defines the minimum score to delete.
2186 * @param {function()} successCallback Callback function that will be called when the objects have been successfully deleted
2187 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2188 */
2189 this.deleteRange = function (table, index, indexPath, start, stop, successCallback, errorCallback) {
2190 channel.deleteIndexRange(table, index, getIndexValue(indexPath, start), getIndexValue(indexPath, stop), successCallback, errorCallback, function () { });
2191 }
2192
2193 /**
2194 * Save changes to one or more database objects in one go.
2195 * @param {bool} useOptimisticLock Should the save only go through, if no other process has modified the object since it was loaded?
2196 * @param {bool} fullOverwrite Will completely overwrite the database object in BigDB with the properties in this instance, instead of just sending the changed properties to the server.
2197 * @param {array} objects The objects with changes to save.
2198 * @param {function()} successCallback Callback function that will be called when the objects have been successfully saved
2199 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2200 */
2201 this.saveChanges = function (useOptimisticLock, fullOverwrite, objects, successCallback, errorCallback) {
2202 var createIfMissing = arguments[5] == true;
2203 var changesets = [];
2204 for (var i in objects) {
2205 var obj = objects[i];
2206
2207 // verify that it's a databaseobject
2208 if (!obj.existsInDatabase || !obj.key || !obj.table || !obj._internal_ || !obj.save) {
2209 var e = _pio.error("You can only save database objects that exist in the database")
2210 _pio.handleError(e, errorCallback, e.code, e.message)
2211 return;
2212 }
2213
2214 // get changeset for object
2215 var changes = _pio.compareForChanges(fullOverwrite ? {} : obj._internal_('get-dbCurrent'), obj, true, true)
2216 if (fullOverwrite || changes.length > 0) {
2217 changesets.push({
2218 key: obj._internal_('get-key'),
2219 table: obj._internal_('get-table'),
2220 fulloverwrite: fullOverwrite,
2221 onlyifversion: useOptimisticLock ? obj._internal_('get-version') : null,
2222 changes: changes
2223 })
2224 }
2225 }
2226
2227 // queue the save
2228 queuedSaves.push({
2229 objects: objects,
2230 changesets: changesets,
2231 fullOverwrite: fullOverwrite,
2232 useOptimisticLock: useOptimisticLock,
2233 futureSaves: [],
2234 setIsSavingOnAll: function (value) {
2235 for (var i = 0; i != this.objects.length; i++) {
2236 this.objects[i]._internal_('set-is-saving', value)
2237 }
2238 },
2239 done: function () {
2240 this.setIsSavingOnAll(false);
2241 startSaves();
2242 },
2243 execute: function () {
2244 var self = this;
2245
2246 // mark as saving
2247 self.setIsSavingOnAll(true);
2248
2249 // save changes to server
2250 if (self.changesets.length > 0) {
2251 channel.saveObjectChanges(_pio.LockType.LockAll, self.changesets, createIfMissing, successCallback, function (e) {
2252 self.done();
2253 _pio.handleError(e, errorCallback, e.code, e.message)
2254 }, function (result) {
2255 for (var i = 0; i != self.objects.length; i++) {
2256 var obj = self.objects[i];
2257 obj._internal_('set-version', result.versions[i])
2258 obj._internal_('change-dbCurrent', self.changesets[i].changes)
2259
2260 // remove changes from any of the future saves we took changes from
2261 for (var x = 0; x != self.futureSaves.length; x++) {
2262 var futureSave = self.futureSaves[x];
2263 for (var o = 0; o < futureSave.objects.length; o++) {
2264 if (futureSave.objects[o] == obj) {
2265 futureSave.changesets.splice(o, 1);
2266 futureSave.objects.splice(o, 1);
2267 o--;
2268 }
2269 }
2270 }
2271 }
2272
2273 self.done();
2274 });
2275 } else {
2276 self.done();
2277 setTimeout(successCallback, 1);
2278 }
2279 }
2280 })
2281
2282 startSaves();
2283 }
2284
2285 function startSaves() {
2286 for (var s = 0; s < queuedSaves.length; s++) {
2287 var save = queuedSaves[s];
2288 var canSave = true;
2289
2290 for (var i in save.objects) {
2291 if (save.objects[i]._internal_('get-is-saving')) {
2292 //console.log(save.objects[i].key + " is already saving...");
2293 canSave = false;
2294 break;
2295 }
2296 }
2297
2298 // execute the save if ready, or queue for next time
2299 if (canSave) {
2300 // scan forward to find newest changeset for each object
2301 for (var i in save.objects) {
2302 for (var f = s + 1; f < queuedSaves.length; f++) {
2303 futureSave = queuedSaves[f];
2304 for (var o = 0; o < futureSave.objects.length; o++) {
2305 // it's the same object, but in the future, in a similar save
2306 if (futureSave.objects[o] == save.objects[i] && futureSave.fullOverwrite == save.fullOverwrite && futureSave.useOptimisticLock == save.useOptimisticLock) {
2307 // override current changeset with future changeset
2308 save.changesets[i] = futureSave.changesets[o];
2309
2310 // save a reference to the future save, so we can
2311 // remove the changeset when successful later
2312 save.futureSaves.push(futureSave);
2313 }
2314 }
2315 }
2316 }
2317
2318 // remove the save from queue
2319 queuedSaves.splice(s, 1);
2320 s--;
2321
2322 // run it
2323 save.execute()
2324 }
2325 }
2326 }
2327
2328 function getIndexValue(values, extraValue) {
2329 if (values == null) {
2330 values = []
2331 } else if (!(values instanceof Array)) {
2332 values = [values];
2333 }
2334 if (extraValue != null) {
2335 values = values.concat([extraValue]);
2336 }
2337
2338 var result = [];
2339 for (var i = 0; i != values.length; i++) {
2340 var value = values[i];
2341 switch (typeof (value)) {
2342 case 'boolean': result.push({ valuetype: _pio.ValueType.Bool, bool: value }); break;
2343 case 'string': result.push({ valuetype: _pio.ValueType.String, string: value }); break;
2344 case 'number':
2345 var isFloatingPoint = value % 1 != 0;
2346 if (isFloatingPoint) {
2347
2348 result.push({ valuetype: _pio.ValueType.Double, double: value })
2349 } else {
2350 if (value > -2147483648 && value < 2147483647) { // INTEGER RANGE
2351 result.push({ valuetype: _pio.ValueType.Int, int: value })
2352 } else if (value > 0 && value < 4294967295) { // uint
2353 result.push({ valuetype: _pio.ValueType.UInt, uint: value })
2354 } else { // long
2355 result.push({ valuetype: _pio.ValueType.Long, long: value })
2356 }
2357 }
2358 break;
2359 case 'object': // date, object & array
2360 if (value.getTime && value.getMilliseconds) { // date
2361 result.push({ valuetype: _pio.ValueType.DateTime, datetime: value.getTime() })
2362 } else {
2363 throw new Error("Don't know how to serialize type '" + typeof value + "' for BigDB");
2364 }
2365 break;
2366 default: throw new Error("Don't know how to serialize type '" + typeof value + "' for BigDB");
2367 }
2368 }
2369 return result;
2370 }
2371 }
2372
2373 /**
2374 * @class This class represents a database object in BigDB. It contains all the data of the database object, as well
2375 * as a method for persisting any changes back to BigDB.
2376 */
2377 _pio.databaseobject = function (owner, table, key, version, createIfMissing, properties) {
2378 // make an object representing what we think is
2379 // currently in the database, for future diffs
2380 var dbCurrent = {};
2381 var isSaving = false;
2382 _pio.bigDBDeserialize(properties, dbCurrent, true);
2383
2384 /** The table of the database object
2385 * @type string
2386 */
2387 this.table = table;
2388
2389 /** The key of the database object
2390 * @type string
2391 */
2392 this.key = key;
2393
2394 /** Returns true if this object has been persisted
2395 * @type bool
2396 */
2397 this.existsInDatabase = true;
2398
2399 /**
2400 * Persist the object to the database, using optimistic locking and full overwrite if specified.
2401 * @param {bool} useOptimisticLock If true, the save will only be completed if the database object has not changed in BigDB since this instance was loaded.
2402 * @param {bool} fullOverwrite Will completely overwrite the database object in BigDB with the properties in this instance, instead of just sending the changed properties to the server.
2403 * @param {function()} successCallback Callback function that will be called when the object have been successfully saved
2404 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2405 */
2406 this.save = function (useOptimisticLocks, fullOverwrite, successCallback, errorCallback) {
2407 owner.saveChanges(useOptimisticLocks, fullOverwrite, [this], successCallback, errorCallback, createIfMissing);
2408 }
2409
2410 this._internal_ = function (method, arg1) {
2411 switch (method) {
2412 case 'get-table': return table;
2413 case 'get-key': return key;
2414 case 'set-is-saving': isSaving = arg1;
2415 case 'get-is-saving': return isSaving;
2416 case 'get-version': return version;
2417 case 'set-version': version = arg1;
2418 case 'get-dbCurrent': return dbCurrent;
2419 case 'change-dbCurrent': _pio.bigDBDeserialize(arg1, dbCurrent, true);
2420 }
2421 }
2422
2423 _pio.bigDBDeserialize(properties, this, true);
2424 }
2425
2426 _pio.compareForChanges = function (original, current, isObject, isRoot) {
2427 var changes = []
2428
2429 // loop over all values in the current object
2430 // to find sets and changes
2431 for (var key in current) {
2432 var value = current[key];
2433 var valueOriginal = original ? original[key] : null
2434
2435 switch (key) {
2436 case 'table': if (isRoot) continue;
2437 case 'key': if (isRoot) continue;
2438 case 'save': if (isRoot) continue;
2439 case 'existsInDatabase': if (isRoot) continue;
2440 case '_internal_': if (isRoot) continue;
2441 case '_circular_reference_finder_': continue;
2442 }
2443
2444 if (value != null) {
2445 var prop = isObject ? { name: key} : { index: parseInt(key) }
2446
2447 switch (typeof (value)) {
2448 case 'boolean':
2449 if (value != valueOriginal) {
2450 prop.value = { valuetype: _pio.ValueType.Bool, bool: value }
2451 changes.push(prop)
2452 }
2453 break;
2454 case 'number':
2455 if (value != valueOriginal) {
2456 if (isFinite(value) && Math.floor(value) === value) {
2457 if (value >= -2147483648 && value <= 2147483647) {
2458 prop.value = { valuetype: _pio.ValueType.Int, int: value }
2459 } else if (value > 0 && value <= 4294967295) {
2460 prop.value = { valuetype: _pio.ValueType.UInt, uint: value }
2461 } else if (value >= -9223372036854775000 && value <= 9223372036854775000) { //Boundary is rounded because max_long and min_long can't be accurately represented as double
2462 prop.value = { valuetype: _pio.ValueType.Long, long: value }
2463 } else if (value > 0 && value <= 18446744073709550000) { //Boundary is rounded because max_ulong can't be accurately represented as double
2464 prop.value = { valuetype: _pio.ValueType.ULong, ulong: value }
2465 } else {
2466 prop.value = { valuetype: _pio.ValueType.Double, double: value }
2467 }
2468 } else {
2469 prop.value = { valuetype: _pio.ValueType.Double, double: value }
2470 }
2471 changes.push(prop)
2472 }
2473 break;
2474 case 'string':
2475 if (value != valueOriginal) {
2476 prop.value = { valuetype: _pio.ValueType.String, string: value }
2477 changes.push(prop)
2478 }
2479 break;
2480 case 'object': // date, object & array
2481 if (value.getTime && value.getMilliseconds) { // date
2482 if (value + '' != valueOriginal + '') {
2483 prop.value = { valuetype: _pio.ValueType.DateTime, datetime: value.getTime() }
2484 changes.push(prop)
2485 }
2486 } else {
2487 // check for circular references
2488 if (value._circular_reference_finder_) {
2489 throw new PlayerIOError(PlayerIOErrorCode.CircularReference, "The object you're trying to save contains a circular reference for " + (isObject ? "a property named" : "the array entry") + "): " + key)
2490 } else {
2491 value._circular_reference_finder_ = true;
2492 }
2493
2494 var valueIsArray = value instanceof Array;
2495 if (valueIsArray && value.bytearray) {
2496 var bytes = new Array(value.length);
2497 for (var i = 0; i != bytes.length; i++) {
2498 var val = value[i];
2499 if (val >= 0 && val <= 255) {
2500 bytes[i] = val
2501 } else {
2502 throw new PlayerIOError(PlayerIOErrorCode.GeneralError, "The bytearray property '" + key + "' was supposed to only contain byte (0-255) values, but contained the value: " + val);
2503 }
2504 }
2505 prop.value = { valuetype: _pio.ValueType.ByteArray, bytearray: bytes }
2506 } else {
2507 var subChanges = _pio.compareForChanges(valueOriginal, value, !valueIsArray, false);
2508 if (valueIsArray) {
2509 prop.value = { valuetype: _pio.ValueType.Array, arrayproperties: subChanges }
2510 } else {
2511 prop.value = { valuetype: _pio.ValueType.Obj, objectproperties: subChanges }
2512 }
2513 }
2514 changes.push(prop)
2515
2516 // remove circular reference finder
2517 delete value._circular_reference_finder_;
2518 }
2519 break;
2520 case 'undefined': break;
2521 case 'function': break;
2522 default: throw new Error("Don't know how to serialize type '" + typeof value + "' for BigDB");
2523 }
2524 }
2525 }
2526
2527 // loop over all values in the original object to find
2528 // properties that were deleted
2529 for (var key in original) {
2530 if (current[key] == null || typeof (current[key]) == 'undefined') {
2531 // property was deleted
2532 changes.push(isObject ? { name: key} : { index: parseInt(key) }); //getProp(null, key, isObject));
2533 }
2534 }
2535
2536 return changes;
2537 }
2538
2539 _pio.bigDBDeserialize = function (properties, target, isObject) {
2540 for (var x in properties) {
2541 var p = properties[x];
2542 var key = isObject ? p.name : (p.index || 0);
2543 var val = p.value;
2544
2545 if (val) {
2546 switch (val.valuetype || 0) {
2547 case _pio.ValueType.String: target[key] = val.string || ""; break;
2548 case _pio.ValueType.Int: target[key] = val.int || 0; break;
2549 case _pio.ValueType.UInt: target[key] = val.uint || 0; break;
2550 case _pio.ValueType.Long: target[key] = val.long || 0; break;
2551 case _pio.ValueType.Bool: target[key] = val.bool || 0; break;
2552 case _pio.ValueType.Float: target[key] = val.float || 0.0; break;
2553 case _pio.ValueType.Double: target[key] = val.double || 0.0; break;
2554 case _pio.ValueType.ByteArray:
2555 target[key] = val.bytearray;
2556 target[key].bytearray = true;
2557 break;
2558 case _pio.ValueType.DateTime: target[key] = new Date(val.datetime || 0); break;
2559 case _pio.ValueType.Array:
2560 var arr = target[key] instanceof Array ? target[key] : [];
2561 _pio.bigDBDeserialize(val.arrayproperties, arr, false);
2562 target[key] = arr;
2563 break;
2564 case _pio.ValueType.Obj:
2565 var obj = typeof (target[key]) == 'object' ? target[key] : {}
2566 _pio.bigDBDeserialize(val.objectproperties, obj, true);
2567 target[key] = obj;
2568 break;
2569 }
2570 } else {
2571 delete target[key];
2572 }
2573 }
2574 }
2575})();
2576(function () {
2577 var entryType_Integer = 0;
2578 var entryType_UnsignedInteger = 1;
2579 var entryType_Long = 2;
2580 var entryType_UnsignedLong = 3;
2581 var entryType_Double = 4;
2582 var entryType_Float = 5;
2583 var entryType_String = 6;
2584 var entryType_ByteArray = 7;
2585 var entryType_Boolean = 8;
2586
2587 /**
2588 * @class The Multiplayer service. This class has method for listing, creating, and joining multiplayer rooms.
2589 * Once a user has been sucessfully connected to a room, a connection object will be returned. This class is also
2590 * used to override the development server setting when developing and testing multiplayer code against your
2591 * local development server.
2592 * @example Joining a multiplayer room and listening to all messages
2593 * <listing>
2594 * client.multiplayer.createJoinRoom(
2595 * 'my-room-id', //Room id
2596 * 'bounce', //Room type
2597 * true, //Visible
2598 * { mode: 'team' }, //Room data
2599 * { team: 'awesome' }, //Join data
2600 * function(connection) {
2601 * //Success!
2602 * //Setup a message handler to listen to messages of all types:
2603 * connection.addMessageCallback("*", function(message) {
2604 * //When we receive a message, log it to the console:
2605 * console.log(message);
2606 * //Send back a message to the room:
2607 * connection.send('messagetype','arg1',2,'arg3');
2608 * //Disconnect from the room:
2609 * connection.disconnect();
2610 * });
2611 * //Setup a disconnect handler:
2612 * connection.addDisconnectCallback(function(){
2613 * console.log("disconnected from room");
2614 * });
2615 * },
2616 * function(error) {
2617 * console.log(error);
2618 * }
2619 * );
2620 * </listing>
2621 */
2622 _pio.multiplayer = function (channel) {
2623 var self = this;
2624 /**
2625 * If not null, rooms will be created on the development server at the address defined by the server endpoint, instead of using the live multiplayer servers.
2626 * @type string
2627 */
2628 this.developmentServer = null;
2629
2630 /**
2631 * If set to true, the multiplayer connections will be encrypted using TLS/SSL. Be aware that this will cause a performance degradation by introducting secure connection negotiation latency when connecting.
2632 * @type bool
2633 */
2634 this.useSecureConnections = false;
2635
2636 /**
2637 * Create a multiplayer room on the Player.IO infrastructure.
2638 * @param {string} roomId The id you wish to assign to your new room - You can use this to connect to the specific room later as long as it still exists.
2639 * @param {string} roomType The name of the room type you wish to run the room as. This value should match one of the [RoomType(...)] attributes of your uploaded code. A room type of 'bounce' is always available.
2640 * @param {bool} visible Should the room be visible when listing rooms with GetRooms or not?
2641 * @param {object} roomData The data to initialize the room with, this can be read with ListRooms and changed from the serverside.
2642 * @param {function(string)} successCallback Callback function that will be called with the newly created room id
2643 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2644 */
2645 this.createRoom = function (roomId, roomType, visible, roomData, successCallback, errorCallback) {
2646 channel.createRoom(roomId, roomType, visible, _pio.convertToKVArray(roomData), self.developmentServer != null, successCallback, errorCallback, function (result) {
2647 return result.roomid;
2648 });
2649 }
2650
2651 /**
2652 * Join a running multiplayer room.
2653 * @param {string} roomId The id of the room you wish to connect to.
2654 * @param {string} joinData Data to send to the room with additional information about the join.
2655 * @param {function(connection)} successCallback Callback function that will be called with a connection to the room
2656 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2657 */
2658 this.joinRoom = function (roomId, joinData, successCallback, errorCallback) {
2659 // this lets autocomplete in visual studio know the success callback takes a connection.
2660 clearTimeout(setTimeout(function () { successCallback(new _pio.connection()) },10000))
2661 var originalStack = new Error();
2662 channel.joinRoom(roomId, _pio.convertToKVArray(joinData), self.developmentServer != null, function(){}, errorCallback, function (result) {
2663 return new _pio.connection(originalStack, self.developmentServer, self.useSecureConnections, result.endpoints, result.joinkey, joinData || {}, successCallback, errorCallback)
2664 });
2665 }
2666
2667 /**
2668 * Creates a multiplayer room (if it does not exist already) and joins it.
2669 * @param {string} roomId The id of the room you wish to create/join.
2670 * @param {string} roomType The name of the room type you wish to run the room as. This value should match one of the [RoomType(...)] attributes of your uploaded code. A room type of 'bounce' is always available.
2671 * @param {bool} visible If the room doesn't exist: Should the room be visible when listing rooms with GetRooms upon creation?
2672 * @param {object} roomData If the room doesn't exist: The data to initialize the room with upon creation.
2673 * @param {object} joinData Data to send to the room with additional information about the join.
2674 * @param {function(connection)} successCallback Callback function that will be called with a connection to the room
2675 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2676 */
2677 this.createJoinRoom = function (roomId, roomType, visible, roomData, joinData, successCallback, errorCallback) {
2678 // this lets autocomplete in visual studio know the success callback takes a connection.
2679 clearTimeout(setTimeout(function () { successCallback(new _pio.connection()) },10000))
2680 var originalStack = new Error();
2681 channel.createJoinRoom(roomId, roomType, visible, _pio.convertToKVArray(roomData), _pio.convertToKVArray(joinData), self.developmentServer != null, function () { }, errorCallback, function (result) {
2682 return new _pio.connection(originalStack, self.developmentServer, self.useSecureConnections, result.endpoints, result.joinkey, joinData || {}, successCallback, errorCallback)
2683 })
2684 }
2685
2686 /**
2687 * List the currently running multiplayer rooms.
2688 * @param {string} roomType The type of room you wish to list.
2689 * @param {object} searchCriteria Only rooms with the same values in their roomdata will be returned.
2690 * @param {int} resultLimit The maximum amount of rooms you want to receive. Use 0 for 'as many as possible'.
2691 * @param {int} resultOffset The offset into the list you wish to start listing at.
2692 * @param {function(RoomInfo[])} successCallback Callback function that will be called with a connection to the room
2693 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
2694 */
2695 this.listRooms = function (roomType, searchCriteria, resultLimit, resultOffset, successCallback, errorCallback) {
2696 channel.listRooms(roomType, _pio.convertToKVArray(searchCriteria), resultLimit, resultOffset, self.developmentServer != null, successCallback, errorCallback, function (result) {
2697 var arr = [];
2698
2699 if (result.rooms && result.rooms.length > 0) {
2700 for (var i = 0; i != result.rooms.length; i++) {
2701 var item = result.rooms[i];
2702 arr.push(new _pio.roomInfo(item.id, item.roomtype, item.onlineusers, _pio.convertFromKVArray(item.roomdata)))
2703 }
2704 }
2705
2706 return arr;
2707 });
2708 }
2709 }
2710
2711 _pio.websocketConnection = function (talkBinary, useSecureConnections, endpoint, connectTimeout, onConnectResult, onDisconnect, onMessage) {
2712 var self = this;
2713 var connectComplete = false;
2714 var serializer = new _pio.messageSerializer();
2715 var connected = false;
2716 var socket = null;
2717 var url = (useSecureConnections ? 'wss://' : 'ws://') + endpoint + '/';
2718 try {
2719 if (typeof (MozWebSocket) != 'undefined') {
2720 socket = new MozWebSocket(url);
2721 } else {
2722 socket = new WebSocket(url);
2723 }
2724 } catch (e) {
2725 onConnectResult(false, '' + e);
2726 return;
2727 }
2728
2729 var timeout = setTimeout(function () {
2730 if (!connectComplete) {
2731 connectComplete = true;
2732 onConnectResult(false, "Connect attempt timed out");
2733 }
2734 }, connectTimeout);
2735
2736 socket.onopen = function () {
2737 if (!connectComplete) {
2738 clearTimeout(timeout)
2739 connectComplete = true;
2740 connected = true;
2741 onConnectResult(connected);
2742 }
2743 }
2744 socket.onclose = function (e) {
2745 self.disconnect();
2746 }
2747 socket.onerror = function (msg) {
2748 self.disconnect();
2749 }
2750 socket.onmessage = function (msg) {
2751 if (connected) {
2752 if (talkBinary) {
2753 var fr = new FileReader();
2754 fr.onloadend = function () {
2755 var uint8view = new Uint8Array(fr.result);
2756 var bytes = new Array(uint8view.length)
2757 for (var i = 0; i != uint8view.length; i++) {
2758 bytes[i] = uint8view[i];
2759 }
2760 onMessage(serializer.deserializeMessage(bytes, 0, bytes.length))
2761 }
2762 fr.readAsArrayBuffer(msg.data);
2763 } else {
2764 var bytes = _pio.base64decode(msg.data);
2765 onMessage(serializer.deserializeMessage(bytes, 0, bytes.length))
2766 }
2767 }
2768 }
2769 this.disconnect = function () {
2770 if (connected) {
2771 connected = false;
2772 onDisconnect();
2773 try {
2774 socket.close();
2775 } catch (e) { _pio.debugLog(e) }
2776 }
2777 }
2778 this.sendMessage = function (message) {
2779 var serialized = serializer.serializeMessage(message);
2780
2781 if (talkBinary) {
2782 var bytes = new Uint8Array(serialized.length);
2783 for (var i = 0; i < serialized.length; i++) {
2784 bytes[i] = serialized[i];
2785 }
2786 socket.send(bytes.buffer)
2787 } else {
2788 var str = _pio.base64encode(serialized);
2789 socket.send(str);
2790 }
2791 }
2792 }
2793
2794 /**
2795 * @class An instance of this class is returned after successfully joining a Multiplayer room.
2796 * It encapsulates the active network connection between the user and the room, and has methods
2797 * for sending and handling messages, as well as disconnecting fom the room. See the Multiplayer class for examples.
2798 */
2799 _pio.connection = function (originalStack, developmentServer, useSecureConnections, endpoints, joinKey, joinData, successCallback, errorCallback) {
2800 var self = this;
2801 var waitingForJoinResult = true;
2802 var disconnectCallbacks = [];
2803 var messageCallback = {}
2804 var queuedMessages = [];
2805 var socket = null;
2806
2807 // decide what kind of socket connection to establish (websocket-binary, flash or websocket-base64).
2808 // unfortunately, there is no way to easily detect if the current browser supports binary data over websockets.
2809 // so what we do is have a whitelist of known supported browsers, then fallback to flash, then fallback to base64 text over websockets.
2810 var browsers = (/(WebKit|Firefox|Trident)\/([0-9]+)/gi).exec(navigator.userAgent);
2811 var engine = browsers && browsers.length >= 3 ? browsers[1] : null
2812 var version = browsers && browsers.length >= 3 ? parseInt(browsers[2]) : null
2813 var supportsBinaryWebsockets = window.FileReader && (engine == "WebKit" && version >= 535) || (engine == "Firefox" && version >= 11) || (engine == "Trident" && version >= 6);
2814
2815 if (supportsBinaryWebsockets) {
2816 connect(function (useSecureConnections, endpoint, connectTimeout, onConnect, onDisconnect, onMesssage) { return new _pio.websocketConnection(true, useSecureConnections, endpoint, connectTimeout, onConnect, onDisconnect, onMesssage); })
2817 } else {
2818 _pio.isFlashFallbackEnabled(function (enabled) {
2819 if (enabled) {
2820 connect(function (useSecureConnections, endpoint, connectTimeout, onConnect, onDisconnect, onMesssage) { return new _pio.flashSocketConnection(endpoint, connectTimeout, onConnect, onDisconnect, onMesssage); })
2821 } else {
2822 connect(function (useSecureConnections, endpoint, connectTimeout, onConnect, onDisconnect, onMesssage) { return new _pio.websocketConnection(false, useSecureConnections, endpoint, connectTimeout, onConnect, onDisconnect, onMesssage); })
2823 }
2824 })
2825 }
2826
2827 function connect(createSocket) {
2828 // find the endpoints
2829 var endpointStrings = [];
2830 if (developmentServer) {
2831 endpointStrings.push(developmentServer);
2832 } else {
2833 for (var i = 0; i != endpoints.length; i++) {
2834 endpointStrings.push(endpoints[i].address + ":" + endpoints[i].port);
2835 }
2836 }
2837
2838 function tryNextEndpoint() {
2839 if (endpointStrings.length > 0) {
2840 // grab the first endpoint
2841 var endpoint = endpointStrings[0];
2842 endpointStrings.splice(0, 1);
2843
2844 // try connecting to this endpoint
2845 var s = createSocket(useSecureConnections, endpoint, 4000,
2846 function (connected, reason) { // onConnect
2847 if (connected) {
2848 socket = s;
2849 self.connected = true;
2850 // send join data
2851 var msg = self.createMessage('join');
2852 msg.addString(joinKey);
2853 if (joinData != null) {
2854 for (var x in joinData) {
2855 msg.addString(x);
2856 msg.addString('' + joinData[x]);
2857 }
2858 }
2859 self.sendMessage(msg);
2860 } else {
2861 _pio.debugLog("Unable to connect to endpoint: " + endpoint + ". reason: \"" + reason + (endpointStrings.length > 0 ? "\". Trying next endpoint." : "\". No more endpoints to try."));
2862 tryNextEndpoint();
2863 }
2864 }, function (reason) { // onDisconnect
2865 if (self.connected) {
2866 self.connected = false;
2867 setTimeout(function () {
2868 for (var i = 0; i != disconnectCallbacks.length; i++) {
2869 _pio.runCallback(disconnectCallbacks[i].callback, self, disconnectCallbacks[i].stackSource);
2870 }
2871 }, 100);
2872 }
2873 }, function (msg) { // onMessage
2874 if (waitingForJoinResult) {
2875 if (msg.type == "playerio.joinresult") {
2876 waitingForJoinResult = false;
2877 if (!msg.getBoolean(0)) {
2878 _pio.handleError(originalStack, errorCallback, msg.getInt(1), msg.getString(2))
2879 } else {
2880 _pio.runCallback(successCallback, self, null);
2881 }
2882 } else {
2883 _pio.handleError(originalStack, errorCallback, PlayerIOErrorCode.GeneralError, "The expected inital messagetype is: playerio.joinresult, received: " + joinResult.getType())
2884 }
2885 } else {
2886 executeCallbacks(msg.type, msg);
2887 executeCallbacks('*', msg);
2888 }
2889 }
2890 )
2891 } else {
2892 _pio.handleError(originalStack, errorCallback, PlayerIOErrorCode.GeneralError, "Could not establish a socket connection to any of the given endpoints for the room")
2893 }
2894 }
2895
2896 tryNextEndpoint();
2897
2898 function executeCallbacks(type, msg) {
2899 var arr = messageCallback[type];
2900 if (arr) {
2901 for (var i = 0; i < arr.length; i++) {
2902 _pio.runCallback(arr[i].callback, msg, arr[i].stackSource);
2903 }
2904 }
2905 }
2906 }
2907
2908 /**
2909 * Indicates if the connection is still alive
2910 */
2911 this.connected = false;
2912
2913 /**
2914 * Add a disconnect callback that will be called when the connection is disconnected
2915 * @param {function()} callback The callback to be called when a disconnect happens
2916 */
2917 this.addDisconnectCallback = function (callback) {
2918 disconnectCallbacks.push({ callback: callback, stackSourc: new Error() });
2919 }
2920
2921 /**
2922 * Add a message callback for the given message type.
2923 * @param {string} type The type of message to invoke the callback for. Use '*' to handle all message types.
2924 * @param {function(message)} callback The callback to be called when a message of the given type is received
2925 */
2926 this.addMessageCallback = function (type, callback) {
2927 if (type == null) {
2928 type = "*"
2929 }
2930 var list = messageCallback[type]
2931 if (!list) {
2932 messageCallback[type] = list = [];
2933 }
2934 list.push({ callback: callback, stackSource: new Error() });
2935 }
2936
2937 /**
2938 * Remove an already registered disconnect callback
2939 * @param {function} callback The callback to remove
2940 */
2941 this.removeDisconnectCallback = function (callback) {
2942 for (var i = 0; i < disconnectCallbacks.length; i++) {
2943 if (disconnectCallbacks[i].callback == callback) {
2944 disconnectCallbacks.splice(i, 1);
2945 i--;
2946 }
2947 }
2948 }
2949
2950 /**
2951 * Remove an already registered message callback
2952 * @param {function} callback The callback to remove
2953 */
2954 this.removeMessageCallback = function (callback) {
2955 for (var t in messageCallback) {
2956 var arr = messageCallback[t];
2957 for (var i = 0; i < arr.length; i++) {
2958 if (arr[i].callback == callback) {
2959 arr.splice(i, 1);
2960 i--;
2961 }
2962 }
2963 }
2964 }
2965
2966 /**
2967 * Create a message with arguments inline: connection.createMessage('invite', arg1, arg2...)
2968 * @param {string} type The string type to give to the message.
2969 * @return {message} message The message
2970 */
2971 this.createMessage = function (type) {
2972 var msg = new _pio.message(type);
2973 for (var i = 1; i < arguments.length; i++) {
2974 msg.add(arguments[i]);
2975 }
2976 return msg;
2977 }
2978
2979 /**
2980 * Send a message with arguments inline: connection.createMessage('invite', arg1, arg2...)
2981 * @param {string} type The string type to give to the message.
2982 */
2983 this.send = function (type) {
2984 var msg = this.createMessage.apply(this, arguments);
2985 this.sendMessage(msg);
2986 }
2987
2988 /**
2989 * Send a message
2990 * @param {message} message The message to send.
2991 */
2992 this.sendMessage = function (message) {
2993 if (self.connected) {
2994 socket.sendMessage(message);
2995 } else {
2996 queuedMessages.push(message);
2997 }
2998 }
2999
3000 /**
3001 * Disconnect from the multiplayer room
3002 */
3003 this.disconnect = function () {
3004 if (self.connected) {
3005 socket.disconnect();
3006 }
3007 }
3008 }
3009
3010 /**
3011 * @class Represents a message sent between client and server.
3012 * A message consists of a string type, and a payload of zero or more typed parameters.
3013 * @example This is how to create a simple message that we send to the server indicating that this player is ready:
3014 * <listing>
3015 * //Create a message of type ready with no payload:
3016 * var m = connection.createMessage("ready");
3017 *
3018 * //Send the message to the server:
3019 * connection.sendMessage(m);
3020 * </listing>
3021 *
3022 * @example Usually, it's much easier to simply use the convenience methods:
3023 * <listing>
3024 * //Send the server a message of which maps this player selected:
3025 * connection.Send("mapsselected", "fields-of-glory", "small-skirmish");
3026 *
3027 * //Send a chat message to the server, that it can broadcast:
3028 * connection.Send("chat", "Hey guys, are you ready to start the game?");
3029 * </listing>
3030 * @example You can also build up messages as you go, if you don't know the exact payload until runtime.
3031 * In this example, imagine the player has multiple pieces and we send in the list of moves this player wants to do in a single turn.
3032 * <listing>
3033 * //Create a new message of type moves:
3034 * var m = connection.createMessage("moves");
3035 *
3036 * //Add all pending moves to the message:
3037 * foreach(var move in moves) {
3038 * m.add(move.pieceid, move.x, move.y);
3039 * }
3040 *
3041 * //Send the message to the server:
3042 * connection.sendMessage(m);
3043 * </listing>
3044 */
3045 _pio.message = function (type) {
3046 var self = this;
3047 var types = [];
3048 var objects = [];
3049
3050 /** The type of the message
3051 * @type string
3052 */
3053 this.type = type;
3054
3055 /** The number of entries in this message
3056 * @type int
3057 */
3058 this.length = 0;
3059
3060 /**
3061 * Adds data entries to the Message object
3062 * @param arguments Entries to add. Valid types are Number, String, Boolean and ByteArray. If a Number is passed, and it is an integer, it will be added to the first type it fits in this order: Int, UInt, Long, ULong. If it doesn't fit in any integer type, or if it's not an integer, it will be added as a Double.
3063 * @example How to add values to a message
3064 * <listing>
3065 * message.add("This is a chat message", 3, true);
3066 * </listing>
3067 */
3068 this.add = function () {
3069 for (var i = 0; i < arguments.length; i++) {
3070 var value = arguments[i];
3071 switch (typeof (value)) {
3072 case 'string': self.addString(value); break;
3073 case 'boolean': self.addBoolean(value); break;
3074 case 'number':
3075 if (isFinite(value) && Math.floor(value) === value) {
3076 if (value >= -2147483648 && value <= 2147483647) {
3077 self.addInt(value); break;
3078 } else if (value > 0 && value <= 4294967295) {
3079 self.addUInt(value); break;
3080 } else if (value >= -9223372036854775000 && value <= 9223372036854775000) {
3081 //Boundary is rounded because max_long and min_long can't be accurately represented as double
3082 self.addLong(value); break;
3083 } else if (value > 0 && value <= 18446744073709550000) {
3084 //Boundary is rounded because max_ulong can't be accurately represented as double
3085 self.addULong(value); break;
3086 }
3087 }
3088 self.addDouble(value); break;
3089 case 'object':
3090 if (isByteArray(value)) {
3091 this.addByteArray(value)
3092 break;
3093 }
3094 default:
3095 throw _pio.error("The type of the value (" + value + ") cannot be inferred");
3096 }
3097 }
3098 }
3099
3100 /** Add a value encoded as an int to the message
3101 * @param {number} value The number to add
3102 */
3103 this.addInt = function (value) {
3104 add(value >= -2147483648 && value <= 2147483647, Math.trunc(value), entryType_Integer, "an integer (32bit)");
3105 }
3106
3107 /** Add a value encoded as a uint to the message
3108 * @param {number} value The number to add
3109 */
3110 this.addUInt = function (value) {
3111 add(value >= 0 && value <= 4294967295, Math.trunc(value), entryType_UnsignedInteger, "an unsigned integer (32bit)");
3112 }
3113
3114 /** Add a value encoded as a long to the message
3115 * @param {number} value The number to add
3116 */
3117 this.addLong = function (value) {
3118 //Boundary is rounded because max_long and min_long can't be accurately represented as double
3119 add(value >= -9223372036854775000 && value <= 9223372036854775000, Math.trunc(value), entryType_Long, "a long (64bit)");
3120 }
3121
3122 /** Add a value encoded as a ulong to the message
3123 * @param {number} value The number to add
3124 */
3125 this.addULong = function (value) {
3126 //Boundary is rounded because max_ulong can't be accurately represented as double
3127 add(value >= 0 && value <= 18446744073709550000, value, entryType_UnsignedLong, "an unsigned long (64bit)");
3128 }
3129
3130 /** Add a boolean value to the message
3131 * @param {bool} value The bool to add
3132 */
3133 this.addBoolean = function (value) {
3134 add(true, value ? true : false, entryType_Boolean, "a boolean value");
3135 }
3136
3137 /** Add a value encoded as a float to the message
3138 * @param {number} value The number to add
3139 */
3140 this.addFloat = function (value) {
3141
3142 add(true, Number(value), entryType_Float, "a floating point value (32bit)");
3143 }
3144
3145 /** Add a value encoded as a double to the message
3146 * @param {number} value The number to add
3147 */
3148 this.addDouble = function (value) {
3149
3150 add(true, Number(value), entryType_Double, "a double floating point value (64bit)");
3151 }
3152
3153 /** Add a byte array value to the message
3154 * @param {byte[]} value The byte array to add
3155 */
3156 this.addByteArray = function (value) {
3157 add(isByteArray(value), value, entryType_ByteArray, "a bytearray");
3158 }
3159
3160 /** Add a string value to the message
3161 * @param {string} value The string to add
3162 */
3163 this.addString = function (value) {
3164 add(true, value + '', entryType_String, "a string");
3165 }
3166
3167 /** Get the int from the message at the given index
3168 * @param {int} index The zero-based index of the entry to get
3169 * @return int */
3170 this.getInt = function (index) {
3171 return get(index, entryType_Integer)
3172 }
3173
3174 /** Get the uint from the message at the given index
3175 * @param {int} index The zero-based index of the entry to get
3176 * @return uint */
3177 this.getUInt = function (index) {
3178 return get(index, entryType_UnsignedInteger)
3179 }
3180
3181 /** Get the long from the message at the given index
3182 * @param {int} index The zero-based index of the entry to get
3183 * @return long */
3184 this.getLong = function (index) {
3185 return get(index, entryType_Long)
3186 }
3187
3188 /** Get the ulong from the message at the given index
3189 * @param {int} index The zero-based index of the entry to get
3190 * @return ulong */
3191 this.getULong = function (index) {
3192 return get(index, entryType_UnsignedLong)
3193 }
3194
3195 /** Get the bool from the message at the given index
3196 * @param {int} index The zero-based index of the entry to get
3197 * @return bool */
3198 this.getBoolean = function (index) {
3199 return get(index, entryType_Boolean)
3200 }
3201
3202 /** Get the double from the message at the given index
3203 * @param {int} index The zero-based index of the entry to get
3204 * @return double */
3205 this.getDouble = function (index) {
3206 return get(index, entryType_Double)
3207 }
3208
3209 /** Get the float from the message at the given index
3210 * @param {int} index The zero-based index of the entry to get
3211 * @return float */
3212 this.getFloat = function (index) {
3213 return get(index, entryType_Float)
3214 }
3215
3216 /** Get the int from the message at the given index
3217 * @param {int} index The zero-based index of the entry to get
3218 * @return int */
3219 this.getByteArray = function (index) {
3220 return get(index, entryType_ByteArray)
3221 }
3222
3223 /** Get the string from the message at the given index
3224 * @param {int} index The zero-based index of the entry to get
3225 * @return string */
3226 this.getString = function (index) {
3227 return get(index, entryType_String)
3228 }
3229
3230 /** Get a string representation of the message
3231 * @return string */
3232 this.toString = function () {
3233 var str = "msg.Type = " + this.type + "";
3234 for (var i = 0; i != this.length; i++) {
3235 str += ", msg[" + i + "] = " + objects[i] + " (" + getTypeString(types[i]) + ")"
3236 }
3237 return str;
3238 }
3239
3240 this._internal_ = function (method, arg) {
3241 switch (method) {
3242 case 'get-objects': return objects;
3243 case 'get-types': return types;
3244 }
3245 }
3246
3247 function add(check, value, type, errorMessage) {
3248 if (check) {
3249 objects.push(value);
3250 types.push(type);
3251 self.length = objects.length;
3252 } else {
3253 throw _pio.error("The given value (" + value + ") is not " + errorMessage);
3254 }
3255 }
3256
3257 function get(index, type) {
3258 if (index > objects.length) {
3259 throw _pio.error("this message (" + self.type + ") only has " + objects.length + " entries");
3260 } else {
3261 if (types[index] == type) {
3262 return objects[index];
3263 } else {
3264 throw _pio.error("Value at index:" + index + " is a " + getTypeString(types[index]) + " and not a " + getTypeString(type) + " as requested. The value is: " + objects[index]);
3265 }
3266 }
3267 }
3268
3269 function getTypeString(type) {
3270 var t = {}
3271 t[entryType_Integer] = "Integer"
3272 t[entryType_UnsignedInteger] = "Unsigned Integer"
3273 t[entryType_Long] = "Long"
3274 t[entryType_UnsignedLong] = "Unsigned Long"
3275 t[entryType_Double] = "Double"
3276 t[entryType_Float] = "Float"
3277 t[entryType_String] = "String"
3278 t[entryType_ByteArray] = "ByteArray"
3279 t[entryType_Boolean] = "Boolean"
3280 return t[type];
3281 }
3282
3283 function isByteArray(value) {
3284 var isBytes = typeof (value) == 'object' && typeof (value.length) != 'undefined'
3285 if (isBytes) {
3286 for (var i = 0; i != value.length; i++) {
3287 if (value[i] > 255 || value[i] < 0) {
3288 isBytes = false;
3289 break;
3290 }
3291 }
3292 }
3293 return isBytes;
3294 }
3295 }
3296
3297 /**
3298 * Information about a room returned from listRooms
3299 */
3300 _pio.roomInfo = function (id, roomType, onlineUsers, roomData) {
3301 /** The id of the room
3302 * @type string
3303 */
3304 this.id = id;
3305
3306 /** The type of the room (coresponding to the [RoomType(...)] attribute assignd to the room)
3307 * @type string
3308 */
3309 this.roomType = roomType;
3310
3311 /** How many users are currently in the room
3312 * @type int
3313 */
3314 this.onlineUsers = onlineUsers;
3315
3316 /** How many users are currently in the room
3317 * @type object
3318 */
3319 this.roomData = roomData;
3320 }
3321
3322 _pio.byteWriter = function () {
3323 this.bytes = [];
3324
3325 this.writeByte = function (byte) {
3326 if (byte >= 0 && byte <= 255) {
3327 this.bytes.push(byte);
3328 } else {
3329 throw new Error("This is not a byte value: " + byte);
3330 }
3331 }
3332
3333 this.writeBytes = function (bytes) {
3334 for (var i = 0; i != bytes.length; i++) {
3335 this.writeByte(bytes[i]);
3336 }
3337 }
3338
3339 this.writeTagWithLength = function (length, topPattern, bottomPattern) {
3340 if (length > 63 || length < 0) {
3341 this.writeBottomPatternAndBytes(bottomPattern, _pio.binaryserializer.bytesFromInt(length))
3342 } else {
3343 this.writeByte(topPattern | length);
3344 }
3345 }
3346
3347 this.writeBottomPatternAndBytes = function (pattern, bytes) {
3348 var count = 0;
3349 if (bytes[0] != 0) {
3350 count = 3
3351 } else if (bytes[1] != 0) {
3352 count = 2;
3353 } else if (bytes[2] != 0) {
3354 count = 1;
3355 }
3356
3357 this.writeByte(pattern | count);
3358 for (var i = bytes.length - count - 1; i != bytes.length; i++) {
3359 this.writeByte(bytes[i]);
3360 }
3361 }
3362
3363 this.writeLongPattern = function (shortPattern, longPattern, bytes) {
3364 var count = 0;
3365 for (var i = 0; i != 7; i++) {
3366 if (bytes[i] != 0) {
3367 count = 7 - i;
3368 break;
3369 }
3370 }
3371
3372 if (count > 3) {
3373 this.writeByte(longPattern | (count - 4));
3374 } else {
3375 this.writeByte(shortPattern | count);
3376 }
3377
3378 for (var i = bytes.length - count - 1; i != bytes.length; i++) {
3379 this.writeByte(bytes[i]);
3380 }
3381 }
3382 }
3383
3384 _pio.messageSerializer = function () {
3385 var topPattern = 192; //11000000
3386 var bottomPattern = 60; //00111100
3387 //------------------------------------------------
3388 var stringTopPattern = 192; //11000000
3389 var integerTopPattern = 128; //10000000
3390 var byteArrayTopPattern = 64; //01000000
3391 //------------------------------------------------
3392 var integerBottomPattern = 4; //00000100
3393 var unsignedIntegerBottomPattern = 8; //00001000
3394 var stringBottomPattern = 12; //00001100
3395 var byteArrayBottomPattern = 16; //00010000
3396 var shortLongBottomPattern = 48; //00110000
3397 var longBottomPattern = 52; //00110100
3398 var shortUnsignedLongBottomPattern = 56; //00111000
3399 var unsignedLongBottomPattern = 60; //00111100
3400 //------------------------------------------------
3401 var doublePattern = 3; //00000011
3402 var floatPattern = 2; //00000010
3403 var booleanTruePattern = 1; //00000001
3404 var booleanFalsePattern = 0; //00000000
3405
3406 this.serializeMessage = function (message) {
3407 var writer = new _pio.byteWriter();
3408 var startLength = writer.length;
3409
3410 // write the amount of items as first thing
3411 writer.writeTagWithLength(message.length, integerTopPattern, integerBottomPattern);
3412
3413 // write the type as the second thing
3414 var bytes = _pio.binaryserializer.bytesFromString(message.type);
3415 writer.writeTagWithLength(bytes.length, stringTopPattern, stringBottomPattern);
3416 writer.writeBytes(bytes);
3417
3418 // write all the contents of the message
3419 for (var i = 0; i != message.length; i++) {
3420 var value = message._internal_('get-objects')[i];
3421 switch (message._internal_('get-types')[i]) {
3422 case entryType_String:
3423 var bytes = _pio.binaryserializer.bytesFromString(value);
3424 writer.writeTagWithLength(bytes.length, stringTopPattern, stringBottomPattern);
3425 writer.writeBytes(bytes);
3426 break;
3427 case entryType_Integer:
3428 writer.writeTagWithLength(value, integerTopPattern, integerBottomPattern);
3429 break;
3430 case entryType_UnsignedInteger:
3431 writer.writeBottomPatternAndBytes(unsignedIntegerBottomPattern, _pio.binaryserializer.bytesFromUInt(value))
3432 break;
3433 case entryType_Long:
3434 writer.writeLongPattern(shortLongBottomPattern, longBottomPattern, _pio.binaryserializer.bytesFromLong(value));
3435 break;
3436 case entryType_UnsignedLong:
3437 writer.writeLongPattern(shortUnsignedLongBottomPattern, unsignedLongBottomPattern, _pio.binaryserializer.bytesFromULong(value));
3438 break;
3439 case entryType_ByteArray:
3440 writer.writeTagWithLength(value.length, byteArrayTopPattern, byteArrayBottomPattern);
3441 writer.writeBytes(value);
3442 break;
3443 case entryType_Double:
3444 writer.writeByte(doublePattern);
3445 writer.writeBytes(_pio.binaryserializer.bytesFromDouble(value));
3446 break;
3447 case entryType_Float:
3448 writer.writeByte(floatPattern);
3449 var fb = _pio.binaryserializer.bytesFromFloat(value);
3450 writer.writeBytes(fb);
3451 break;
3452 case entryType_Boolean:
3453 writer.writeByte(value ? booleanTruePattern : booleanFalsePattern);
3454 break;
3455 }
3456 }
3457
3458 return writer.bytes;
3459 }
3460
3461 this.deserializeMessage = function (bytes, start, count) {
3462 var position = start;
3463 var end = start + count;
3464 var output = null;
3465 var partsInMessage = 0;
3466
3467 while (position < end) {
3468 var startPosition = position;
3469 var length = 0;
3470 var value = 0;
3471
3472 // find the pattern used
3473 var tag = bytes[position];
3474 position++; // pass the tag
3475 var pattern = tag & topPattern;
3476 if (pattern == 0) {
3477 pattern = tag & bottomPattern;
3478 if (pattern == 0) {
3479 pattern = tag;
3480 }
3481 }
3482
3483 // find the length of the actual data
3484 switch (pattern) {
3485 case stringBottomPattern:
3486 case byteArrayBottomPattern:
3487 length = (tag & 3) + 1; // bytes
3488
3489 // do we have the bytes for the length?
3490 if (position + length > end) {
3491 throw new Error("Unexpected: Unfinished message");
3492 }
3493
3494 // read the bytes containing the length
3495 var jump = length;
3496 length = _pio.binaryserializer.intFromBytes(bytes, position, length);
3497 position += jump; // move forward over the bytes containing the length
3498 break;
3499 case stringTopPattern: length = tag & 63; break;
3500 case integerTopPattern:
3501 value = tag & 63;
3502 break;
3503 case byteArrayTopPattern: length = tag & 63; break;
3504 case integerBottomPattern:
3505 case unsignedIntegerBottomPattern:
3506 case shortLongBottomPattern:
3507 case shortUnsignedLongBottomPattern:
3508 length = (tag & 3) + 1; // 3 = 00000011;
3509 break;
3510 case longBottomPattern:
3511 case unsignedLongBottomPattern:
3512 length = (tag & 3) + 5; // 3 = 00000011;
3513 break;
3514 case doublePattern: length = 8; break;
3515 case floatPattern: length = 4; break;
3516 case booleanTruePattern: break;
3517 case booleanFalsePattern: break;
3518 }
3519
3520 // move forward and ensure we've got those bytes
3521 if (position + length > end) {
3522 throw new Error("Unexpected: Unfinished message");
3523 }
3524
3525 switch (pattern) {
3526 case stringBottomPattern:
3527 case stringTopPattern:
3528 if (output == null) {
3529 output = new _pio.message(_pio.binaryserializer.stringFromBytes(bytes, position, length));
3530 partsInMessage++; //Add one to parts since the type of the message isn't counted as a parameter.
3531 } else {
3532 output.addString(_pio.binaryserializer.stringFromBytes(bytes, position, length));
3533 }
3534 break;
3535 case integerBottomPattern:
3536 value = _pio.binaryserializer.intFromBytes(bytes, position, length);
3537 case integerTopPattern:
3538 if (partsInMessage == 0) {
3539 //If partsInMessage is 0, then we've just started deserializing a new message, which means that
3540 //the first integer is the number of parameters in the message.
3541 partsInMessage = value;
3542 } else {
3543 output.addInt(value);
3544 }
3545 break;
3546 case byteArrayBottomPattern:
3547 case byteArrayTopPattern: output.addByteArray(bytes.slice(position, position + length)); break;
3548 case unsignedIntegerBottomPattern: output.addUInt(_pio.binaryserializer.uintFromBytes(bytes, position, length)); break;
3549 case shortLongBottomPattern:
3550 case longBottomPattern: output.addLong(_pio.binaryserializer.longFromBytes(bytes, position, length)); break;
3551 case shortUnsignedLongBottomPattern:
3552 case unsignedLongBottomPattern: output.addULong(_pio.binaryserializer.ulongFromBytes(bytes, position, length)); break;
3553 case doublePattern: output.addDouble(_pio.binaryserializer.doubleFromBytes(bytes, position, length)); break;
3554 case floatPattern: output.addFloat(_pio.binaryserializer.floatFromBytes(bytes, position, length)); break;
3555 case booleanTruePattern: output.addBoolean(true); break;
3556 case booleanFalsePattern: output.addBoolean(false); break;
3557 }
3558
3559 // move forward
3560 position += length;
3561
3562 // no parts left in the message -- then it's done!
3563 if (output != null && (--partsInMessage) == 0) {
3564 return output;
3565 }
3566 }
3567 throw new Error("Unexpected: Misaligned message");
3568 }
3569 }
3570
3571
3572 _pio.binaryserializer = {
3573 pow2 : function(n) {
3574 return (n >= 0 && n < 31) ? (1 << n) : (this.pow2[n] || (this.pow2[n] = Math.pow(2, n)));
3575 },
3576 _intEncode: function (value, bytes) {
3577
3578 var b = new Array(bytes);
3579 if (bytes == 4) {
3580 b = [(value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff];
3581 } else {
3582 if (value >= 0) {
3583 var hi = Math.floor(value / this.pow2(32));
3584 var lo = value - hi * this.pow2(32);
3585 b = [(hi >>> 24) & 0xff, (hi >>> 16) & 0xff, (hi >>> 8) & 0xff, hi & 0xff, (lo >>> 24) & 0xff, (lo >>> 16) & 0xff, (lo >>> 8) & 0xff, lo & 0xff];
3586 } else {
3587 var hi = Math.floor(value / this.pow2(32));
3588 var lo = value - hi * this.pow2(32);
3589 hi += this.pow2(32);
3590 b = [(hi >>> 24) & 0xff, (hi >>> 16) & 0xff, (hi >>> 8) & 0xff, hi & 0xff, (lo >>> 24) & 0xff, (lo >>> 16) & 0xff, (lo >>> 8) & 0xff, lo & 0xff];
3591 }
3592 }
3593 return b;
3594 },
3595 _floatEncode: function (value, mantSize, expSize) {
3596
3597 var signBit = value < 0 ? 1 : 0,
3598 exponent,
3599 mantissa,
3600 eMax = ~(-1 << (expSize - 1)),
3601 eMin = 1 - eMax;
3602
3603 if (value < 0) {
3604 value = -value;
3605 }
3606
3607 if (value === 0) {
3608 exponent = 0;
3609 mantissa = 0;
3610 } else if (isNaN(value)) {
3611 exponent = 2 * eMax + 1;
3612 mantissa = 1;
3613 } else if (value === Infinity) {
3614 exponent = 2 * eMax + 1;
3615 mantissa = 0;
3616 } else {
3617 exponent = Math.floor(Math.log(value) / Math.LN2);
3618 if (exponent >= eMin && exponent <= eMax) {
3619 mantissa = Math.floor((value * this.pow2(-exponent) - 1) * this.pow2(mantSize));
3620 exponent += eMax;
3621 } else {
3622 mantissa = Math.floor(value / this.pow2(eMin - mantSize));
3623 exponent = 0;
3624 }
3625 }
3626
3627 var b = [];
3628 while (mantSize >= 8) {
3629 b.push(mantissa % 256);
3630 mantissa = Math.floor(mantissa / 256);
3631 mantSize -= 8;
3632 }
3633 exponent = (exponent << mantSize) | mantissa;
3634 expSize += mantSize;
3635 while (expSize >= 8) {
3636 b.push(exponent & 0xff);
3637 exponent >>>= 8;
3638 expSize -= 8;
3639 }
3640 b.push((signBit << expSize) | exponent);
3641 b.reverse(); //big endian
3642 return b;
3643 },
3644 bytesFromString: function (value) {
3645 var byteArray = [];
3646 for (var i = 0; i < value.length; i++) {
3647 if (value.charCodeAt(i) <= 0x7F) {
3648 byteArray.push(value.charCodeAt(i));
3649 } else {
3650 var h = encodeURIComponent(value.charAt(i)).substr(1).split('%');
3651 for (var j = 0; j < h.length; j++) {
3652 byteArray.push(parseInt(h[j], 16));
3653 }
3654 }
3655 }
3656 return byteArray;
3657 },
3658 bytesFromInt: function (value) {
3659 return this._intEncode(value, 4);
3660 },
3661 bytesFromUInt: function (value) {
3662 return this._intEncode(value, 4);
3663 },
3664 bytesFromLong: function (value) {
3665 return this._intEncode(value, 8);
3666 },
3667 bytesFromULong: function (value) {
3668 return this._intEncode(value, 8);
3669 },
3670 bytesFromFloat: function (value) {
3671 return this._floatEncode(value, 23, 8);
3672 },
3673 bytesFromDouble: function (value) {
3674 return this._floatEncode(value, 52, 11);
3675 },
3676 //------------
3677
3678 _intDecode: function (bytes, position, length, typeBytes, signed) {
3679 var end = position + length - 1;
3680 var negate = signed && length == typeBytes && bytes[position] & 0x80;
3681 var value = 0, carry = 1;
3682 for (var i = 0; i < length; i++) {
3683 var v = bytes[end - i];
3684 if (negate) {
3685 v = (v ^ 0xff) + carry;
3686 carry = v >> 8;
3687 v = v & 0xff;
3688 }
3689 value += v * this.pow2(i*8);
3690 }
3691 value = negate ? -value : value;
3692 return value;
3693 },
3694 _float32Decode: function (bytes, position) {
3695
3696 var b = bytes.slice(position, position + 4).reverse(),
3697 sign = 1 - (2 * (b[3] >> 7)),
3698 exponent = (((b[3] << 1) & 0xff) | (b[2] >> 7)) - 127,
3699 mantissa = ((b[2] & 0x7f) << 16) | (b[1] << 8) | b[0];
3700
3701 if (exponent === 128) {
3702 if (mantissa !== 0) {
3703 return NaN;
3704 } else {
3705 return sign * Infinity;
3706 }
3707 }
3708
3709 if (exponent === -127) { // Denormalized
3710 return sign * mantissa * this.pow2(-126 - 23);
3711 }
3712
3713 return sign * (1 + mantissa * this.pow2(-23)) * this.pow2(exponent);
3714 },
3715 _float64Decode: function (bytes, position) {
3716
3717 var b = bytes.slice(position, position + 8).reverse(),
3718 sign = 1 - (2 * (b[7] >> 7)),
3719 exponent = ((((b[7] << 1) & 0xff) << 3) | (b[6] >> 4)) - ((1 << 10) - 1),
3720 mantissa = ((b[6] & 0x0f) * this.pow2(48)) + (b[5] * this.pow2(40)) + (b[4] * this.pow2(32)) +
3721 (b[3] * this.pow2(24)) + (b[2] * this.pow2(16)) + (b[1] * this.pow2(8)) + b[0];
3722
3723 if (exponent === 1024) {
3724 if (mantissa !== 0) {
3725 return NaN;
3726 } else {
3727 return sign * Infinity;
3728 }
3729 }
3730
3731 if (exponent === -1023) { // Denormalized
3732 return sign * mantissa * this.pow2(-1022 - 52);
3733 }
3734
3735 return sign * (1 + mantissa * this.pow2(-52)) * this.pow2(exponent);
3736 },
3737 stringFromBytes: function (bytes, position, length) {
3738 var str = '';
3739 for (var i = position; i < position + length; i++) str += bytes[i] <= 0x7F ?
3740 bytes[i] === 0x25 ? "%25" : // %
3741 String.fromCharCode(bytes[i]) :
3742 "%" + bytes[i].toString(16).toUpperCase();
3743 return decodeURIComponent(str);
3744 },
3745 intFromBytes: function (bytes, position, length) {
3746 return this._intDecode(bytes, position, length, 4, true);
3747 },
3748 uintFromBytes: function (bytes, position, length) {
3749 return this._intDecode(bytes, position, length, 4, false);
3750 },
3751 longFromBytes: function (bytes, position, length) {
3752 return this._intDecode(bytes, position, length, 8, true);
3753 },
3754 ulongFromBytes: function (bytes, position, length) {
3755 return this._intDecode(bytes, position, length, 8, false);
3756 },
3757 floatFromBytes: function (bytes, position, length) {
3758 if (length == 4) {
3759 return this._float32Decode(bytes, position);
3760 }
3761 return NaN;
3762 },
3763 doubleFromBytes: function (bytes, position, length) {
3764 if (length == 8) {
3765 return this._float64Decode(bytes, position);
3766 }
3767 return NaN;
3768 }
3769 }
3770
3771 // simple bas64 round trip methods for arrays of bytes (0-255)
3772 var codex = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
3773 var inverseCodex = [];
3774 for (var i = 0; i != codex.length; i++) {
3775 inverseCodex[codex.charCodeAt(i)] = i;
3776 };
3777 _pio.base64encode = function (bytes) {
3778 var output = [];
3779
3780 for (var b = 0; b < bytes.length; b++) {
3781 // pick the 3 bytes
3782 var b1 = bytes[b];
3783 var b2 = ++b <= bytes.length ? bytes[b] : NaN;
3784 var b3 = ++b <= bytes.length ? bytes[b] : NaN;
3785
3786 // encode them together
3787 var enc1 = b1 >> 2;
3788 var enc2 = ((b1 & 3) << 4) | (b2 >> 4);
3789 var enc3 = ((b2 & 15) << 2) | (b3 >> 6);
3790 var enc4 = b3 & 63;
3791
3792 // overflow w. /
3793 if (isNaN(b2)) {
3794 enc3 = enc4 = 64;
3795 } else if (isNaN(b3)) {
3796 enc4 = 64;
3797 }
3798
3799 output.push(codex.charAt(enc1));
3800 output.push(codex.charAt(enc2));
3801 output.push(codex.charAt(enc3));
3802 output.push(codex.charAt(enc4));
3803 }
3804
3805 return output.join("")
3806 }
3807
3808 _pio.base64decode = function (string) {
3809 var output = [];
3810 for (var c = 0; c < string.length; c++) {
3811 // pick the 4 characters representing 3 bytes
3812 var chr1 = inverseCodex[string.charCodeAt(c)];
3813 var chr2 = ++c < string.length ? inverseCodex[string.charCodeAt(c)] : 64;
3814 var chr3 = ++c < string.length ? inverseCodex[string.charCodeAt(c)] : 64;
3815 var chr4 = ++c < string.length ? inverseCodex[string.charCodeAt(c)] : 64;
3816
3817 // encode them together
3818 var b1 = (chr1 << 2) | (chr2 >> 4);
3819 var b2 = ((chr2 & 15) << 4) | (chr3 >> 2);
3820 var b3 = ((chr3 & 3) << 6) | chr4;
3821
3822 output.push(b1);
3823 if (chr3 != 64) {
3824 output.push(b2);
3825 if (chr4 != 64) {
3826 output.push(b3);
3827 }
3828 }
3829 }
3830 return output;
3831 }
3832})();
3833(function () {
3834 /**
3835 * @class The Achievements service. This class is used to update the progress of an achievement for the
3836 * current user, and loading achievements of other users. All achievements have to be defined in the
3837 * admin panel first.
3838 * @example Refresh to get the user's current achievements and the progress of each:
3839 * <listing>
3840 * client.achievements.refresh(function() {
3841 * //Print all achievements and their progress
3842 * for (var i=0; i!=client.achievements.myAchievements.length; i++) {
3843 * var achievement = client.achievements.myAchievements[i];
3844 * console.log("[ " + achievement.title + " ]");
3845 * console.log(achievement.description);
3846 * console.log(achievement.progress);
3847 * console.log(achievement.progressGoal);
3848 * console.log(achievement.progressRatio);
3849 * }
3850 * }, function(error) {
3851 * console.log(error);
3852 * });
3853 *</listing>
3854 * @example Load achivements for other users:
3855 * <listing>
3856 * client.achievements.load(['connectUserId1', 'connectUserId2'], function(result) {
3857 * for (var user in result) {
3858 * var list = result[user];
3859 * console.log(user);
3860 * for (var i = 0; i != list.length; i++) {
3861 * var achievement = list[i];
3862 * console.log("[ "+ achievement.title +" ]");
3863 * console.log(achievement.description);
3864 * console.log(achievement.progress);
3865 * console.log(achievement.progressGoal);
3866 * console.log(achievement.progressRatio);
3867 * }
3868 * }
3869 * }, function(error) {
3870 * console.log(error);
3871 * });
3872 *</listing>
3873 * @example Set the progress of the achievement "fiveinarow" to 2.
3874 * <listing>
3875 * client.achievements.progressSet('fiveinarow', 2, function(achievement) {
3876 * console.log(achievement);
3877 * }, function(error) {
3878 * console.log(error);
3879 * });
3880 *</listing>
3881 */
3882 _pio.achievements = function (channel) {
3883 var currentVersion = null;
3884 /** The list of achievements for the current user. You must call refresh() first to initialize this list.
3885 * @type achievement[]
3886 */
3887 this.myAchievements = "[ERROR: You tried to access achievements.myAchievements before loading them. You have to call the refresh method first.]";
3888 this.onCompleteHandlers = [];
3889 var self = this;
3890
3891 /**
3892 * Add an OnComplete event handler that will be called every time an achievement is completed.
3893 * @param {function(achievement)} onComplete The function to add.
3894 */
3895 this.addOnComplete = function (onCompleteHandler) {
3896 if (typeof onCompleteHandler === 'function' && onCompleteHandler.length == 1) {
3897 self.onCompleteHandlers.push(onCompleteHandler);
3898 } else {
3899 throw new PlayerIOError(PlayerIOErrorCode.InvalidArgument, "Expects argument to be a function that takes an achievement as an argument.");
3900 }
3901 }
3902
3903 /**
3904 * Get an achievement by id
3905 * @param {string} achievementId Id of the achievement to get.
3906 */
3907 this.get = function (achievementId) {
3908 if (typeof self.myAchievements === 'string') { return null; }
3909 for (var i = 0; i < self.myAchievements.length; i++) {
3910 if (self.myAchievements[i].id == achievementId) {
3911 return self.myAchievements[i];
3912 }
3913 }
3914 return null;
3915 }
3916
3917 /**
3918 * Refresh the list of achievements.
3919 * @param {function()} successCallback Callback function that will be called when the refresh is complete
3920 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
3921 */
3922 this.refresh = function (successCallback, errorCallback) {
3923 channel.achievementsRefresh(currentVersion, successCallback, errorCallback, function (result) {
3924 if (currentVersion != result.version) {
3925 currentVersion = result.version;
3926 if (result.achievements == null || result.achievements.length == 0) {
3927 self.myAchievements = [];
3928 } else {
3929 var ach = [];
3930 for (var i = 0; i < result.achievements.length; i++) {
3931 var item = result.achievements[i];
3932 ach.push(new _pio.achievement(item.identifier, item.title, item.description, item.imageurl, item.progress, item.progressgoal, item.lastupdated));
3933 }
3934 self.myAchievements = ach;
3935 }
3936 }
3937 });
3938 }
3939
3940 /**
3941 * Load the achivements for multiple users by their connectUserId
3942 * @param {string[]} userIds The list of users to load achievements for.
3943 * @param {function(result)} successCallback Callback function that will be called with the loaded achievements.
3944 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
3945 */
3946 this.load = function (userIds, successCallback, errorCallback) {
3947 if (typeof (userIds) != 'object' && !requests.length) {
3948 var e = _pio.error("The first argument to load should be an array: client.achievements.load(['user1', 'user2', ...], ...)");
3949 _pio.handleError(e, errorCallback, e.code, e.message);
3950 return;
3951 }
3952
3953 channel.achievementsLoad(userIds, successCallback, errorCallback, function (result) {
3954 if (result == null || result.length == 0) { return {}; }
3955 var users = {};
3956 for (var i = 0; i < result.userachievements.length; i++) {
3957 var user = result.userachievements[i];
3958 var ach = [];
3959 for (var j = 0; j < user.achievements.length; j++) {
3960 var item = user.achievements[j];
3961 ach.push(new _pio.achievement(item.identifier, item.title, item.description, item.imageurl, item.progress, item.progressgoal, item.lastupdated));
3962 }
3963 users[user.userid] = ach;
3964 }
3965 return users;
3966 });
3967 }
3968
3969 /**
3970 * Sets the progress of the specified achievement
3971 * @param {string} achievementId The id of the achievement to set the progress for.
3972 * @param {Number} progress The value to set the progress to
3973 * @param {function(achievement)} successCallback Callback function that will be called when the operation has completed.
3974 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
3975 */
3976 this.progressSet = function (achievementId, progress, successCallback, errorCallback) {
3977 channel.achievementsProgressSet(achievementId, progress, successCallback, errorCallback, function (result) {
3978 return update(result.achievement, result.completednow);
3979 });
3980 }
3981
3982 /**
3983 * Adds the delta to the progress of the specified achievement
3984 * @param {string} achievementId The id of the achievement to add to the progress for.
3985 * @param {Number} progressDelta The value to add to the progress.
3986 * @param {function(achievement)} successCallback Callback function that will be called when the operation has completed.
3987 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
3988 */
3989 this.progressAdd = function (achievementId, progressDelta, successCallback, errorCallback) {
3990 channel.achievementsProgressAdd(achievementId, progressDelta, successCallback, errorCallback, function (result) {
3991 return update(result.achievement, result.completednow);
3992 });
3993 }
3994
3995 /**
3996 * Sets the progress of the specified achievement to the bigger value of the current value and the given value.
3997 * @param {string} achievementId The id of the achievement to set the progress for.
3998 * @param {Number} progress The value to set the progress to, if it's bigger than the current value.
3999 * @param {function(achievement)} successCallback Callback function that will be called when the operation has completed.
4000 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4001 */
4002 this.progressMax = function (achievementId, progress, successCallback, errorCallback) {
4003 channel.achievementsProgressMax(achievementId, progress, successCallback, errorCallback, function (result) {
4004 return update(result.achievement, result.completednow);
4005 });
4006 }
4007
4008 /**
4009 * Completes the specified achievement by setting the progress to the progress goal.
4010 * @param {string} achievementId The id of the achievement to complete.
4011 * @param {function(achievement)} successCallback Callback function that will be called when the operation has completed.
4012 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4013 */
4014 this.progressComplete = function (achievementId, successCallback, errorCallback) {
4015 channel.achievementsProgressComplete(achievementId, successCallback, errorCallback, function (result) {
4016 return update(result.achievement, result.completednow);
4017 });
4018 }
4019
4020 function update(achievement, completednow) {
4021 //Convert received achievement data into client achievement object
4022 var ach = new _pio.achievement(achievement.identifier, achievement.title, achievement.description, achievement.imageurl, achievement.progress, achievement.progressgoal, achievement.lastupdated);
4023 //Update myAchievements, replacing the old if we have it.
4024 if (typeof self.myAchievements !== 'string') {
4025 for (var i = 0; i < self.myAchievements.length; i++) {
4026 if (self.myAchievements[i].id == ach.id) {
4027 self.myAchievements[i] = ach;
4028 //Clear version, because we can't be sure our current local state is the latest.
4029 self.currentVersion = null;
4030 }
4031 }
4032 }
4033
4034 //Fire event on complete.
4035 if (completednow) {
4036 for (var i = 0; i < self.onCompleteHandlers.length; i++) {
4037 var handler = self.onCompleteHandlers[i];
4038 handler(ach);
4039 }
4040 }
4041
4042 return ach;
4043 }
4044 }
4045
4046 /**
4047 * @class This class encapsulates all the data of a single achievement.
4048 */
4049 _pio.achievement = function (id, title, description, imageUrl, progress, progressGoal, lastUpdated) {
4050 /** The id of this achievement.
4051 * @type string
4052 */
4053 this.id = id;
4054 /** The title of this achievement.
4055 * @type string
4056 */
4057 this.title = title;
4058 /** The description of this achievement.
4059 * @type string
4060 */
4061 this.description = description;
4062 /** The image url of this achievement.
4063 * @type string
4064 */
4065 this.imageUrl = imageUrl;
4066 /** The progress of this achievement.
4067 * @type Number
4068 */
4069 this.progress = (typeof progress === 'undefined') ? 0 : progress;
4070 /** The progress goal of this achievement.
4071 * @type Number
4072 */
4073 this.progressGoal = progressGoal;
4074 /** When this achievement was last updated.
4075 * @type Date
4076 */
4077 this.lastUpdated = new Date(lastUpdated * 1000);
4078 /** The progress ratio of this achievement.
4079 * @type Number
4080 */
4081 this.progressRatio = this.progress / this.progressGoal;
4082 /** If this achievement is completed.
4083 * @type bool
4084 */
4085 this.completed = (this.progress == this.progressGoal);
4086 }
4087})();
4088
4089(function () {
4090 /**
4091 * @class The PlayerInsight service. This class is used for setting segments for the current user and tracking
4092 * events in PlayerInsight.
4093 * @example Load PlayerInsight data:
4094 * <listing>
4095 * client.playerInsight.refresh(function() {
4096 * //How many users are online in this game?
4097 * console.log(client.playerInsight.playersOnline);
4098 * //What segments are set on the current user?
4099 * console.log(client.playerInsight.segments);
4100 * }, function(error) {
4101 * console.log(error);
4102 * });
4103 * </listing>
4104 * @example Set segments on the current user:
4105 * <listing>
4106 * client.playerInsight.setSegments({browser:'chrome', build:'v23'}, function() {
4107 * //Success
4108 * console.log(client.playerInsight.segments);
4109 * }, function(error) {
4110 * console.log(error);
4111 * });
4112 * </listing>
4113 * @example Track a $1 purchase:
4114 * <listing>
4115 * client.playerInsight.trackExternalPayment('USD', 100, function (result) {
4116 * //Success
4117 * }, function(error) {
4118 * console.log(error);
4119 * });
4120 * </listing>
4121 */
4122 _pio.playerInsight = function (channel) {
4123 /** The number of users online in this game. You must call refresh() to initialize this number.
4124 * @type Number
4125 */
4126 this.playersOnline = "[ERROR: You tried to access playerInsight.playersOnline before loading it. You have to call the refresh method first.]";
4127
4128 /** The list of PlayerInsight segments that are set on this user. You must call refresh() to initialize this list.
4129 * @type Object
4130 */
4131 this.segments = {};
4132 var self = this;
4133
4134 /**
4135 * Refresh the players online counter and the current segments of the user.
4136 * @param {function()} successCallback Callback function that will be called when the refresh is complete.
4137 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4138 */
4139 this.refresh = function (successCallback, errorCallback) {
4140 channel.playerInsightRefresh(successCallback, errorCallback, function (result) {
4141 setState(result.state);
4142 });
4143 }
4144
4145 /**
4146 * Assign custom PlayerInsight segments to the current user
4147 * @param {Object} segments Custom segments for the user in PlayerInsight
4148 * @param {function()} successCallback Callback function that will be called when the operation is complete.
4149 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4150 */
4151 this.setSegments = function (segments, successCallback, errorCallback) {
4152 channel.playerInsightSetSegments(_pio.convertToSegmentArray(segments), successCallback, errorCallback, function (result) {
4153 setState(result.state);
4154 });
4155 }
4156
4157 /**
4158 * Tell PlayerInsight who invited the current user in this session. Used for viral analysis.
4159 * @param {string} invitingUserId The connectUserId of the user who invited this user.
4160 * @param {string} invitationChannel An identifier for the channel the invitation was received over, like 'email' or 'fb_invite'.
4161 * @param {function()} successCallback Callback function that will be called when the operation is complete.
4162 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4163 */
4164 this.trackInvitedBy = function (invitingUserId, invitationChannel, successCallback, errorCallback) {
4165 channel.playerInsightTrackInvitedBy(invitingUserId, invitationChannel, successCallback, errorCallback, function (result) { });
4166 }
4167
4168 /**
4169 * Track the given event for the current user in PlayerInsight
4170 * @param {string} eventType The name of the event to track.
4171 * @param {Number} value The amount to add to the counter value.
4172 * @param {function()} successCallback Callback function that will be called when the operation is complete.
4173 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4174 */
4175 this.trackEvent = function (eventType, value, successCallback, errorCallback) {
4176 channel.playerInsightTrackEvents([{eventtype:eventType, value:value}], successCallback, errorCallback, function (result) { });
4177 }
4178
4179 /**
4180 * Track a completed payment in PlayerInsight from an external non-PayVault payment method.
4181 * @param {string} currency The currency the payment was made in.
4182 * @param {string} amount The amount of the payment in the given currency.
4183 * @param {function()} successCallback Callback function that will be called when the operation is complete.
4184 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4185 */
4186 this.trackExternalPayment = function (currency, amount, successCallback, errorCallback) {
4187 channel.playerInsightTrackExternalPayment(currency, amount, successCallback, errorCallback, function (result) { });
4188 }
4189
4190 /**
4191 * Keep the PlayerInsight session alive. Call this method if you know the game hasn't made any other API requests in the last 20 minutes, and the player is still playing.
4192 * @param {function()} successCallback Callback function that will be called when the operation is complete.
4193 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4194 */
4195 this.sessionKeepAlive = function (successCallback, errorCallback) {
4196 channel.playerInsightSessionKeepAlive(successCallback, errorCallback, function (result) { });
4197 }
4198
4199 function setState(state) {
4200 if (state.playersonline == -1) {
4201 self.playersOnline = "[ERROR: The current connection does not have the rights required to read the playersonline variable.]";
4202 } else {
4203 self.playersOnline = state.playersonline;
4204 }
4205 self.segments = _pio.convertFromKVArray(state.segments);
4206 }
4207 }
4208})();
4209(function () {
4210 /**
4211 * @class The OneScore service. This class is used to update the score of the current user, and loading the
4212 * scores of other users.
4213 * @example Load the current user's OneScore and print the details:
4214 * <listing>
4215 * client.oneScore.refresh(function() {
4216 * console.log(client.oneScore.score);
4217 * console.log(client.oneScore.percentile);
4218 * console.log(client.oneScore.topRank);
4219 * }, function(error) {
4220 * console.log(error);
4221 * });
4222 * </listing>
4223 * @example Setting the current user's OneScore:
4224 * <listing>
4225 *client.oneScore.set(5, function(result) {
4226 * //Success
4227 * }, function(error) {
4228 * console.log(error);
4229 * });
4230 * </listing>
4231 * @example Adding to the current user's OneScore:
4232 * <listing>
4233 *client.oneScore.add(10, function(result) {
4234 * //Success
4235 * }, function(error) {
4236 * console.log(error);
4237 * });
4238 * </listing>
4239 * @example Loading other users' OneScore:
4240 * <listing>
4241 *client.oneScore.load(['UserId1', 'UserId2', 'Bob', 'Bobbelina'], function(result) {
4242 * //Print out the scores for each user
4243 * for (var i = 0; i != result.length; i++) {
4244 * console.log(result[i]);
4245 * }
4246 * }, function(error) {
4247 * console.log(error);
4248 * });
4249 * </listing>
4250 */
4251 _pio.oneScore = function (channel) {
4252 /** The percentile compared to all other players. A value from 0 -> 100. A value of 30.0 means you are in the bottom 30% of players. A value of 100 means you are in the top 1% with other players. You must call refresh() first to initialize this value.
4253 * @type Number
4254 */
4255 this.percentile = "[ERROR: You tried to access oneScore.percentile before loading the OneScore. You have to call the refresh method first.]";
4256
4257 /** The score. You must call refresh() first to initialize this value.
4258 * @type Number
4259 */
4260 this.score = "[ERROR: You tried to access oneScore.score before loading the OneScore. You have to call the refresh method first.]";
4261
4262 /** The absolute ranking number -- if you are one of the N top players, then it returns N. 1 means you are the best. Returns 0 if you are not one the top N players. (N is currently 1000.) You must call refresh() first to initialize this value.
4263 * @type Number
4264 */
4265 this.topRank = "[ERROR: You tried to access oneScore.topRank before loading the OneScore. You have to call the refresh method first.]";
4266 var self = this;
4267
4268 /**
4269 * Refresh the OneScore of the current user.
4270 * @param {function()} successCallback Callback function that will be called when the refresh is complete
4271 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
4272 */
4273 this.refresh = function (successCallback, errorCallback) {
4274 channel.oneScoreRefresh(successCallback, errorCallback, function (result) {
4275 var score = new _pio.oneScoreValue(result.onescore.percentile, result.onescore.score, result.onescore.toprank);
4276 self.percentile = score.percentile;
4277 self.score = score.score;
4278 self.topRank = score.toprank;
4279 });
4280 }
4281
4282 /**
4283 * Sets the OneScore for the user.
4284 * @param {number} score The score to set for the user.
4285 * @param {function(oneScoreValue)} successCallback Callback function that will be called when the operation has been completed.
4286 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4287 */
4288 this.set = function (score, successCallback, errorCallback) {
4289 channel.oneScoreSet(score, successCallback, errorCallback, function (result) {
4290 var score = new _pio.oneScoreValue(result.onescore.percentile, result.onescore.score, result.onescore.toprank);
4291 self.percentile = score.percentile;
4292 self.score = score.score;
4293 self.topRank = score.toprank;
4294 return score;
4295 });
4296 }
4297
4298 /**
4299 * Adds the score to the OneScore for the user.
4300 * @param {number} score The score to add for the user.
4301 * @param {function(oneScoreValue)} successCallback Callback function that will be called when the operation has been completed.
4302 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4303 */
4304 this.add = function (score, successCallback, errorCallback) {
4305 channel.oneScoreAdd(score, successCallback, errorCallback, function (result) {
4306 var score = new _pio.oneScoreValue(result.onescore.percentile, result.onescore.score, result.onescore.toprank);
4307 self.percentile = score.percentile;
4308 self.score = score.score;
4309 self.topRank = score.toprank;
4310 return score;
4311 });
4312 }
4313
4314 /**
4315 * Load the OneScores for multiple users by their connectUserId
4316 * @param {string[]} userIds The list of users to load scores for.
4317 * @param {function(oneScoreValue[])} successCallback Callback function that will be called with the loaded scores.
4318 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4319 */
4320 this.load = function (userIds, successCallback, errorCallback) {
4321 if (typeof (userIds) != 'object' && !requests.length) {
4322 var e = _pio.error("The first argument to load should be an array: client.oneScore.load(['user1', 'user2', ...], ...)");
4323 _pio.handleError(e, errorCallback, e.code, e.message);
4324 return;
4325 }
4326
4327 channel.oneScoreLoad(userIds, successCallback, errorCallback, function (result) {
4328 if (result == null || result.onescores == null || result.onescores.length == 0) { return []; }
4329 var scores = [];
4330 var j = 0;
4331 for (var i = 0; i < userIds.length; i++) {
4332 var score = result.onescores[j];
4333 if (score && userIds[i] == score.userid) {
4334 scores.push(new _pio.oneScoreValue(score.percentile, score.score, score.toprank));
4335 j++;
4336 } else {
4337 scores.push(null);
4338 }
4339 }
4340 return scores;
4341 });
4342 }
4343 }
4344
4345 /**
4346 * @class This class encapsulates the OneScore value for a single user. It contains the user's score, as well as the user's global rank.
4347 */
4348 _pio.oneScoreValue = function (percentile, score, topRank) {
4349 /** The percentile compared to all other players. A value from 0 -> 100. A value of 30.0 means you are in the bottom 30% of players. A value of 100 means you are in the top 100% with other players.
4350 * @type Number
4351 */
4352 this.percentile = (typeof percentile === 'undefined') ? 0 : percentile;
4353 /** The score.
4354 * @type Number
4355 */
4356 this.score = (typeof score === 'undefined') ? 0 : score;
4357 /** The absolute ranking number -- if you are one of the N top players, then it returns N. 1 means you are the best. Returns 0 if you are not one the top N players. (N is currently 1000.)
4358 * @type Number
4359 */
4360 this.topRank = (typeof topRank === 'undefined') ? 0 : topRank;
4361 }
4362})();
4363(function () {
4364 /**
4365 * @class The Notifications service. This class is used for registering mobile endpoints and sending
4366 * mobile push notifications to other users.
4367 * @example Refresh and list the user's registered endpoints:
4368 * <listing>
4369 * client.notifications.refresh(function() {
4370 * for (var i = 0; i != client.notifications.myEndpoints.length; i++) {
4371 * console.log(client.notifications.myEndpoints[i])
4372 * }
4373 * }, function(error) {
4374 * console.log(error);
4375 * });
4376 * </listing>
4377
4378 * @example Registering a notification endpoint for the current client:
4379 * <listing>
4380 *client.notifications.registerEndpoint(
4381 * 'test', //Type
4382 * 'identifier', //Device identifier
4383 * { configuration: 'goes here' }, //Optional configuration
4384 * true, //Initially enabled
4385 * function () {
4386 * //Success
4387 * }, function(error) {
4388 * console.log(error);
4389 * }
4390 * );
4391 * </listing>
4392
4393 * @example Sending a notification:
4394 * <listing>
4395 * client.notifications.send([{ endpointType: 'test', recipientUserId: 'someUserId', message: 'Hello world!' }], function () {
4396 * //Notification sent
4397 * }, function(error) {
4398 * console.log(error);
4399 * });
4400 * </listing>
4401 */
4402 _pio.notifications = function (channel) {
4403 /** The list of registered endpoints for the current user. You must call refresh() first to initialize this list.
4404 * @type notificationEndpoint[]
4405 */
4406 this.myEndpoints = "[ERROR: You tried to access notifications.myEndpoints before calling refresh.]";
4407 var self = this;
4408 var version = ""
4409
4410 function refreshed(result) {
4411 if( result.version != version){
4412 var list = []
4413 if(result.endpoints){
4414 for (var i = 0; i != result.endpoints.length; i++) {
4415 var e = result.endpoints[i]
4416 list[i] = new _pio.notificationEndpoint(e.type, e.identifier, _pio.convertFromKVArray(e.configuration), e.enabled?true:false)
4417 }
4418 }
4419 version = result.version;
4420 self.myEndpoints = list;
4421 }
4422 }
4423
4424 function equalConfiguration(c1, c2) {
4425 return JSON.stringify(c1) == JSON.stringify(c2);
4426 }
4427
4428 function get(type, identifier) {
4429 if (version != "") {
4430 for (var i = 0; i != self.myEndpoints.length; i++) {
4431 var ep = self.myEndpoints[i]
4432 if (ep.type == type && ep.identifier == identifier) {
4433 return ep
4434 }
4435 }
4436 }
4437 return null
4438 }
4439
4440 function getIds(endpoints) {
4441 var result = []
4442 if (endpoints && endpoints.length > 0) {
4443 for (var i = 0; i != endpoints.length; i++) {
4444 var ep = endpoints[i]
4445 if (ep.type && ep.identifier) {
4446 result.push({type:ep.type, identifier:ep.identifier})
4447 }
4448 }
4449 }
4450 return result;
4451 }
4452
4453 /**
4454 * Refresh the notification endpoints of the current user.
4455 * @param {function()} successCallback Callback function that will be called when the refresh is complete
4456 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs
4457 */
4458 this.refresh = function (successCallback, errorCallback) {
4459 channel.notificationsRefresh(version, successCallback, errorCallback, refreshed)
4460 }
4461
4462 /**
4463 * Register a device or endpoint with Player.IO
4464 * @param {string} endpointType Endpoint type, such as, IOS, Android, email, etc.
4465 * @param {string} identifier The identifier of the endpoint, such as the device token on iOS or an email address for email.
4466 * @param {object} configuration Configuration relating to the endpoint. Can be null.
4467 * @param {bool} enabled Should the endpoint be enabled
4468 * @param {function()} successCallback Callback function that will be called when the operation has been completed.
4469 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4470 */
4471 this.registerEndpoint = function (endpointType, identifier, configuration, enabled, successCallback, errorCallback) {
4472 var current = get(endpointType, identifier)
4473 if (current == null || current.enabled != enabled || !equalConfiguration(current.configuration, configuration)) {
4474 channel.notificationsRegisterEndpoints(version, [{ type: endpointType, identifier: identifier, configuration: _pio.convertToKVArray(configuration), enabled: enabled }], successCallback, errorCallback, refreshed)
4475 }else if(successCallback){ successCallback() }
4476 }
4477
4478 /**
4479 * Enables or disabled the given endpoints
4480 * @param {notificationEndpoint[]} endpoints The endpoints to enable or disable.
4481 * @param {bool} enabled If true, all the given endpoints will be enabled. If false, they'll be disabled.
4482 * @param {function()} successCallback Callback function that will be called when the operation has been completed.
4483 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4484 */
4485 this.toggleEndpoints = function (endpoints, enable, successCallback, errorCallback) {
4486 var ids = getIds(endpoints)
4487 if (ids.length > 0) {
4488 channel.notificationsToggleEndpoints(version, ids, enable?true:false, successCallback, errorCallback, refreshed)
4489 }else if(successCallback){ successCallback() }
4490 }
4491
4492 /**
4493 * Deletes the given endpoints
4494 * @param {notificationEndpoint[]} endpoints The endpoints to delete
4495 * @param {function()} successCallback Callback function that will be called when the operation has been completed.
4496 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4497 */
4498 this.deleteEndpoints = function (endpoints, successCallback, errorCallback) {
4499 var ids = getIds(endpoints)
4500 if (ids.length > 0) {
4501 channel.notificationsDeleteEndpoints(version, ids, successCallback, errorCallback, refreshed)
4502 }else if(successCallback){ successCallback() }
4503 }
4504
4505 /**
4506 * Send one or more notifications
4507 * @param {object[]} notifications The notifications to send
4508 * @param {function()} successCallback Callback function that will be called when the operation has been completed.
4509 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4510 */
4511 this.send = function (notifications, successCallback, errorCallback) {
4512 var toSend = []
4513 for(var i=0;i!=notifications.length;i++){
4514 var s = notifications[i]
4515 var n = { recipient: s.recipientUserId, endpointtype: s.endpointType, data: {} }
4516 if( (n.recipient+'').length == 0 || (n.endpointtype+'').length == 0){
4517 console.log("error")
4518 }
4519 for(var x in s){
4520 if(x != 'recipientUserId' && x != 'endpointType'){
4521 n.data[x] = s[x]
4522 }
4523 }
4524 toSend[i] = n
4525 }
4526 if (toSend.length > 0) {
4527 channel.notificationsSend(toSend, successCallback, errorCallback, null)
4528 }else if(successCallback){ successCallback() }
4529 }
4530 }
4531
4532 /**
4533 * @class This class encapsulates all the data of a notification endpoint.
4534 */
4535 _pio.notificationEndpoint = function (type, identifier, configuration, enabled) {
4536 /** The type of the endpoint
4537 * @type string
4538 */
4539 this.type = type
4540 /** The endpoint identifier (e.g, push device id or e-mail address...)
4541 * @type string
4542 */
4543 this.identifier = identifier
4544 /** The configuration of the endpoint
4545 * @type object
4546 */
4547 this.configuration = configuration;
4548 /** Is this endpoint currently enabled?
4549 * @type bool
4550 */
4551 this.enabled = enabled
4552 }
4553})();
4554(function () {
4555 /**
4556 * @class The PlayerIO Publishing Network service. This class is used to access all the functionality available to games
4557 * that are published on the PlayerIO Publishing Network.
4558 * @example Refreshing data
4559 * <listing>
4560 * client.publishingNetwork.refresh(function(){
4561 * // refresh succeeded
4562 * }, function (err) { console.log(err) })
4563 * </listing>
4564 */
4565 _pio.publishingNetwork = function (channel, connectUserId) {
4566 var self = this;
4567 /**
4568 * Access to the PlayerIO Publishing Network profiles
4569 * @type publishingNetworkProfiles
4570 */
4571 this.profiles = new _pio.publishingNetworkProfiles(channel);
4572
4573 /**
4574 * Access to PlayerIO Publishing Network payments
4575 * @type publishingNetworkPayments
4576 */
4577 this.payments = new _pio.publishingNetworkPayments(channel);
4578
4579 /**
4580 * Access to PlayerIO Publishing Network relations
4581 * @type publishingNetworkRelations
4582 */
4583 this.relations = new _pio.publishingNetworkRelations(channel, connectUserId, this);
4584
4585 /**
4586 * A UserToken that can be used to authenticate as the current user.
4587 * @type string
4588 */
4589 this.userToken = "[ERROR: you tried to access publishingNetwork.userToken before calling publishingNetwork.refresh(callback)]"
4590
4591 /**
4592 * Refresh the Profiles and Relations
4593 * @param {function()} successCallback Callback function that will be called when the refresh is complete.
4594 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4595 */
4596 this.refresh = function (successCallback, errorCallback) {
4597 channel.socialRefresh(successCallback, errorCallback, function (result) {
4598 self.userToken = result.myprofile.usertoken
4599 self.profiles.myProfile = new _pio.publishingNetworkProfile(result.myprofile)
4600
4601 if(typeof(_pio.friendLookup)=='undefined'){
4602 _pio.friendLookup = {}
4603 _pio.blockedLookup = {}
4604 }
4605
4606 var fl = _pio.friendLookup[self.profiles.myProfile.userId]
4607 var bl = _pio.blockedLookup[self.profiles.myProfile.userId]
4608 if (!fl && !bl) {
4609 fl = _pio.friendLookup[self.profiles.myProfile.userId] = {}
4610 bl = _pio.blockedLookup[self.profiles.myProfile.userId] = {}
4611 }
4612
4613 self.relations.friends = []
4614 for (var i = 0; i != result.friends.length; i++) {
4615 var f = new _pio.publishingNetworkProfile(result.friends[i])
4616 self.relations.friends.push(f)
4617 fl[f.userId] = true
4618 }
4619 for (var i = 0; i != result.blocked.length; i++) {
4620 bl[result.blocked[i]] = true
4621 }
4622 });
4623 }
4624 }
4625
4626 _pio.showDialog = function(dialog, channel, args, closedCallback){
4627 if (typeof (window.PublishingNetwork) == 'undefined') {
4628 throw new PlayerIOError(PlayerIOErrorCode.PublishingNetworkNotAvailable, "PublishingNetwork.js was not found on the current page. You have to include the 'piocdn.com/publishingnetwork.js' on the containing page to show dialogs. See http://playerio.com/documentation/ for more information.")
4629 } else {
4630 args.__apitoken__ = channel.token
4631 window.PublishingNetwork.dialog(dialog, args, closedCallback)
4632 }
4633 }
4634})();
4635(function () {
4636 /**
4637 * @class The PlayerIO Publishing Network Payments service. This class is used to initiate in-game payments for games
4638 * that are published on the PlayerIO Publishing Network.
4639 * @example Showing a buy coins dialog
4640 * <listing>
4641 * client.publishingNetwork.payments.showBuyCoinsDialog(200, {
4642 * name: 'Buy 200 coins',
4643 * description: '...',
4644 * icon: 'http://server.com/icon.jpg',
4645 * currency:'USD'
4646 * },function(){
4647 * // payment success
4648 * }, function (err) { console.log(err) })
4649 * </listing>
4650 * @example Showing a buy items dialog
4651 * <listing>
4652 * client.publishingNetwork.payments.showBuyItemsDialog([
4653 * {itemkey:'myitem'}
4654 * ],{
4655 * name: 'Buy 200 coins',
4656 * description: '...',
4657 * icon: 'http://server.com/icon.jpg',
4658 * currency:'USD'
4659 * },function(){
4660 * // payment success
4661 * }, function (err) { console.log(err) })
4662 * </listing>
4663 */
4664 _pio.publishingNetworkPayments = function (channel) {
4665 var self = this;
4666
4667 /**
4668 * Shows a dialog for buying coins
4669 * @param {int} coinAmount The amount of coins to buy
4670 * @param {object} purchaseArguments Any additional information that is needed to show the payment dialog
4671 * @param {function(result)} successCallback Callback function that will be called with the result
4672 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4673 */
4674 this.showBuyCoinsDialog = function (coinAmount, purchaseArguments, successCallback, errorCallback) {
4675 if (!purchaseArguments) {
4676 purchaseArguments = {}
4677 }
4678 purchaseArguments.coinamount = coinAmount
4679 channel.payVaultPaymentInfo('publishingnetwork', _pio.convertToKVArray(purchaseArguments), null, function(result){
4680 _pio.showDialog('buy', channel, result, function (r) {
4681 if (r.error) {
4682 errorCallback(new PlayerIOError(PlayerIOErrorCode.GeneralError, r.error))
4683 } else {
4684 successCallback(r)
4685 }
4686 })
4687 }, errorCallback, function (result) { return _pio.convertFromKVArray(result.providerarguments) })
4688 }
4689
4690 /**
4691 * Shows a dialog for buying items
4692 * @param {object[]} items A list of items to buy, together with any additional payload.
4693 * @param {object} purchaseArguments Any additional information that is needed to show the payment dialog
4694 * @param {function(result)} successCallback Callback function that will be called with the result
4695 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4696 */
4697 this.showBuyItemsDialog = function (items, purchaseArguments, successCallback, errorCallback) {
4698 channel.payVaultPaymentInfo('publishingnetwork', _pio.convertToKVArray(purchaseArguments||{}), _pio.convertBuyItems(items), function(result){
4699 _pio.showDialog('buy',channel, result, function (r) {
4700 if (r.error) {
4701 errorCallback(new PlayerIOError(PlayerIOErrorCode.GeneralError, r.error))
4702 } else {
4703 successCallback(r)
4704 }
4705 })
4706 }, errorCallback, function (result) { return _pio.convertFromKVArray(result.providerarguments) })
4707 }
4708 }
4709})();
4710(function () {
4711 /**
4712 * @class PlayerIO Publishing Network Profiles service. This class is used to fetch or show the profile of
4713 * the current user, or to load the profiles of other users.
4714 */
4715 _pio.publishingNetworkProfiles = function (channel) {
4716 var self = this;
4717
4718 /**
4719 * The profile of the current user
4720 * @type publishingNetworkProfile
4721 */
4722 this.myProfile = "[ERROR: you tried to access publishingNetworkProfiles.myProfile before calling publishingNetwork.refresh(callback)]"
4723
4724 /**
4725 * Show the profile for the specified user
4726 * @param {string} userId The userId of the user to show
4727 * @param {function()} closedCallback Callback function that will be called when the profile is closed
4728 */
4729 this.showProfile = function (userId, closedCallback) {
4730 _pio.showDialog('profile', channel, { userId: userId }, closedCallback)
4731 }
4732
4733 /**
4734 * Load a set of PlayerIO Publishing Network profiles
4735 * @param {string[]} userIds The userIds of the profiles to load
4736 * @param {function(publishingNetworkProfile[])} successCallback Callback function that will be called with the loaded profiles
4737 * @param {function(PlayerIOError)} errorCallback Callback function that will be called if an error occurs.
4738 */
4739 this.loadProfiles = function(userIds, successCallback, errorCallback){
4740 channel.socialLoadProfiles(userIds, successCallback, errorCallback, function (result) {
4741 var arr = []
4742 for (var i = 0; i != userIds.length; i++) {
4743 var userId = userIds[i];
4744 arr[i] = null
4745 for (var x = 0; x != result.profiles.length; x++) {
4746 var user = result.profiles[x]
4747 if (user && user.userid == userId) {
4748 arr[i] = new _pio.publishingNetworkProfile(result.profiles[x])
4749 break
4750 }
4751 }
4752 }
4753 return arr
4754 })
4755 }
4756 }
4757
4758 /**
4759 * @class This class encapsulates all the data of a PlayerIO Publishing Network Profile.
4760 */
4761 _pio.publishingNetworkProfile = function (profile) {
4762 /** The userId of the user
4763 * @type string
4764 */
4765 this.userId = profile.userid
4766
4767 /** The display name of the user
4768 * @type string
4769 */
4770 this.displayName = profile.displayname
4771
4772 /** The avatar url for the user
4773 * @type string
4774 */
4775 this.avatarUrl = profile.avatarurl
4776
4777 /** When was this user last seen
4778 * @type Date
4779 */
4780 this.lastOnline = new Date(profile.lastonline)
4781
4782 /** The country code of the user
4783 * @type string
4784 */
4785 this.countryCode = profile.countrycode
4786 }
4787})();
4788(function () {
4789 /**
4790 * @class The PlayerIO Publishing Network Relations service. This class is used to fetch the current users friends, and
4791 * to show the various friend management dialogs for games published on the PlayerIO Publishing Network.
4792 */
4793 _pio.publishingNetworkRelations = function (channel, connectUserId, publishingNetwork) {
4794 var self = this;
4795
4796 /**
4797 * List of all friends
4798 * @type publishingNetworkProfile[]
4799 */
4800 this.friends = "[ERROR: you tried to access publishingNetworkRelations.friends before calling publishingNetwork.refresh(callback)]"
4801
4802 /**
4803 * Check if a specific user is a friend
4804 * @param {string} userId The userId of the user to check
4805 * @return {bool} A boolean indicating friendship status
4806 */
4807 this.isFriend = function (userId) {
4808 if (typeof (_pio.friendLookup) != 'undefined' && typeof(_pio.friendLookup[connectUserId]) != 'undefined') {
4809 return _pio.friendLookup[connectUserId][userId] || false;
4810 } else {
4811 throw new PlayerIOError(PlayerIOErrorCode.PublishingNetworkNotLoaded, "Cannot access profile, friends, ignored before Publishing Network has been loaded. Please refresh Publishing Network first")
4812 }
4813 }
4814
4815 /**
4816 * Check if a specific user is a blocked
4817 * @param {string} userId The userId of the user to check
4818 * @return {bool} A boolean indicating blocked status
4819 */
4820 this.isBlocked = function (userId) {
4821 if (typeof (_pio.blockedLookup) != 'undefined' && typeof(_pio.blockedLookup[connectUserId]) != 'undefined') {
4822 return _pio.blockedLookup[connectUserId][userId] || false;
4823 } else {
4824 throw new PlayerIOError(PlayerIOErrorCode.PublishingNetworkNotLoaded, "Cannot access profile, friends, ignored before Publishing Network has been loaded. Please refresh Publishing Network first")
4825 }
4826 }
4827
4828
4829 /**
4830 * Shows a dialog where the user can choose to send a friend request to the specified user
4831 * @param {string} userId The userId of the user that will receive the friendship request, if sent
4832 * @param {function()} closedCallback Callback function that will be called when the dialog is closed
4833 */
4834 this.showRequestFriendshipDialog = function (userId, closedCallback) {
4835 _pio.showDialog('requestfriendship',channel, { userId: userId }, closedCallback)
4836 }
4837
4838 /**
4839 * Shows a dialog where the user can choose to block a specific user
4840 * @param {string} userId The userId of the user to block
4841 * @param {function()} closedCallback Callback function that will be called when the dialog is closed
4842 */
4843 this.showRequestBlockUserDialog = function(userId, closedCallback){
4844 _pio.showDialog('requestblockuser',channel, { userId: userId }, function(){
4845 publishingNetwork.refresh(function(){
4846 if(closedCallback)closedCallback()
4847 }, function(){
4848 if(closedCallback)closedCallback()
4849 })
4850 })
4851 }
4852
4853 /**
4854 * Shows the a dialog where the user can manage their friends list
4855 * @param {function()} closedCallback Callback function that will be called when the dialog is closed
4856 */
4857 this.showFriendsManager = function(closedCallback){
4858 _pio.showDialog('friendsmanager',channel, { }, function (result) {
4859 if (result.updated) {
4860 publishingNetwork.refresh(function () {
4861 if (closedCallback) closedCallback()
4862 }, function () {
4863 if (closedCallback) closedCallback()
4864 })
4865 } else {
4866 if (closedCallback) closedCallback()
4867 }
4868 })
4869 }
4870
4871 /**
4872 * Shows the a dialog where the user can manage their blocked users list
4873 * @param {function()} closedCallback Callback function that will be called when the dialog is closed
4874 */
4875 this.showBlockedUsersManager = function (closedCallback) {
4876 _pio.showDialog('blockedusersmanager',channel, { }, function (result) {
4877 if (result.updated) {
4878 publishingNetwork.refresh(function () {
4879 if (closedCallback) closedCallback()
4880 }, function () {
4881 if (closedCallback) closedCallback()
4882 })
4883 } else {
4884 if (closedCallback) closedCallback()
4885 }
4886 })
4887 }
4888 }
4889})();