@colyseus/tools 0.15.41 → 0.15.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,19 +7,19 @@
7
7
  <a href="https://npmjs.com/package/colyseus">
8
8
  <img src="https://img.shields.io/npm/dm/colyseus.svg?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETESWYxR33AAAAtElEQVQoz4WQMQrCQBRE38Z0QoTcwF4Qg1h4BO0sxGOk80iCtViksrIQRRBTewWxMI1mbELYjYu+4rPMDPtn12ChMT3gavb4US5Jym0tcBIta3oDHv4Gwmr7nC4QAxBrCdzM2q6XqUnm9m9r59h7Rc0n2pFv24k4ttGMUXW+sGELTJjSr7QDKuqLS6UKFChVWWuFkZw9Z2AAvAirKT+JTlppIRnd6XgaP4goefI2Shj++OnjB3tBmHYK8z9zAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE5LTAyLTAxVDE4OjE3OjM3KzAxOjAwGQQixQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOS0wMi0wMVQxODoxNzozNyswMTowMGhZmnkAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC">
9
9
  </a>
10
- <a href="https://github.com/colyseus/colyseus/discussions" title="Discuss on Forum">
11
- <img src="https://img.shields.io/badge/discuss-on%20forum-brightgreen.svg?style=for-the-badge&colorB=0069b8&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETDROxCNUzAAABB0lEQVQoz4WRvyvEARjGP193CnWRH+dHQmGwKZtFGcSmxHAL400GN95ktIpV2dzlLzDJgsGgGNRdDAzoQueS/PgY3HXHyT3T+/Y87/s89UANBKXBdoZo5J6L4K1K5ZxHfnjnlQUf3bKvkgy57a0r9hS3cXfMO1kWJMza++tj3Ac7/LY343x1NA9cNmYMwnSS/SP8JVFuSJmr44iFqvtmpjhmhBCrOOazCesq6H4P3bPBjFoIBydOk2bUA17I080Es+wSZ51B4DIA2zgjSpYcEe44Js01G0XjRcCU+y4ZMrDeLmfc9EnVd5M/o0VMeu6nJZxWJivLmhyw1WHTvrr2b4+2OFqra+ALwouTMDcqmjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDItMDFUMTg6MTM6MTkrMDE6MDAC9f6fAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTAyLTAxVDE4OjEzOjE5KzAxOjAwc6hGIwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAASUVORK5CYII=" alt="Discussion forum" />
12
- </a>
13
10
  <a href="http://chat.colyseus.io">
14
11
  <img src="https://img.shields.io/discord/525739117951320081.svg?style=for-the-badge&colorB=7581dc&logo=discord&logoColor=white">
15
12
  </a>
13
+ <a href="https://github.com/colyseus/colyseus/discussions" title="Discuss on Forum">
14
+ <img src="https://img.shields.io/badge/discuss-on%20forum-brightgreen.svg?style=for-the-badge&colorB=0069b8&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETDROxCNUzAAABB0lEQVQoz4WRvyvEARjGP193CnWRH+dHQmGwKZtFGcSmxHAL400GN95ktIpV2dzlLzDJgsGgGNRdDAzoQueS/PgY3HXHyT3T+/Y87/s89UANBKXBdoZo5J6L4K1K5ZxHfnjnlQUf3bKvkgy57a0r9hS3cXfMO1kWJMza++tj3Ac7/LY343x1NA9cNmYMwnSS/SP8JVFuSJmr44iFqvtmpjhmhBCrOOazCesq6H4P3bPBjFoIBydOk2bUA17I080Es+wSZ51B4DIA2zgjSpYcEe44Js01G0XjRcCU+y4ZMrDeLmfc9EnVd5M/o0VMeu6nJZxWJivLmhyw1WHTvrr2b4+2OFqra+ALwouTMDcqmjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDItMDFUMTg6MTM6MTkrMDE6MDAC9f6fAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTAyLTAxVDE4OjEzOjE5KzAxOjAwc6hGIwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAASUVORK5CYII=" alt="Discussion forum" />
15
+ </a>
16
16
  <h3>
17
17
  Multiplayer Framework for Node.js. <br /><a href="https://docs.colyseus.io/">View documentation</a>
