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/index.js
CHANGED
|
@@ -1,168 +1,168 @@
|
|
|
1
|
-
import { GameGlueAuth } from './auth';
|
|
2
|
-
import { io } from "socket.io-client";
|
|
3
|
-
import { Listener } from "./listener";
|
|
4
|
-
import { PresenceListener } from "./presence_listener";
|
|
5
|
-
import { isCorsError, logCorsHelp } from './utils';
|
|
6
|
-
import { getGameSchema, getCategorySchema, normalizeTelemetry } from '@gameglue/schemas';
|
|
7
|
-
|
|
8
|
-
const GAME_IDS = {
|
|
9
|
-
'msfs': true,
|
|
10
|
-
'xplane': true,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const DEFAULT_SOCKET_URL = 'https://socks.gameglue.gg';
|
|
14
|
-
|
|
15
|
-
class GameGlue extends GameGlueAuth {
|
|
16
|
-
constructor(cfg) {
|
|
17
|
-
super(cfg);
|
|
18
|
-
this._socket = null;
|
|
19
|
-
this._socketUrl = cfg.socketUrl || DEFAULT_SOCKET_URL;
|
|
20
|
-
this._connectPromise = null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Create a listener for game telemetry.
|
|
25
|
-
* Connects to socket server lazily on first call.
|
|
26
|
-
* @param {Object} config - { userId, gameId, fields? }
|
|
27
|
-
* @returns {Promise<Listener>}
|
|
28
|
-
*/
|
|
29
|
-
async createListener(config) {
|
|
30
|
-
if (!config) throw new Error('Not a valid listener config');
|
|
31
|
-
if (!config.gameId || !GAME_IDS[config.gameId]) throw new Error('Not a valid Game ID');
|
|
32
|
-
if (!config.userId) throw new Error('User ID not supplied');
|
|
33
|
-
if (config.fields && !Array.isArray(config.fields)) throw new Error('fields must be an array');
|
|
34
|
-
|
|
35
|
-
// Ensure socket is connected (lazy initialization)
|
|
36
|
-
await this._ensureConnected();
|
|
37
|
-
|
|
38
|
-
const listener = new Listener(this._socket, config);
|
|
39
|
-
const establishConnectionResponse = await listener.establishConnection();
|
|
40
|
-
|
|
41
|
-
// Handle reconnection
|
|
42
|
-
this._socket.io.on('reconnect_attempt', () => {
|
|
43
|
-
this._updateSocketAuth(this.getAccessToken());
|
|
44
|
-
});
|
|
45
|
-
this._socket.io.on('reconnect', () => {
|
|
46
|
-
listener.establishConnection();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (establishConnectionResponse.status !== 'success') {
|
|
50
|
-
throw new Error(`There was a problem setting up the listener. Reason: ${establishConnectionResponse.reason}`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return listener.setupEventListener();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Create a presence listener for public broadcaster updates.
|
|
58
|
-
* This does not require authentication.
|
|
59
|
-
* @param {Object} config - { clientId, gameId }
|
|
60
|
-
* @returns {Promise<PresenceListener>}
|
|
61
|
-
*/
|
|
62
|
-
async createPresenceListener(config) {
|
|
63
|
-
if (!config) throw new Error('Not a valid presence listener config');
|
|
64
|
-
if (!config.gameId || !GAME_IDS[config.gameId]) throw new Error('Not a valid Game ID');
|
|
65
|
-
if (!config.clientId) throw new Error('Client ID not supplied');
|
|
66
|
-
|
|
67
|
-
const socket = io(this._socketUrl, {
|
|
68
|
-
transports: ['websocket'],
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
await new Promise((resolve, reject) => {
|
|
72
|
-
socket.on('connect', resolve);
|
|
73
|
-
socket.on('connect_error', (err) => {
|
|
74
|
-
if (isCorsError(err)) {
|
|
75
|
-
logCorsHelp('WebSocket Connection', this._socketUrl);
|
|
76
|
-
}
|
|
77
|
-
reject(new Error(`Socket connection failed: ${err.message}`));
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const listener = new PresenceListener(socket, config);
|
|
82
|
-
const establishConnectionResponse = await listener.establishConnection();
|
|
83
|
-
|
|
84
|
-
// Handle reconnection
|
|
85
|
-
socket.io.on('reconnect', () => {
|
|
86
|
-
listener.establishConnection();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
if (establishConnectionResponse.status !== 'success') {
|
|
90
|
-
throw new Error(
|
|
91
|
-
`There was a problem setting up the presence listener. Reason: ${establishConnectionResponse.reason}`
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return listener.setupEventListener();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// ============ Internal Methods ============
|
|
99
|
-
|
|
100
|
-
async _ensureConnected() {
|
|
101
|
-
// Already connected
|
|
102
|
-
if (this._socket?.connected) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Connection in progress - wait for it
|
|
107
|
-
if (this._connectPromise) {
|
|
108
|
-
await this._connectPromise;
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Start new connection
|
|
113
|
-
this._connectPromise = this._connect();
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
await this._connectPromise;
|
|
117
|
-
} finally {
|
|
118
|
-
this._connectPromise = null;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
_connect() {
|
|
123
|
-
return new Promise((resolve, reject) => {
|
|
124
|
-
const token = this.getAccessToken();
|
|
125
|
-
|
|
126
|
-
if (!token) {
|
|
127
|
-
reject(new Error('Not authenticated - call isAuthenticated() first'));
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
this._socket = io(this._socketUrl, {
|
|
132
|
-
transports: ['websocket'],
|
|
133
|
-
auth: { token }
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
this._socket.on('connect', () => {
|
|
137
|
-
resolve();
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
this._socket.on('connect_error', (err) => {
|
|
141
|
-
if (isCorsError(err)) {
|
|
142
|
-
logCorsHelp('WebSocket Connection', this._socketUrl);
|
|
143
|
-
}
|
|
144
|
-
reject(new Error(`Socket connection failed: ${err.message}`));
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// Update socket auth when token refreshes
|
|
148
|
-
this.onTokenRefreshed((newToken) => {
|
|
149
|
-
this._updateSocketAuth(newToken);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
_updateSocketAuth(authToken) {
|
|
155
|
-
if (this._socket) {
|
|
156
|
-
this._socket.auth.token = authToken;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (typeof window !== 'undefined') {
|
|
162
|
-
window.GameGlue = GameGlue;
|
|
163
|
-
// Expose schema utilities for validation tools
|
|
164
|
-
window.GameGlueSchemas = { getGameSchema, getCategorySchema, normalizeTelemetry };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export default GameGlue;
|
|
168
|
-
export { GameGlue, getGameSchema, getCategorySchema, normalizeTelemetry };
|
|
1
|
+
import { GameGlueAuth } from './auth';
|
|
2
|
+
import { io } from "socket.io-client";
|
|
3
|
+
import { Listener } from "./listener";
|
|
4
|
+
import { PresenceListener } from "./presence_listener";
|
|
5
|
+
import { isCorsError, logCorsHelp } from './utils';
|
|
6
|
+
import { getGameSchema, getCategorySchema, normalizeTelemetry } from '@gameglue/schemas';
|
|
7
|
+
|
|
8
|
+
const GAME_IDS = {
|
|
9
|
+
'msfs': true,
|
|
10
|
+
'xplane': true,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const DEFAULT_SOCKET_URL = 'https://socks.gameglue.gg';
|
|
14
|
+
|
|
15
|
+
class GameGlue extends GameGlueAuth {
|
|
16
|
+
constructor(cfg) {
|
|
17
|
+
super(cfg);
|
|
18
|
+
this._socket = null;
|
|
19
|
+
this._socketUrl = cfg.socketUrl || DEFAULT_SOCKET_URL;
|
|
20
|
+
this._connectPromise = null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Create a listener for game telemetry.
|
|
25
|
+
* Connects to socket server lazily on first call.
|
|
26
|
+
* @param {Object} config - { userId, gameId, fields? }
|
|
27
|
+
* @returns {Promise<Listener>}
|
|
28
|
+
*/
|
|
29
|
+
async createListener(config) {
|
|
30
|
+
if (!config) throw new Error('Not a valid listener config');
|
|
31
|
+
if (!config.gameId || !GAME_IDS[config.gameId]) throw new Error('Not a valid Game ID');
|
|
32
|
+
if (!config.userId) throw new Error('User ID not supplied');
|
|
33
|
+
if (config.fields && !Array.isArray(config.fields)) throw new Error('fields must be an array');
|
|
34
|
+
|
|
35
|
+
// Ensure socket is connected (lazy initialization)
|
|
36
|
+
await this._ensureConnected();
|
|
37
|
+
|
|
38
|
+
const listener = new Listener(this._socket, config);
|
|
39
|
+
const establishConnectionResponse = await listener.establishConnection();
|
|
40
|
+
|
|
41
|
+
// Handle reconnection
|
|
42
|
+
this._socket.io.on('reconnect_attempt', () => {
|
|
43
|
+
this._updateSocketAuth(this.getAccessToken());
|
|
44
|
+
});
|
|
45
|
+
this._socket.io.on('reconnect', () => {
|
|
46
|
+
listener.establishConnection();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (establishConnectionResponse.status !== 'success') {
|
|
50
|
+
throw new Error(`There was a problem setting up the listener. Reason: ${establishConnectionResponse.reason}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return listener.setupEventListener();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a presence listener for public broadcaster updates.
|
|
58
|
+
* This does not require authentication.
|
|
59
|
+
* @param {Object} config - { clientId, gameId }
|
|
60
|
+
* @returns {Promise<PresenceListener>}
|
|
61
|
+
*/
|
|
62
|
+
async createPresenceListener(config) {
|
|
63
|
+
if (!config) throw new Error('Not a valid presence listener config');
|
|
64
|
+
if (!config.gameId || !GAME_IDS[config.gameId]) throw new Error('Not a valid Game ID');
|
|
65
|
+
if (!config.clientId) throw new Error('Client ID not supplied');
|
|
66
|
+
|
|
67
|
+
const socket = io(this._socketUrl, {
|
|
68
|
+
transports: ['websocket'],
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await new Promise((resolve, reject) => {
|
|
72
|
+
socket.on('connect', resolve);
|
|
73
|
+
socket.on('connect_error', (err) => {
|
|
74
|
+
if (isCorsError(err)) {
|
|
75
|
+
logCorsHelp('WebSocket Connection', this._socketUrl);
|
|
76
|
+
}
|
|
77
|
+
reject(new Error(`Socket connection failed: ${err.message}`));
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const listener = new PresenceListener(socket, config);
|
|
82
|
+
const establishConnectionResponse = await listener.establishConnection();
|
|
83
|
+
|
|
84
|
+
// Handle reconnection
|
|
85
|
+
socket.io.on('reconnect', () => {
|
|
86
|
+
listener.establishConnection();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (establishConnectionResponse.status !== 'success') {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`There was a problem setting up the presence listener. Reason: ${establishConnectionResponse.reason}`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return listener.setupEventListener();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ============ Internal Methods ============
|
|
99
|
+
|
|
100
|
+
async _ensureConnected() {
|
|
101
|
+
// Already connected
|
|
102
|
+
if (this._socket?.connected) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Connection in progress - wait for it
|
|
107
|
+
if (this._connectPromise) {
|
|
108
|
+
await this._connectPromise;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Start new connection
|
|
113
|
+
this._connectPromise = this._connect();
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await this._connectPromise;
|
|
117
|
+
} finally {
|
|
118
|
+
this._connectPromise = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_connect() {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const token = this.getAccessToken();
|
|
125
|
+
|
|
126
|
+
if (!token) {
|
|
127
|
+
reject(new Error('Not authenticated - call isAuthenticated() first'));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this._socket = io(this._socketUrl, {
|
|
132
|
+
transports: ['websocket'],
|
|
133
|
+
auth: { token }
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
this._socket.on('connect', () => {
|
|
137
|
+
resolve();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
this._socket.on('connect_error', (err) => {
|
|
141
|
+
if (isCorsError(err)) {
|
|
142
|
+
logCorsHelp('WebSocket Connection', this._socketUrl);
|
|
143
|
+
}
|
|
144
|
+
reject(new Error(`Socket connection failed: ${err.message}`));
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Update socket auth when token refreshes
|
|
148
|
+
this.onTokenRefreshed((newToken) => {
|
|
149
|
+
this._updateSocketAuth(newToken);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
_updateSocketAuth(authToken) {
|
|
155
|
+
if (this._socket) {
|
|
156
|
+
this._socket.auth.token = authToken;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (typeof window !== 'undefined') {
|
|
162
|
+
window.GameGlue = GameGlue;
|
|
163
|
+
// Expose schema utilities for validation tools
|
|
164
|
+
window.GameGlueSchemas = { getGameSchema, getCategorySchema, normalizeTelemetry };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export default GameGlue;
|
|
168
|
+
export { GameGlue, getGameSchema, getCategorySchema, normalizeTelemetry };
|