├── .gitignore ├── README.md ├── docs ├── css │ └── media-queries.md └── javascript │ ├── navigator.md │ └── nx-api.md └── tools ├── debug-client ├── .gitignore ├── common │ └── js │ │ └── socket.js ├── config-example.php ├── debugger │ ├── index.php │ └── js │ │ └── devtool.js └── switch │ ├── index.php │ └── js │ └── devtool.js └── debug-server ├── .gitignore ├── DebugServer.php ├── SwitchBrowserDebugDaemonHandler.php ├── composer.json ├── composer.lock ├── config-example.php └── start.sh /.gitignore: -------------------------------------------------------------------------------- 1 | */error_log 2 | error_log 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # switch-browser 2 | 3 | Documentation for the Nintendo Switch's inbuilt browser applet, and tools for remote testing and debugging. 4 | 5 | Maintained by [sudofox](https://github.com/sudofox) and [jaames](https://github.com/jaames) 6 | -------------------------------------------------------------------------------- /docs/css/media-queries.md: -------------------------------------------------------------------------------- 1 | # Media Queries 2 | 3 | ## Custom 4 | 5 | The Nintendo Switch browser has two custom CSS media queries which allow developers to specify styles depending on the device mode. 6 | 7 | ### device-mode 8 | 9 | The `-webkit-nintendo-switch-device-mode` query can be used to detect whether or not the Switch is in console or handheld mode. 10 | 11 | **example:** 12 | 13 | ```css 14 | @media(-webkit-nintendo-switch-device-mode: -webkit-nintendo-switch-device-console) { 15 | /* css styles for console mode */ 16 | } 17 | 18 | @media(-webkit-nintendo-switch-device-mode: -webkit-nintendo-switch-device-handheld) { 19 | /* css styles for handheld mode */ 20 | } 21 | ``` 22 | 23 | ### performance-mode 24 | 25 | 26 | The `-webkit-nintendo-switch-performance-mode` query can be used to detect which performance setting the Switch is currently using. 27 | 28 | **example:** 29 | 30 | ```css 31 | @media(-webkit-nintendo-switch-performance-mode: -webkit-nintendo-switch-performance-0) { 32 | /* css styles for performance setting 0 */ 33 | } 34 | 35 | @media(-webkit-nintendo-switch-performance-mode: -webkit-nintendo-switch-performance-1) { 36 | /* css styles for performance setting 1 */ 37 | } 38 | ``` 39 | 40 | ### sources 41 | 42 | CSS media query definitions can be found in `NintendoSwitch_OpenSources1.0.0/webkit/WebCore/css/CSSValueKeywords.in` 43 | 44 | line 977: 45 | 46 | ``` 47 | #if defined(ENABLE_WKC_DEVICE_MODE_CSS_MEDIA) && ENABLE_WKC_DEVICE_MODE_CSS_MEDIA 48 | // (-webkit-nintendo-switch-device-mode:) media feature: 49 | -webkit-nintendo-switch-device-console 50 | -webkit-nintendo-switch-device-handheld 51 | #endif 52 | ``` 53 | 54 | line 983: 55 | 56 | ``` 57 | #if defined(ENABLE_WKC_PERFORMANCE_MODE_CSS_MEDIA) && ENABLE_WKC_PERFORMANCE_MODE_CSS_MEDIA 58 | // (-webkit-nintendo-switch-performance-mode:) media feature: 59 | -webkit-nintendo-switch-performance-0 60 | -webkit-nintendo-switch-performance-1 61 | #endif 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/javascript/navigator.md: -------------------------------------------------------------------------------- 1 | # Navigator 2 | 3 | | Property | Value | 4 | |:---------|:------| 5 | | `appCodeName` | `Mozilla` | 6 | | `appName` | `Netscape` | 7 | | `appVersion` | See below | 8 | | `platform ` | `Nintendo Switch` | 9 | | `auserAgent` | See below | 10 | 11 | ## appVersion 12 | 13 | | Firmware | Value | 14 | |:---------|:------| 15 | | 2.0.0 | `5.0 (Nintendo Switch; LoginApplet) AppleWebKit/601.6 (KHTML, like Gecko) NF/4.0.0.5.9 NintendoBrowser/5.1.0.13341` | 16 | 17 | ## userAgent 18 | 19 | | Firmware | Value | 20 | |:---------|:------| 21 | | 2.0.0 | `Mozilla/5.0 (Nintendo Switch; LoginApplet) AppleWebKit/601.6 (KHTML, like Gecko) NF/4.0.0.5.9 NintendoBrowser/5.1.0.13341` | 22 | -------------------------------------------------------------------------------- /docs/javascript/nx-api.md: -------------------------------------------------------------------------------- 1 | # NX api 2 | 3 | `window.nx` exposes a small API for accessing various browser features from within Javascript. The properties and methods documented here are available across all of the Switch's browser applets, however certain applets (such as eshop) may expose more. 4 | 5 | ### Properties 6 | 7 | #### `isKeyboardShown` 8 | 9 | * **Type:** `{Boolean}` 10 | 11 | * **Usage:** 12 | 13 | Indicates whether or not the keyboard UI is active. 14 | 15 | ### Methods 16 | 17 | #### `canHistoryBack` 18 | 19 | * **Returns:** `{Boolean}` 20 | 21 | * **Usage:** 22 | 23 | Returns `true` if the user can navigate to the previous page (untested but assumed). 24 | 25 | #### `endApplet` 26 | 27 | * **Returns:** `{Boolean}` 28 | 29 | * **Usage:** 30 | 31 | Closes the browser applet. 32 | 33 | #### `openTimeoutDialog` 34 | 35 | * **Arguments:** 36 | 37 | * `{String}` message 38 | 39 | * **Usage:** 40 | 41 | Show a message for a short period of time. 42 | 43 | #### `open1ButtonDialog` 44 | 45 | * **Arguments:** 46 | 47 | * `{String}` message 48 | * `{String}` buttonText 49 | 50 | * **Usage:** 51 | 52 | Show a message with a custom 'OK' button. 53 | 54 | * **Example:** 55 | 56 | ```js 57 | nx.open1ButtonDialog("I'm a message!", "OK!"); 58 | ``` 59 | 60 | #### `open2ButtonDialog` 61 | 62 | * **Arguments:** 63 | 64 | * `{String}` message 65 | * `{String}` left button text 66 | * `{String}` right button text 67 | 68 | * **Usage:** 69 | 70 | Show a message with custom 'Cancel' and 'OK' buttons. 71 | 72 | * **Example:** 73 | 74 | ```js 75 | nx.open2ButtonDialog("I'm a message!", "Cancel", "OK!"); 76 | ``` 77 | 78 | #### `footer.setDefaultAssign` 79 | 80 | * **Arguments:** 81 | 82 | * `{String}` key (`"A"`, `"B"`, `"X"` or `"Y"`) 83 | * `{String}` label 84 | 85 | * **Usage:** 86 | 87 | Add a prompt for a given button to the footer. The button will retain its default behaviour, but the label given to it can be set to whatever you like. 88 | 89 | #### `footer.setAssign` 90 | 91 | * **Arguments:** 92 | 93 | * `{String}` key (`"A"`, `"B"`, `"X"` or `"Y"`) 94 | * `{String}` label 95 | * `{Function}` callback 96 | * `{Object}` params 97 | * `{String}` `se` (sound effect label) 98 | 99 | * **Usage:** 100 | 101 | Add a prompt for a given button to the footer, and override its default behaviour(?). The `callback` function given will be called whenever the button is pressed. 102 | 103 | * **Example:** 104 | 105 | ```js 106 | // When the A button is pressed, alert the user, and play the "SeWebBtnDecide" sound effect 107 | 108 | nx.footer.setAssign("A", "Test", function() { 109 | alert("A key pressed!") 110 | }, { 111 | se: "SeWebBtnDecide" 112 | }); 113 | ``` 114 | 115 | #### `footer.unsetAssign` 116 | 117 | * **Arguments:** 118 | 119 | * `{String}` key (`"A"`, `"B"`, `"X"` or `"Y"`) 120 | 121 | * **Usage:** 122 | 123 | Remove a button prompt added with `footer.setAssign`. 124 | 125 | #### `playSystemSe` 126 | 127 | * **Arguments:** 128 | 129 | * `{String}` soundLabel 130 | 131 | * **Usage:** 132 | 133 | Play a predefined sound effect from the following list of `soundLabel` values: 134 | 135 | ``` 136 | SeToggleBtnFocus 137 | SeToggleBtnOn 138 | SeToggleBtnOff 139 | SeCheckboxFocus 140 | SeCheckboxOn 141 | SeCheckboxOff 142 | SeRadioBtnFocus 143 | SeRadioBtnOn 144 | SeSelectCheck 145 | SeSelectUncheck 146 | SeBtnDecide 147 | SeTouchUnfocus 148 | SeBtnFocus 149 | SeKeyError 150 | SeDialogOpen 151 | SeWebZoomOut 152 | SeWebZoomIn 153 | SeWebNaviFocus 154 | SeWebPointerFocus 155 | SeFooterFocus 156 | SeFooterDecideBack 157 | SeFooterDecideFinish 158 | SeWebChangeCursorPointer 159 | SeWebTouchFocus 160 | SeWebLinkDecide 161 | SeWebTextboxStartEdit 162 | SeWebButtonDecide 163 | SeWebRadioBtnOn 164 | SeWebCheckboxUncheck 165 | SeWebCheckboxCheck 166 | SeWebMenuListOpen 167 | ``` 168 | 169 | #### `stopSystemSe` 170 | 171 | * **Usage:** 172 | 173 | Stop sound effect playback. -------------------------------------------------------------------------------- /tools/debug-client/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | -------------------------------------------------------------------------------- /tools/debug-client/common/js/socket.js: -------------------------------------------------------------------------------- 1 | var socket = (function () { 2 | var connection; 3 | var messageCallbacks = []; 4 | var config = {}; 5 | 6 | function sendMessage(type, content) { 7 | var message = { 8 | type: type, 9 | contentType: Array.isArray(content) ? "array" : typeof content, 10 | }; 11 | // convert the message content to a string 12 | switch (message.contentType) { 13 | case "object": 14 | case "array": 15 | // objects and arrays can contain self references, which choke JSON.stringify 16 | // so we build up a cashe of referenced objects, and disallow multiple instances 17 | var cache = []; 18 | message.content = JSON.stringify(content, function(key, value) { 19 | // if the value is a function, we want to serialize it 20 | if (typeof value === "function") { 21 | return value.toString(); 22 | } else { 23 | // if the value is an object, there's a chance it may reference itself, so we need to check that 24 | if (typeof value === "object" && value !== null) { 25 | // if the value has already been read, ignore it 26 | if (cache.indexOf(value) !== -1) return; 27 | // else push it to the cache so we don't read it again 28 | cache.push(value); 29 | } 30 | return value; 31 | } 32 | }); 33 | cache = null; 34 | break; 35 | default: 36 | message.content = content + ""; 37 | break; 38 | } 39 | // send the message as a string 40 | if (connection) connection.send(JSON.stringify(message)); 41 | }; 42 | 43 | function receiveMessage(message) { 44 | var messageData = message.data; 45 | // convert the message string back into an object 46 | var message = JSON.parse(messageData); 47 | // convert message content back into whatever type it was 48 | var content; 49 | switch (message.contentType) { 50 | case "object": 51 | case "array": 52 | content = JSON.parse(message.content); 53 | break; 54 | case "string": 55 | content = message.content; 56 | break; 57 | default: 58 | content = eval(message.content); 59 | break; 60 | } 61 | 62 | message.content = content; 63 | 64 | messageCallbacks.forEach(function (callback) { 65 | callback(message); 66 | }); 67 | }; 68 | 69 | return { 70 | start: function(opts) { 71 | connection = new WebSocket(opts.host + ":" + opts.port); 72 | 73 | config = opts; 74 | 75 | connection.addEventListener("open", function (event) { 76 | connection.send("REGISTER_" + opts.mode.toUpperCase()); 77 | }); 78 | 79 | connection.addEventListener("message", receiveMessage); 80 | }, 81 | onMessage: function (callback) { 82 | messageCallbacks.push(callback); 83 | }, 84 | send: sendMessage 85 | } 86 | 87 | })(); 88 | -------------------------------------------------------------------------------- /tools/debug-client/config-example.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | Switch Debug Client 11 | 12 | 13 | 14 | 15 | 16 | Please open the javascript console (: 17 | 18 |

