Source: PG.js

/**
 * Phune Gaming SDK for JavaScript v{{ VERSION }}
 *
 * @file Handles the communication between the Phune Gaming platform and the games.
 * @license MIT License <http://opensource.org/licenses/MIT>
 * @copyright Copyright (c) 2014 Present Technologies
 */
(function(window, document) {
    'use strict';

    /**
     * Constructs a new instance of the Phune Gaming SDK.
     *
     * @constructor PG
     * @classdesc Phune Gaming SDK
     */
    var PG = function() {

        var origin = '*', // (document.location.origin) ? document.location.origin : document.location.protocol + '//' + document.location.host,
            result = {
                WON: 'won',
                LOST: 'lost',
                DRAW: 'draw'
            },
            message, lastMessage = null,
            player, opponent;

        /**
         * Get the match result with the correct value based on the winnerPlayerId param.
         *
         * @private
         * @abstract
         * @memberof PG
         * @param {number} winnerPlayerId The id of the player that won the match.
         * @returns {string} A match result valid value.
         */
        var getMatchResult = function(winnerPlayerId) {
            if (typeof winnerPlayerId === 'undefined') {
                return;
            }

            if (winnerPlayerId === player.id) {
                return result.WON;
            } else if (winnerPlayerId === opponent.id) {
                return result.LOST;
            }

            return result.DRAW;
        };

        /**
         * The game should build the user interface and get ready to start playing.
         *
         * @private
         * @abstract
         * @memberof PG
         * @param {Object} player The current player details.
         * @param {Object} opponent The opponent details.
         * @param {string} deviceType Indicates the type of the device where the game is running. Possible values are 'MOBILE' and 'TV'.
         */
        var onMatchPrepare = function(player, opponent, deviceType) {
            throw new Error('onMatchPrepare is not implemented.');
        };

        /**
         * The game can now configure additional match details.
         *
         * @private
         * @abstract
         * @memberof PG
         * @param {number} allowedTime Time allowed for the player to configure the game and start the match.
         */
        var onGameLobby = function(allowedTime) {
            throw new Error('onGameLobby is not implemented.');
        };

        /**
         * The match start confirmation. Only now is the player allowed to play the game.
         *
         * @private
         * @abstract
         * @memberof PG
         * @param {number} playerIdToPlayNext The identifier of the player to whom the next move belongs.
         * @param {number} timeToPlay Time allowed for the player to make a move.
         */
        var onMatchStart = function(playerIdToPlayNext, timeToPlay) {
            throw new Error('onMatchStart is not implemented.');
        };

        /**
         * Acknowledgment to a valid move.
         *
         * @private
         * @abstract
         * @memberof PG
         * @param {number} playerIdWhoSentTheMove The identifier of the player that sent the move.
         * @param {number} playerIdToPlayNext The identifier of the player to whom the next move belongs.
         * @param {Object} moveDetails The move details.
         * @param {Object} moveEvaluation The result of the move validation.
         * @param {string} [matchResult] If the move ended the match, this indicates its result. Possible values are 'won', 'lost' or 'draw'.
         */
        var onMoveValid = function(playerIdWhoSentTheMove, playerIdToPlayNext, moveDetails, moveEvaluation, matchResult) {
            throw new Error('onMoveValid is not implemented.');
        };

        /**
         * Acknowledgment to an invalid move sent by the current player.
         *
         * @private
         * @abstract
         * @memberof PG
         * @param {number} playerIdToPlayNext The identifier of the player to whom the next move belongs.
         * @param {Object} moveEvaluation The result of the move validation.
         */
        var onMoveInvalid = function(playerIdToPlayNext, moveEvaluation) {
            throw new Error('onMoveInvalid is not implemented.');
        };

        /**
         * A message from the server-side rules was received.
         *
         * @private
         * @abstract
         * @memberof PG
         * @param {number} playerWhoSentTheMessage The identifier of the player that sent the message.
         * @param {Object} messageDetails Message specific to a game and unknown to the platform. The developer is advised to have multiple message types with different bodies in order to achieve different goals.
         * @param {Object} messageResult The result returned by the server-side rules.
         */
        var onServerMessage = function(playerIdWhoSentTheMessage, messageDetails, messageResult) {
            throw new Error('onServerMessage is not implemented.');
        };

        /**
         * A message sent directly from another player was received.
         *
         * @private
         * @abstract
         * @memberof PG
         * @param {Object} messageDetails Message specific to a game and unknown to the platform. The developer is advised to have multiple message types with different bodies in order to achieve different goals.
         */
        var onPlayerMessage = function(messageDetails) {
            throw new Error('onPlayerMessage is not implemented.');
        };

        /**
         * Called by the platform when a match end event is received.
         *
         * @private
         * @abstract
         * @memberof PG
         * @param {string} matchResult The match result. Possible values are 'won', 'lost' or 'draw'.
         */
        var onMatchEnd = function(matchResult) {
            throw new Error('onMatchEnd is not implemented.');
        };

        /**
         * A keyboard or TV remote control key was pressed.
         *
         * @private
         * @abstract
         * @memberof PG
         * @param {string} key The key that was pressed. Possible values are 'left', 'right', 'up', 'down' or 'enter'.
         */
        var onKeyPress = function(key) {
            throw new Error('onKeyPress is not implemented.');
        };

        return {
            /**
             * Initialize the Phune Gaming SDK.
             *
             * @public
             * @memberof PG
             * @param {Object} params Object containing all the options to configure the SDK. This includes the mandatory callback functions: onMatchPrepare, onMatchStart, onMoveValid, onMoveInvalid, onMatchEnd, as well as the optional callback functions: onServerMessage, onPlayerMessage and onKeyPress.
             */
            init: function(params) {
                onMatchPrepare = params.onMatchPrepare || onMatchPrepare;
                onGameLobby = params.onGameLobby || onGameLobby;
                onMatchStart = params.onMatchStart || onMatchStart;
                onMoveValid = params.onMoveValid || onMoveValid;
                onMoveInvalid = params.onMoveInvalid || onMoveInvalid;
                onMatchEnd = params.onMatchEnd || onMatchEnd;
                onServerMessage = params.onServerMessage || onServerMessage;
                onPlayerMessage = params.onPlayerMessage || onPlayerMessage;
                onKeyPress = params.onKeyPress || onKeyPress;

                window.addEventListener('message', function(msg) {
                    // if (msg.origin !== origin) {
                    //     console.warn('Origin not recognized: ' + msg.origin);
                    //     return;
                    // }
                    switch (msg.data.type) {
                    case 'matchPrepare':
                        player = msg.data.player;
                        opponent = msg.data.opponent;
                        onMatchPrepare(player, opponent, msg.data.deviceType);
                        break;
                    case 'enterGameLobby':
                        onGameLobby(msg.data.timeout);
                        break;
                    case 'matchStart':
                        onMatchStart(msg.data.nextPlayerId, msg.data.timeout);
                        break;
                    case 'matchMoveValid':
                        onMoveValid(msg.data.playerId, msg.data.nextPlayerId, msg.data.content, msg.data.evaluationContent, getMatchResult(msg.data.winnerPlayerId));
                        break;
                    case 'matchMoveInvalid':
                        onMoveInvalid(msg.data.nextPlayerId, msg.data.evaluationContent);
                        break;
                    case 'matchEnd':
                        onMatchEnd(getMatchResult(msg.data.winnerPlayerId));
                        break;
                    case 'serverMessage':
                        onServerMessage(msg.data.playerId, msg.data.content, msg.data.result);
                        break;
                    case 'playerMessage':
                        onPlayerMessage(msg.data.content);
                        break;
                    case 'keyPress':
                        onKeyPress(msg.data.value);
                        break;
                    }
                });

                // Inform the platform that the game loaded successfully.
                window.parent.postMessage({
                    type: 'loaded'
                }, origin);
            },

            /**
             * Informs the server that the client is ready to start the match.
             *
             * @public
             * @memberof PG
             */
            ready: function() {
                window.parent.postMessage({
                    type: 'ready'
                }, origin);
            },

            /**
             * Informs the server that the game configuration phase ended and the game can start.
             *
             * @public
             * @memberof PG
             */
            exitGameLobby: function() {
                window.parent.postMessage({
                    type: 'exitGameLobby'
                }, origin);
            },

            /**
             * Asks the platform to show the game menu.
             *
             * @public
             * @memberof PG
             */
            showMenu: function() {
                window.parent.postMessage({
                    type: 'showMenu'
                }, origin);
            },

            /**
             * Send a move to the platform server.
             *
             * @public
             * @memberof PG
             * @param {Object} moveDetails The move details.
             * @param {function(Object): boolean} [validate] Optional function to validate the move before sending it to server.
             * @returns {boolean} True when the move is valid or when no validation function was provided.
             */
            sendMove: function(moveDetails, validate) {
                if (validate && !validate(moveDetails)) {
                    return false;
                }

                window.parent.postMessage({
                    type: 'move',
                    content: moveDetails
                }, origin);

                return true;
            },

            /**
             * Send a message to the server-side rules. This message is specific to a game and will not be processed by the platform itself.
             * The response from the server could either be sent to you only or to all players.
             *
             * @public
             * @memberof PG
             * @param {Object} messageDetails The content of the message to be sent.
             * @param {boolean} isAnswerPublic Whether the reply from the server's rules should be sent to all players or not.
             * @param {boolean} [serializeRequest] Whether the messages should be processed in order of arrival or can be executed in parallel.
             */
            sendMessageToServer: function(messageDetails, isAnswerPublic, serializeRequest) {
                window.parent.postMessage({
                    type: 'serverMessage',
                    publicAnswer: isAnswerPublic,
                    requiresConcurrencyControl: (serializeRequest) ? serializeRequest : true,
                    content: messageDetails
                }, origin);
            },

            /**
             * Peer-to-peer message sent directly to another player.
             *
             * @public
             * @memberof PG
             * @param {Object} messageDetails The content of the message to be sent.
             * @param {number} [sendTimeIntervalLimit] Do not allow sending more than one message within the specified time in milliseconds.
             *                 If this is called more than once during this interval only the last message will be sent.
             */
            sendMessageToPlayer: function(messageDetails, sendTimeIntervalLimit) {
                if (!sendTimeIntervalLimit || lastMessage === null) {
                    message = {
                        type: 'playerMessage',
                        content: messageDetails
                    };
                    window.parent.postMessage(message, origin);

                    if (sendTimeIntervalLimit) {
                        setTimeout(function() {
                            if (lastMessage !== message) {
                                message = lastMessage;
                                lastMessage = null;
                                window.parent.postMessage(message, origin);
                            } else {
                                lastMessage = null;
                            }
                        }, sendTimeIntervalLimit);
                    }
                }
                if (sendTimeIntervalLimit) {
                    lastMessage = message;
                }
            }
        };
    };
    window.PG = new PG();

}(window, document));