18
18
  </h3>
19
19
  </div>
20
20
 
21
- Colyseus is an Authoritative Multiplayer Framework for Node.js, with clients
22
- available for the Web, Unity3d, Defold, Haxe, and Cocos. ([See official clients](#%EF%B8%8F-official-client-integration))
21
+ Colyseus is an Authoritative Multiplayer Framework for Node.js, with SDKs
22
+ available for the Web, Unity, Defold, Haxe, Cocos and Construct3. ([See official SDKs](https://docs.colyseus.io/client/))
23
23
 
24
24
  The project focuses on providing synchronizable data structures for realtime and
25
25
  turn-based games, matchmaking, and ease of usage both on the server-side and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/tools",
3
- "version": "0.15.41",
3
+ "version": "0.15.43",
4
4
  "description": "Colyseus Tools for Production",
5
5
  "input": "./src/index.ts",
6
6
  "main": "./build/index.js",
@@ -8,7 +8,8 @@
8
8
  "typings": "./build/index.d.ts",
9
9
  "bin": {
10
10
  "colyseus-system-boot": "system-boot.js",
11
- "colyseus-post-deploy": "post-deploy.js"
11
+ "colyseus-post-deploy": "post-deploy.js",
12
+ "colyseus-report-stats": "report-stats.js"
12
13
  },
13
14
  "repository": {
14
15
  "type": "git",
@@ -35,14 +36,14 @@
35
36
  "@types/cors": "^2.8.10",
36
37
  "@types/dotenv": "^8.2.0",
37
38
  "uwebsockets-express": "^1.1.10",
38
- "@colyseus/uwebsockets-transport": "^0.15.8"
39
+ "@colyseus/uwebsockets-transport": "^0.15.9"
39
40
  },
40
41
  "dependencies": {
41
42
  "node-os-utils": "^1.3.7",
42
43
  "cors": "^2.8.5",
43
44
  "dotenv": "^8.2.0",
44
45
  "express": "^4.16.2",
45
- "@colyseus/core": "^0.15.48",
46
+ "@colyseus/core": "^0.15.52",
46
47
  "@colyseus/ws-transport": "^0.15.2"
47
48
  },
48
49
  "publishConfig": {
package/post-deploy.js CHANGED
@@ -27,9 +27,14 @@ if (!CONFIG_FILE) {
27
27
  pm2.list(function(err, apps) {
28
28
  bailOnErr(err);
29
29
 
30
+ // TODO: flush previous logs (?)
31
+ // pm2.flush();
32
+
30
33
  if (apps.length === 0) {
34
+ //
31
35
  // first deploy
32
- pm2.start(CONFIG_FILE, {...opts}, onAppRunning);
36
+ //
37
+ pm2.start(CONFIG_FILE, { ...opts }, () => onAppRunning());
33
38
 
34
39
  } else {
35
40
 
@@ -37,51 +42,84 @@ pm2.list(function(err, apps) {
37
42
  // detect if cwd has changed, and restart PM2 if it has
38
43
  //
39
44
  if (apps[0].pm2_env.pm_cwd !== pm2.cwd) {
40
-
41
45
  //
42
46
  // remove all and start again with new cwd
43
47
  //
44
- pm2.delete('all', function(err) {
45
-
46
- // kill & start again
47
- pm2.kill(function() {
48
- pm2.start(CONFIG_FILE, { ...opts }, onAppRunning);
49
- });
50
-
51
- });
48
+ restartAll();
52
49
 
53
50
  } else {
54
51
  //
55
52
  // reload existing apps
56
53
  //
57
- pm2.reload(CONFIG_FILE, {...opts}, function(err, apps) {
58
- bailOnErr(err);
59
-
60
- const name = apps[0].name;
61
-
62
- // scale app to use all CPUs available
63
- if (apps.length !== maxCPU) {
64
- pm2.scale(name, maxCPU, onAppRunning);
65
-
66
- } else {
67
- onAppRunning();
68
- }
69
- });
54
+ reloadAll();
70
55
  }
71
-
72
56
  }
73
57
  });
74
58
 
75
- function onAppRunning() {
59
+ function onAppRunning(reloadedAppIds) {
60
+ // reset reloaded app stats
61
+ if (reloadedAppIds) {
62
+ resetAppStats(reloadedAppIds);
63
+ }
76
64
  updateColyseusBootService();
77
65
  updateAndReloadNginx();
78
66
  }
79
67
 
68
+ function restartAll () {
69
+ pm2.delete('all', function (err) {
70
+ // kill & start again
71
+ pm2.kill(function () {
72
+ pm2.start(CONFIG_FILE, { ...opts }, () => onAppRunning());
73
+ });
74
+ });
75
+ }
76
+
77
+ function reloadAll(retry = 0) {
78
+ pm2.reload(CONFIG_FILE, { ...opts }, function (err, apps) {
79
+ if (err) {
80
+ //
81
+ // Retry in case of "Reload in progress" error.
82
+ //
83
+ if (err.message === 'Reload in progress' && retry < 5) {
84
+ console.warn(err.message, ", retrying...");
85
+ setTimeout(() => reloadAll(retry + 1), 1000);
86
+
87
+ } else {
88
+ bailOnErr(err);
89
+ }
90
+
91
+ return;
92
+ }
93
+
94
+ const name = apps[0].name;
95
+ const reloadedAppIds = apps.map(app => app.pm_id);
96
+
97
+ // scale app to use all CPUs available
98
+ if (apps.length !== maxCPU) {
99
+ pm2.scale(name, maxCPU, () => onAppRunning(reloadedAppIds));
100
+
101
+ } else {
102
+ onAppRunning(reloadedAppIds);
103
+ }
104
+ });
105
+ }
106
+
107
+ function resetAppStats (reloadedAppIds) {
108
+ reloadedAppIds.forEach((pm_id) => {
109
+ pm2.reset(pm_id, (err, _) => {
110
+ if (err) {
111
+ console.error(err);
112
+ } else {
113
+ console.log(`metrics re-set for app_id: ${pm_id}`);
114
+ }
115
+ });
116
+ });
117
+ };
118
+
80
119
  function updateColyseusBootService() {
81
120
  //
82
121
  // Update colyseus-boot.service to use the correct paths for the application
83
122
  //
84
-
85
123
  const COLYSEUS_CLOUD_BOOT_SERVICE = '/etc/systemd/system/colyseus-boot.service';
86
124
 
87
125
  // ignore if no boot service found
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const net = require('net');
5
+ const pm2 = require('pm2');
6
+
7
+ const COLYSEUS_CLOUD_URL = `${process.env.ENDPOINT}/vultr/stats`;
8
+
9
+ const FAILED_ATTEMPS_FILE = "/var/tmp/pm2-stats-attempts.txt";
10
+ const FETCH_TIMEOUT = 7000;
11
+
12
+ async function retryFailedAttempts() {
13
+ /**
14
+ * Retry cached failed attempts
15
+ */
16
+ if (!fs.existsSync(FAILED_ATTEMPS_FILE)) {
17
+ return;
18
+ }
19
+
20
+ // Retry up to last 30 attempts
21
+ const failedAttempts = fs.readFileSync(FAILED_ATTEMPS_FILE, "utf8").split("\n").slice(-30);
22
+
23
+ for (const body of failedAttempts) {
24
+ // skip if empty
25
+ if (!body) { continue; }
26
+
27
+ try {
28
+ await fetch(COLYSEUS_CLOUD_URL, {
29
+ method: "POST",
30
+ headers: {
31
+ "Content-Type": "application/json",
32
+ "Authorization": `Bearer ${process.env.COLYSEUS_SECRET}`,
33
+ "ngrok-skip-browser-warning": "yes",
34
+ },
35
+ body,
36
+ signal: AbortSignal.timeout(FETCH_TIMEOUT)
37
+ });
38
+ } catch (e) {
39
+ console.error(e);
40
+ }
41
+ }
42
+
43
+ fs.unlinkSync(FAILED_ATTEMPS_FILE);
44
+ }
45
+
46
+ async function fetchRetry(url, options, retries = 3) {
47
+ try {
48
+ return await fetch(url, options)
49
+ } catch (err) {
50
+ if (retries === 1) throw err;
51
+ return await fetchRetry(url, options, retries - 1);
52
+ }
53
+ };
54
+
55
+ pm2.Client.executeRemote('getMonitorData', {}, async function(err, list) {
56
+ const aggregate = { ccu: 0, roomcount: 0, };
57
+ const apps = {};
58
+
59
+ await Promise.all(list.map(async (item) => {
60
+ const env = item.pm2_env;
61
+ const app_id = env.pm_id;
62
+ const uptime = new Date(env.pm_uptime); // uptime in milliseconds
63
+ const axm_monitor = env.axm_monitor;
64
+ const restart_time = env.restart_time;
65
+ const status = env.status; // 'online', 'stopped', 'stopping', 'waiting restart', 'launching', 'errored'
66
+ const node_version = env.node_version;
67
+
68
+ const monit = {
69
+ cpu: item.monit.cpu, // 0 ~ 100 (%)
70
+ memory: item.monit.memory / 1024 / 1024, // in MB
71
+ };
72
+
73
+ const version = {};
74
+ if (env.versioning) {
75
+ version.revision = env.versioning.revision;
76
+ version.comment = env.versioning.comment;
77
+ version.branch = env.versioning.branch;
78
+ version.update_time = env.versioning.update_time;
79
+ }
80
+
81
+ aggregate.ccu += axm_monitor.ccu?.value ?? 0;
82
+ aggregate.roomcount += axm_monitor.roomcount?.value ?? 0;
83
+
84
+ const custom_monitor = {};
85
+ for (const originalKey in axm_monitor) {
86
+ const key = (originalKey.indexOf(" ") !== -1)
87
+ ? axm_monitor[originalKey].type
88
+ : originalKey;
89
+ custom_monitor[key] = Number(axm_monitor[originalKey].value);
90
+ }
91
+
92
+ // check if process .sock file is active
93
+ const socket_is_active = await checkSocketIsActive(`/run/colyseus/${(2567 + env.NODE_APP_INSTANCE)}.sock`);
94
+
95
+ apps[app_id] = {
96
+ pid: item.pid,
97
+ uptime,
98
+ app_id,
99
+ status,
100
+ restart_time,
101
+ ...monit,
102
+ ...custom_monitor,
103
+ version,
104
+ node_version,
105
+ socket_is_active,
106
+ };
107
+ }));
108
+
109
+ const fetchipv4 = await fetch("http://169.254.169.254/v1.json");
110
+ const ip = (await fetchipv4.json()).interfaces[0].ipv4.address;
111
+
112
+ const body = {
113
+ version: 1,
114
+ ip,
115
+ time: new Date(),
116
+ aggregate,
117
+ apps,
118
+ };
119
+
120
+ console.log(body);
121
+
122
+ try {
123
+ const response = await fetchRetry(COLYSEUS_CLOUD_URL, {
124
+ method: "POST",
125
+ headers: {
126
+ "Content-Type": "application/json",
127
+ "Authorization": `Bearer ${process.env.COLYSEUS_SECRET}`,
128
+ "ngrok-skip-browser-warning": "yes",
129
+ },
130
+ body: JSON.stringify(body),
131
+ signal: AbortSignal.timeout(FETCH_TIMEOUT)
132
+ });
133
+
134
+ if (response.status !== 200) {
135
+ throw new Error(`Failed to send stats to Colyseus Cloud. Status: ${response.status}`);
136
+ }
137
+
138
+ console.log("OK");
139
+
140
+ // Only retry failed attempts if the current attempt was successful
141
+ await retryFailedAttempts();
142
+
143
+ } catch (e) {
144
+ console.error("Failed to send stats to Colyseus Cloud. ");
145
+
146
+ // cache failed attempts
147
+ fs.appendFileSync(FAILED_ATTEMPS_FILE, JSON.stringify(body) + "\n");
148
+
149
+ } finally {
150
+ process.exit();
151
+ }
152
+ });
153
+
154
+ function checkSocketIsActive(sockFilePath) {
155
+ return new Promise((resolve, _) => {
156
+ const client = net.createConnection({ path: sockFilePath, timeout: 5000 })
157
+ .on('connect', () => {
158
+ client.end(); // close the connection
159
+ resolve(true);
160
+ })
161
+ .on('error', () => resolve(false))
162
+ .on('timeout', () => resolve(false));
163
+ });
164
+ }