Usage:

19 | 22 | 23 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tools/debug-client/debugger/js/devtool.js: -------------------------------------------------------------------------------- 1 | window.devTool = (function () { 2 | 3 | function exec(input) { 4 | var code; 5 | switch (typeof input) { 6 | case "function": 7 | code = "window.TEMP_FN = " + input.toString(); 8 | break; 9 | default: 10 | code = input; 11 | break; 12 | } 13 | socket.send("EXECUTE_CODE", code); 14 | }; 15 | 16 | var messageHandlers = { 17 | "CONSOLE_LOG": function (message) { 18 | console.log.apply(console, message.content); 19 | }, 20 | "CONSOLE_WARN": function (message) { 21 | console.warn.apply(console, message.content); 22 | }, 23 | "CONSOLE_INFO": function (message) { 24 | console.info.apply(console, message.content); 25 | }, 26 | "CONSOLE_ERROR": function (message) { 27 | console.error.apply(console, message.content); 28 | }, 29 | "CONSOLE_DEBUG": function (message) { 30 | console.debug.apply(console, message.content); 31 | } 32 | } 33 | 34 | socket.onMessage(function (message) { 35 | if (messageHandlers.hasOwnProperty(message.type)) { 36 | var handler = messageHandlers[message.type]; 37 | handler(message); 38 | } 39 | }); 40 | 41 | return { 42 | socket: socket, 43 | exec: exec, 44 | } 45 | 46 | })(); 47 | -------------------------------------------------------------------------------- /tools/debug-client/switch/index.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | Switch Debug Client 12 | 13 | 14 | 15 | 16 | 17 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tools/debug-client/switch/js/devtool.js: -------------------------------------------------------------------------------- 1 | window.devTool = (function () { 2 | 3 | // execute a string as javascript code, pass the result to callback 4 | function exec(str) { 5 | var result = eval(str); 6 | if (window.TEMP_FN) { 7 | result = window.TEMP_FN(); 8 | window.TEMP_FN = null; 9 | } 10 | if (result !== null) console.log(result); 11 | }; 12 | 13 | // get the object at a given path, ie "window.navigator" 14 | function get(path) { 15 | return path.split(".").reduce(function(prev, curr) { 16 | return prev ? prev[curr] : undefined 17 | }, window); 18 | }; 19 | 20 | // util to override a function while still maintaining the default bevahiour 21 | function overrideFn(nativeCtx, native, override) { 22 | return function() { 23 | override.call(arguments); 24 | native.apply(nativeCtx, arguments); 25 | } 26 | }; 27 | 28 | /* OVERRIDE DEFAULT DEV TOOLS */ 29 | 30 | function argListToArray (argList) { 31 | return Array.prototype.slice.call(argList); 32 | }; 33 | 34 | var consoleOverrides = { 35 | log: function (args) { 36 | socket.send("CONSOLE_LOG", args); 37 | }, 38 | warn: function () { 39 | socket.send("CONSOLE_WARN", argListToArray(arguments)); 40 | }, 41 | info: function () { 42 | socket.send("CONSOLE_INFO", argListToArray(arguments)); 43 | }, 44 | error: function () { 45 | socket.send("CONSOLE_ERROR", argListToArray(arguments)); 46 | }, 47 | }; 48 | 49 | console.log = (function() { 50 | var log = console.log; 51 | return function() { 52 | log.apply(console, arguments); 53 | consoleOverrides.log(argListToArray(arguments)); 54 | } 55 | })(); 56 | 57 | console.warn = (function() { 58 | var warn = console.warn; 59 | return function() { 60 | warn.apply(console, arguments); 61 | consoleOverrides.warn(argListToArray(arguments)); 62 | } 63 | })(); 64 | 65 | console.info = (function() { 66 | var info = console.info; 67 | return function() { 68 | info.apply(console, arguments); 69 | consoleOverrides.info(argListToArray(arguments)); 70 | } 71 | })(); 72 | 73 | console.error = (function() { 74 | var error = console.error; 75 | return function() { 76 | error.apply(console, arguments); 77 | consoleOverrides.error(argListToArray(arguments)); 78 | } 79 | })(); 80 | 81 | var messageHandlers = { 82 | "EXECUTE_CODE": function (message) { 83 | exec(message.content); 84 | }, 85 | } 86 | 87 | socket.onMessage(function (message) { 88 | if (messageHandlers.hasOwnProperty(message.type)) { 89 | var handler = messageHandlers[message.type]; 90 | handler(message); 91 | } 92 | }); 93 | 94 | return { 95 | console: consoleOverrides, 96 | exec: exec, 97 | get: get 98 | }; 99 | 100 | })(); 101 | -------------------------------------------------------------------------------- /tools/debug-server/.gitignore: -------------------------------------------------------------------------------- 1 | error_log 2 | vendor* 3 | config.php 4 | websocket_server.pid 5 | debugger.log 6 | -------------------------------------------------------------------------------- /tools/debug-server/DebugServer.php: -------------------------------------------------------------------------------- 1 | "SwitchBrowserDebugDaemonHandler", 12 | 'eventDriver' => 'socket_select', //'event', 13 | 'pid' => __DIR__ . '/websocket_server.pid', 14 | 'websocket' => 'tcp://' . $websocket_config['host'] . ':' . $websocket_config['port'], 15 | ); 16 | 17 | $vendor = require('vendor/autoload.php'); 18 | $vendor->add("SwitchBrowserDebugDaemonHandler",__DIR__); 19 | $server = new morozovsk\websocket\Server($config); 20 | echo "Starting server on " . $config["websocket"] . "\n"; 21 | call_user_func(array($server, $argv[1])); 22 | 23 | -------------------------------------------------------------------------------- /tools/debug-server/SwitchBrowserDebugDaemonHandler.php: -------------------------------------------------------------------------------- 1 | clientRoles[$connectionID] = "new"; // Not registered as a debugger or debuggee 13 | echo "{$connectionID} opened, total connections: ".count($this->clientRoles) ."\n"; 14 | } 15 | 16 | protected function onClose($connectionID) { 17 | //call when existing client close connection 18 | switch ($this->clientRoles[$connectionID]) { 19 | 20 | case "new": 21 | unset($this->clientRoles[$connectionID]); 22 | echo "Connection (ID: {$connectionID}) closed before handshake received\n"; 23 | break; 24 | 25 | case "debuggee": 26 | $this->debuggee = null; 27 | unset($this->clientRoles[$connectionID]); 28 | foreach ($this->debuggers as $debugger) { 29 | $this->sendToClient($debugger, json_encode(array("type" => "STATUS", "contentType" => "string", "content" => "DEBUGGEE_DISCONNECTED"))); 30 | } 31 | echo "Connection with debuggee (ID: {$connectionID}) closed\n"; 32 | break; 33 | 34 | case "debugger": 35 | unset($this->debuggers[$connectionID]); 36 | unset($this->clientRoles[$connectionID]); 37 | echo "Connection with debugger (ID: {$connectionID}) closed\n"; 38 | break; 39 | default: 40 | echo "Connection (ID: {$connectionID}) closed\n"; 41 | break; 42 | } 43 | 44 | } 45 | 46 | protected function onMessage($connectionID, $data, $type) { 47 | //call when new message from existing client 48 | $message = $data; //"user #{$connectionId}: $data"; 49 | echo "$connectionID: $message\n"; 50 | if ($this->clientRoles[$connectionID] == "new") { 51 | // TODO: Test to see if lock is needed 52 | switch ($message) { 53 | 54 | case "REGISTER_DEBUGGEE": 55 | 56 | if (!is_null($this->debuggee)) { 57 | // Deny attempt to register debuggee. 58 | $this->sendToClient($connectionID, json_encode(array("type" => "STATUS", "contentType" => "string", "content" => "ERROR_DEBUGGEE_EXISTS"))); 59 | echo "ERROR_DEBUGGEE_EXISTS: Connection $connectionID attempted to register as the debuggee but failed\n"; 60 | } else { 61 | $this->debuggee = $connectionID; 62 | $this->clientRoles[$connectionID] = "debuggee"; 63 | $this->sendToClient($connectionID, json_encode(array("type" => "STATUS", "contentType" => "string", "content" => "SUCCESS_DEBUGGEE_REGISTERED"))); 64 | echo "SUCCESS_DEBUGGEE_REGISTERED: Connection $connectionID registered as the debuggee\n"; 65 | 66 | } 67 | return; 68 | break; 69 | 70 | case "REGISTER_DEBUGGER": 71 | 72 | $this->clientRoles[$connectionID] = "debugger"; 73 | $this->debuggers[$connectionID] = $connectionID; 74 | $this->sendToClient($connectionID, json_encode(array("type" => "STATUS", "contentType" => "string", "content" => "SUCCESS_DEBUGGER_REGISTERED"))); 75 | echo "SUCCESS_DEBUGGER_REGISTERED: Connection $connectionID registered as a debugger\n"; 76 | 77 | return; 78 | break; 79 | 80 | default: 81 | 82 | $this->sendToClient($connectionID, json_encode(array("type" => "STATUS", "contentType" => "string", "content" => "ERROR_INVALID_COMMAND"))); 83 | echo "ERROR_INVALID_COMMAND: Connection $connectionID issued an invalid command.\n"; 84 | 85 | return; 86 | break; 87 | } 88 | } elseif ($this->clientRoles[$connectionID] == "debuggee") { 89 | 90 | foreach($this->debuggers as $debuggerID) { 91 | $this->sendToClient($debuggerID, $message); // TODO: Prefix to indicate message source? 92 | } 93 | } elseif ($this->clientRoles[$connectionID] == "debugger") { 94 | 95 | $this->sendToClient($this->debuggee, $message); 96 | foreach($this->debuggers as $debuggerID) { 97 | if ($debuggerID != $connectionID) { 98 | // $this->sendToClient($debuggerID, "Debugger $connectionID: $message"); 99 | } 100 | } 101 | } 102 | 103 | } 104 | } 105 | 106 | -------------------------------------------------------------------------------- /tools/debug-server/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "morozovsk/websocket": "^4.2" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tools/debug-server/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "ac8aeae3daa0ef76164a35e77fb97d4c", 8 | "packages": [ 9 | { 10 | "name": "morozovsk/websocket", 11 | "version": "4.2.2", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/morozovsk/websocket.git", 15 | "reference": "034a514ec64eed9be3cb6fc9f4b22f67216f71ab" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/morozovsk/websocket/zipball/034a514ec64eed9be3cb6fc9f4b22f67216f71ab", 20 | "reference": "034a514ec64eed9be3cb6fc9f4b22f67216f71ab", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-mbstring": "*", 25 | "php": ">=5.3.0" 26 | }, 27 | "type": "library", 28 | "autoload": { 29 | "psr-4": { 30 | "morozovsk\\websocket\\": "" 31 | } 32 | }, 33 | "notification-url": "https://packagist.org/downloads/", 34 | "license": [ 35 | "MIT" 36 | ], 37 | "authors": [ 38 | { 39 | "name": "simple chat (single daemon)", 40 | "homepage": "http://sharoid.ru/chat.html" 41 | }, 42 | { 43 | "name": "pro chat (master + worker)", 44 | "homepage": "http://sharoid.ru/chat2.html" 45 | }, 46 | { 47 | "name": "simple game", 48 | "homepage": "http://sharoid.ru/game.html" 49 | } 50 | ], 51 | "description": "simple php websocket server with examples and demo: simple chat (single daemon) - http://sharoid.ru/chat.html , pro chat (master + worker) - http://sharoid.ru/chat2.html , simple game - http://sharoid.ru/game.html", 52 | "homepage": "http://sharoid.ru/game.html", 53 | "keywords": [ 54 | "chat", 55 | "demo", 56 | "event", 57 | "examle", 58 | "game", 59 | "laravel", 60 | "libevent", 61 | "server", 62 | "socket_select", 63 | "websocket", 64 | "yii" 65 | ], 66 | "time": "2017-02-24T10:13:40+00:00" 67 | } 68 | ], 69 | "packages-dev": [], 70 | "aliases": [], 71 | "minimum-stability": "stable", 72 | "stability-flags": [], 73 | "prefer-stable": false, 74 | "prefer-lowest": false, 75 | "platform": [], 76 | "platform-dev": [] 77 | } 78 | -------------------------------------------------------------------------------- /tools/debug-server/config-example.php: -------------------------------------------------------------------------------- 1 |