gameglue 4.0.0 → 4.0.2
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/LICENSE +21 -21
- package/README.md +275 -275
- package/babel.config.cjs +5 -5
- package/coverage/auth.js.html +525 -525
- package/coverage/base.css +224 -224
- package/coverage/block-navigation.js +87 -87
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +175 -175
- package/coverage/index.js.html +309 -309
- package/coverage/lcov-report/auth.js.html +525 -525
- package/coverage/lcov-report/base.css +224 -224
- package/coverage/lcov-report/block-navigation.js +87 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +175 -175
- package/coverage/lcov-report/index.js.html +309 -309
- package/coverage/lcov-report/listener.js.html +528 -528
- package/coverage/lcov-report/prettify.css +1 -1
- package/coverage/lcov-report/prettify.js +2 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -210
- package/coverage/lcov-report/user.js.html +117 -117
- package/coverage/lcov-report/utils.js.html +117 -117
- package/coverage/lcov.info +391 -391
- package/coverage/listener.js.html +528 -528
- package/coverage/prettify.css +1 -1
- package/coverage/prettify.js +2 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -210
- package/coverage/user.js.html +117 -117
- package/coverage/utils.js.html +117 -117
- package/dist/gg.cjs.js +1 -1
- package/dist/gg.cjs.js.map +1 -1
- package/dist/gg.esm.js +1 -1
- package/dist/gg.esm.js.map +1 -1
- package/dist/gg.umd.js +1 -1
- package/dist/gg.umd.js.map +1 -1
- package/examples/certs/cert.pem +19 -19
- package/examples/certs/key.pem +28 -28
- package/examples/flight-dashboard.html +431 -431
- package/examples/server.js +99 -99
- package/examples/telemetry-validator.html +1410 -1410
- package/jest.config.cjs +33 -33
- package/package.json +56 -56
- package/rollup.config.js +57 -57
- package/src/auth.js +255 -255
- package/src/auth.spec.js +481 -481
- package/src/index.js +168 -168
- package/src/listener.js +196 -193
- package/src/listener.spec.js +598 -598
- package/src/presence_listener.js +112 -112
- package/src/test/fixtures.js +106 -106
- package/src/test/setup.js +51 -51
- package/src/utils.js +63 -63
- package/src/utils.spec.js +78 -78
- package/types/index.d.ts +338 -338
- package/webpack.config.js +15 -15
package/src/listener.js
CHANGED
|
@@ -1,193 +1,196 @@
|
|
|
1
|
-
import EventEmitter from 'event-emitter';
|
|
2
|
-
import { getGameSchema, normalizeTelemetry } from '@gameglue/schemas';
|
|
3
|
-
|
|
4
|
-
export class Listener {
|
|
5
|
-
constructor(socket, config) {
|
|
6
|
-
this._config = config;
|
|
7
|
-
this._socket = socket;
|
|
8
|
-
this._callbacks = [];
|
|
9
|
-
this._fields = config.fields ? [...config.fields] : null;
|
|
10
|
-
this._gameSchema = getGameSchema(config.gameId);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async establishConnection() {
|
|
14
|
-
if (!this._socket || !this._config.userId || !this._config.gameId) {
|
|
15
|
-
throw new Error('Missing arguments in establishConnection');
|
|
16
|
-
}
|
|
17
|
-
return new Promise((resolve) => {
|
|
18
|
-
// Use object format if fields are specified, otherwise use legacy string format
|
|
19
|
-
let listenPayload;
|
|
20
|
-
if (this._fields) {
|
|
21
|
-
listenPayload = {
|
|
22
|
-
userId: this._config.userId,
|
|
23
|
-
gameId: this._config.gameId,
|
|
24
|
-
fields: this._fields
|
|
25
|
-
};
|
|
26
|
-
} else {
|
|
27
|
-
listenPayload = `${this._config.userId}:${this._config.gameId}`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
this._socket.timeout(5000).emit('listen', listenPayload, (error, response) => {
|
|
31
|
-
if (error) {
|
|
32
|
-
return resolve({status: 'failed', reason: 'Listen request timed out.'});
|
|
33
|
-
}
|
|
34
|
-
if (response.status === 'success') {
|
|
35
|
-
return resolve({status: 'success'});
|
|
36
|
-
} else {
|
|
37
|
-
return resolve({status: 'failed', reason: response.reason});
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
setupEventListener() {
|
|
44
|
-
// Listen for telemetry updates
|
|
45
|
-
this._socket.on('update', (payload) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
*
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
1
|
+
import EventEmitter from 'event-emitter';
|
|
2
|
+
import { getGameSchema, normalizeTelemetry } from '@gameglue/schemas';
|
|
3
|
+
|
|
4
|
+
export class Listener {
|
|
5
|
+
constructor(socket, config) {
|
|
6
|
+
this._config = config;
|
|
7
|
+
this._socket = socket;
|
|
8
|
+
this._callbacks = [];
|
|
9
|
+
this._fields = config.fields ? [...config.fields] : null;
|
|
10
|
+
this._gameSchema = getGameSchema(config.gameId);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async establishConnection() {
|
|
14
|
+
if (!this._socket || !this._config.userId || !this._config.gameId) {
|
|
15
|
+
throw new Error('Missing arguments in establishConnection');
|
|
16
|
+
}
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
// Use object format if fields are specified, otherwise use legacy string format
|
|
19
|
+
let listenPayload;
|
|
20
|
+
if (this._fields) {
|
|
21
|
+
listenPayload = {
|
|
22
|
+
userId: this._config.userId,
|
|
23
|
+
gameId: this._config.gameId,
|
|
24
|
+
fields: this._fields
|
|
25
|
+
};
|
|
26
|
+
} else {
|
|
27
|
+
listenPayload = `${this._config.userId}:${this._config.gameId}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this._socket.timeout(5000).emit('listen', listenPayload, (error, response) => {
|
|
31
|
+
if (error) {
|
|
32
|
+
return resolve({status: 'failed', reason: 'Listen request timed out.'});
|
|
33
|
+
}
|
|
34
|
+
if (response.status === 'success') {
|
|
35
|
+
return resolve({status: 'success'});
|
|
36
|
+
} else {
|
|
37
|
+
return resolve({status: 'failed', reason: response.reason});
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
setupEventListener() {
|
|
44
|
+
// Listen for telemetry updates
|
|
45
|
+
this._socket.on('update', (payload) => {
|
|
46
|
+
// Filter events by gameId when present (multiple listeners share one socket)
|
|
47
|
+
if (payload?.gameId && payload.gameId !== this._config.gameId) return;
|
|
48
|
+
|
|
49
|
+
const rawData = payload?.data;
|
|
50
|
+
|
|
51
|
+
// Normalize telemetry to canonical field names
|
|
52
|
+
const normalizedData = rawData && this._gameSchema
|
|
53
|
+
? normalizeTelemetry(rawData, this._gameSchema)
|
|
54
|
+
: rawData;
|
|
55
|
+
|
|
56
|
+
// Apply client-side field filtering on normalized data
|
|
57
|
+
let filteredData = normalizedData;
|
|
58
|
+
if (this._fields && this._fields.length > 0 && normalizedData) {
|
|
59
|
+
filteredData = {};
|
|
60
|
+
for (const field of this._fields) {
|
|
61
|
+
if (field in normalizedData) {
|
|
62
|
+
filteredData[field] = normalizedData[field];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Emit with both raw and normalized data
|
|
68
|
+
this.emit('update', {
|
|
69
|
+
...payload,
|
|
70
|
+
raw: rawData,
|
|
71
|
+
data: filteredData
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Listen for derived events (landing, takeoff, flight_phase, etc.)
|
|
76
|
+
this._socket.on('key-events', (payload) => {
|
|
77
|
+
const { gameId, eventType, data } = payload || {};
|
|
78
|
+
|
|
79
|
+
// Only emit events for our game
|
|
80
|
+
if (gameId !== this._config.gameId) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Emit specific event type (landing, takeoff, flight_phase)
|
|
85
|
+
if (eventType && data) {
|
|
86
|
+
this.emit(eventType, data);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Subscribe to additional fields dynamically
|
|
95
|
+
* @param {string[]} fields - Array of field names to add
|
|
96
|
+
* @returns {Promise<{status: string, reason?: string}>}
|
|
97
|
+
*/
|
|
98
|
+
async subscribe(fields) {
|
|
99
|
+
if (!Array.isArray(fields) || fields.length === 0) {
|
|
100
|
+
throw new Error('fields must be a non-empty array');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Add new fields to existing list
|
|
104
|
+
if (!this._fields) {
|
|
105
|
+
this._fields = [...fields];
|
|
106
|
+
} else {
|
|
107
|
+
for (const field of fields) {
|
|
108
|
+
if (!this._fields.includes(field)) {
|
|
109
|
+
this._fields.push(field);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return this._updateSubscription();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Unsubscribe from specific fields
|
|
119
|
+
* @param {string[]} fields - Array of field names to remove
|
|
120
|
+
* @returns {Promise<{status: string, reason?: string}>}
|
|
121
|
+
*/
|
|
122
|
+
async unsubscribe(fields) {
|
|
123
|
+
if (!Array.isArray(fields) || fields.length === 0) {
|
|
124
|
+
throw new Error('fields must be a non-empty array');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!this._fields) {
|
|
128
|
+
// Currently receiving all fields, create explicit list without these fields
|
|
129
|
+
throw new Error('Cannot unsubscribe when receiving all fields. Use subscribe() first to set explicit field list.');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this._fields = this._fields.filter(f => !fields.includes(f));
|
|
133
|
+
|
|
134
|
+
return this._updateSubscription();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get the current list of subscribed fields
|
|
139
|
+
* @returns {string[]|null} - Array of field names, or null if receiving all fields
|
|
140
|
+
*/
|
|
141
|
+
getFields() {
|
|
142
|
+
return this._fields ? [...this._fields] : null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Send a command to the broadcaster (game client)
|
|
147
|
+
* @param {string} command - The canonical command name (e.g., 'gear_up', 'set_autopilot_heading')
|
|
148
|
+
* @param {any} value - The value to set (optional for toggle commands)
|
|
149
|
+
* @returns {Promise<{status: string, reason?: string}>}
|
|
150
|
+
*/
|
|
151
|
+
async sendCommand(command, value) {
|
|
152
|
+
if (!command || typeof command !== 'string') {
|
|
153
|
+
throw new Error('command must be a non-empty string');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return new Promise((resolve) => {
|
|
157
|
+
const payload = {
|
|
158
|
+
userId: this._config.userId,
|
|
159
|
+
gameId: this._config.gameId,
|
|
160
|
+
data: {
|
|
161
|
+
fieldName: command,
|
|
162
|
+
value
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
this._socket.timeout(5000).emit('set', payload, (error, response) => {
|
|
167
|
+
if (error) {
|
|
168
|
+
return resolve({ status: 'failed', reason: 'Command request timed out.' });
|
|
169
|
+
}
|
|
170
|
+
return resolve(response);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Internal method to send subscription update to server
|
|
177
|
+
*/
|
|
178
|
+
async _updateSubscription() {
|
|
179
|
+
return new Promise((resolve) => {
|
|
180
|
+
const payload = {
|
|
181
|
+
userId: this._config.userId,
|
|
182
|
+
gameId: this._config.gameId,
|
|
183
|
+
fields: this._fields
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
this._socket.timeout(5000).emit('listen-update', payload, (error, response) => {
|
|
187
|
+
if (error) {
|
|
188
|
+
return resolve({status: 'failed', reason: 'Update request timed out.'});
|
|
189
|
+
}
|
|
190
|
+
return resolve(response);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
EventEmitter(Listener.prototype);
|