Anatoly03

Untitled

Aug 5th, 2018
463
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 201.52 KB | None | 0 0
  1. if (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. */
  169. PlayerIO = {
  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. };
  224. var JSON;
  225. if (!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. */
  854. PlayerIOError = 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. };
  880. PlayerIOError.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. */
  905. PlayerIOErrorCode = {
  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. })();
Add Comment
Please, Sign In to add comment