@genex-ai/embed-sdk 0.12.0 → 0.14.0
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/dist/{chunk-EEOKYBXG.js → chunk-5SVRR26C.js} +446 -9
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/sentry.js +1 -1
- package/package.json +4 -2
|
@@ -1,11 +1,249 @@
|
|
|
1
|
+
// ../embed-protocol/src/constants.ts
|
|
2
|
+
var NATIVE_CHANNEL = "genex-native";
|
|
3
|
+
var NATIVE_PROTOCOL_VERSION = 1;
|
|
4
|
+
var NATIVE_MARKER_PARAM = "genex_native";
|
|
5
|
+
var NATIVE_MARKER_VALUE = "1";
|
|
6
|
+
var NATIVE_PROTOCOL_PARAM = "genex_protocol";
|
|
7
|
+
var NATIVE_ATTEMPT_PARAM = "genex_attempt";
|
|
8
|
+
var NATIVE_NONCE_PARAM = "genex_nonce";
|
|
9
|
+
var NATIVE_RECEIVER = "__genexNative";
|
|
10
|
+
var MAX_BRIDGE_MESSAGE_BYTES = 4096;
|
|
11
|
+
var BRIDGE_ID_RE = /^[0-9a-f]{32}$/;
|
|
12
|
+
var SLUG_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
13
|
+
function isBridgeId(value) {
|
|
14
|
+
return typeof value === "string" && BRIDGE_ID_RE.test(value);
|
|
15
|
+
}
|
|
16
|
+
function isBridgeSlug(value) {
|
|
17
|
+
return typeof value === "string" && SLUG_RE.test(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ../embed-protocol/src/base64url.ts
|
|
21
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
22
|
+
var LOOKUP = (() => {
|
|
23
|
+
const table = new Array(128).fill(-1);
|
|
24
|
+
for (let i = 0; i < ALPHABET.length; i++) table[ALPHABET.charCodeAt(i)] = i;
|
|
25
|
+
return table;
|
|
26
|
+
})();
|
|
27
|
+
function utf8Bytes(text) {
|
|
28
|
+
const out = [];
|
|
29
|
+
for (let i = 0; i < text.length; i++) {
|
|
30
|
+
let code = text.charCodeAt(i);
|
|
31
|
+
if (code >= 55296 && code <= 56319) {
|
|
32
|
+
const next = i + 1 < text.length ? text.charCodeAt(i + 1) : 0;
|
|
33
|
+
if (next >= 56320 && next <= 57343) {
|
|
34
|
+
code = 65536 + (code - 55296 << 10) + (next - 56320);
|
|
35
|
+
i++;
|
|
36
|
+
} else {
|
|
37
|
+
code = 65533;
|
|
38
|
+
}
|
|
39
|
+
} else if (code >= 56320 && code <= 57343) {
|
|
40
|
+
code = 65533;
|
|
41
|
+
}
|
|
42
|
+
if (code < 128) out.push(code);
|
|
43
|
+
else if (code < 2048) out.push(192 | code >> 6, 128 | code & 63);
|
|
44
|
+
else if (code < 65536) {
|
|
45
|
+
out.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
|
|
46
|
+
} else {
|
|
47
|
+
out.push(
|
|
48
|
+
240 | code >> 18,
|
|
49
|
+
128 | code >> 12 & 63,
|
|
50
|
+
128 | code >> 6 & 63,
|
|
51
|
+
128 | code & 63
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
function utf8Text(bytes) {
|
|
58
|
+
let out = "";
|
|
59
|
+
for (let i = 0; i < bytes.length; ) {
|
|
60
|
+
const b0 = bytes[i];
|
|
61
|
+
let code;
|
|
62
|
+
let size;
|
|
63
|
+
if (b0 < 128) {
|
|
64
|
+
code = b0;
|
|
65
|
+
size = 1;
|
|
66
|
+
} else if ((b0 & 224) === 192) {
|
|
67
|
+
code = b0 & 31;
|
|
68
|
+
size = 2;
|
|
69
|
+
} else if ((b0 & 240) === 224) {
|
|
70
|
+
code = b0 & 15;
|
|
71
|
+
size = 3;
|
|
72
|
+
} else if ((b0 & 248) === 240) {
|
|
73
|
+
code = b0 & 7;
|
|
74
|
+
size = 4;
|
|
75
|
+
} else return null;
|
|
76
|
+
if (i + size > bytes.length) return null;
|
|
77
|
+
for (let k = 1; k < size; k++) {
|
|
78
|
+
const b = bytes[i + k];
|
|
79
|
+
if ((b & 192) !== 128) return null;
|
|
80
|
+
code = code << 6 | b & 63;
|
|
81
|
+
}
|
|
82
|
+
if (size === 2 && code < 128) return null;
|
|
83
|
+
if (size === 3 && (code < 2048 || code >= 55296 && code <= 57343)) return null;
|
|
84
|
+
if (size === 4 && (code < 65536 || code > 1114111)) return null;
|
|
85
|
+
if (code > 65535) {
|
|
86
|
+
const v = code - 65536;
|
|
87
|
+
out += String.fromCharCode(55296 + (v >> 10), 56320 + (v & 1023));
|
|
88
|
+
} else {
|
|
89
|
+
out += String.fromCharCode(code);
|
|
90
|
+
}
|
|
91
|
+
i += size;
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
function utf8ByteLength(text) {
|
|
96
|
+
return utf8Bytes(text).length;
|
|
97
|
+
}
|
|
98
|
+
function encodeBase64UrlJson(value) {
|
|
99
|
+
const bytes = utf8Bytes(JSON.stringify(value));
|
|
100
|
+
let out = "";
|
|
101
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
102
|
+
const b0 = bytes[i];
|
|
103
|
+
const b1 = bytes[i + 1];
|
|
104
|
+
const b2 = bytes[i + 2];
|
|
105
|
+
out += ALPHABET[b0 >> 2];
|
|
106
|
+
out += ALPHABET[(b0 & 3) << 4 | (b1 ?? 0) >> 4];
|
|
107
|
+
if (b1 === void 0) break;
|
|
108
|
+
out += ALPHABET[(b1 & 15) << 2 | (b2 ?? 0) >> 6];
|
|
109
|
+
if (b2 === void 0) break;
|
|
110
|
+
out += ALPHABET[b2 & 63];
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
function decodeBase64UrlJson(encoded) {
|
|
115
|
+
if (encoded.length % 4 === 1) return void 0;
|
|
116
|
+
const bytes = [];
|
|
117
|
+
let buffer = 0;
|
|
118
|
+
let bits = 0;
|
|
119
|
+
for (let i = 0; i < encoded.length; i++) {
|
|
120
|
+
const code = encoded.charCodeAt(i);
|
|
121
|
+
const value = code < 128 ? LOOKUP[code] : -1;
|
|
122
|
+
if (value < 0) return void 0;
|
|
123
|
+
buffer = buffer << 6 | value;
|
|
124
|
+
bits += 6;
|
|
125
|
+
if (bits >= 8) {
|
|
126
|
+
bits -= 8;
|
|
127
|
+
bytes.push(buffer >> bits & 255);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (bits > 0 && (buffer & (1 << bits) - 1) !== 0) return void 0;
|
|
131
|
+
const text = utf8Text(bytes);
|
|
132
|
+
if (text === null) return void 0;
|
|
133
|
+
try {
|
|
134
|
+
return JSON.parse(text);
|
|
135
|
+
} catch {
|
|
136
|
+
return void 0;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ../embed-protocol/src/messages.ts
|
|
141
|
+
var MAX_TICKET_LENGTH = 2048;
|
|
142
|
+
var MAX_EXPIRES_AT_LENGTH = 32;
|
|
143
|
+
var ISO_INSTANT_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
|
|
144
|
+
function isRecord(value) {
|
|
145
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
146
|
+
}
|
|
147
|
+
function hasExactKeys(value, keys) {
|
|
148
|
+
const own = Object.keys(value);
|
|
149
|
+
if (own.length !== keys.length) return false;
|
|
150
|
+
for (const key of keys) {
|
|
151
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) return false;
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
var BASE_KEYS = ["channel", "v", "type", "slug", "attemptId", "nonce"];
|
|
156
|
+
function envelopeBaseIsValid(value) {
|
|
157
|
+
return value.channel === NATIVE_CHANNEL && value.v === NATIVE_PROTOCOL_VERSION && isBridgeSlug(value.slug) && isBridgeId(value.attemptId) && isBridgeId(value.nonce);
|
|
158
|
+
}
|
|
159
|
+
function parseBridgeNativeCommand(value) {
|
|
160
|
+
if (!isRecord(value) || !envelopeBaseIsValid(value)) return void 0;
|
|
161
|
+
switch (value.type) {
|
|
162
|
+
case "ticket":
|
|
163
|
+
if (!hasExactKeys(value, [...BASE_KEYS, "ticket", "expiresAt"])) return void 0;
|
|
164
|
+
if (typeof value.ticket !== "string" || value.ticket.length === 0 || value.ticket.length > MAX_TICKET_LENGTH) {
|
|
165
|
+
return void 0;
|
|
166
|
+
}
|
|
167
|
+
if (typeof value.expiresAt !== "string" || value.expiresAt.length > MAX_EXPIRES_AT_LENGTH || !ISO_INSTANT_RE.test(value.expiresAt)) {
|
|
168
|
+
return void 0;
|
|
169
|
+
}
|
|
170
|
+
return value;
|
|
171
|
+
case "guest":
|
|
172
|
+
case "cancel":
|
|
173
|
+
if (!hasExactKeys(value, BASE_KEYS)) return void 0;
|
|
174
|
+
return value;
|
|
175
|
+
case "foreground":
|
|
176
|
+
if (!hasExactKeys(value, [...BASE_KEYS, "active"])) return void 0;
|
|
177
|
+
if (typeof value.active !== "boolean") return void 0;
|
|
178
|
+
return value;
|
|
179
|
+
case "network":
|
|
180
|
+
if (!hasExactKeys(value, [...BASE_KEYS, "online"])) return void 0;
|
|
181
|
+
if (typeof value.online !== "boolean") return void 0;
|
|
182
|
+
return value;
|
|
183
|
+
default:
|
|
184
|
+
return void 0;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ../embed-protocol/src/transport.ts
|
|
189
|
+
function read(raw, parse) {
|
|
190
|
+
if (typeof raw !== "string" || raw.length === 0) return { ok: false, reason: "undecodable" };
|
|
191
|
+
if (utf8ByteLength(raw) > MAX_BRIDGE_MESSAGE_BYTES) return { ok: false, reason: "too-large" };
|
|
192
|
+
const decoded = decodeBase64UrlJson(raw);
|
|
193
|
+
if (decoded === void 0) return { ok: false, reason: "undecodable" };
|
|
194
|
+
const message = parse(decoded);
|
|
195
|
+
if (message === void 0) return { ok: false, reason: "invalid" };
|
|
196
|
+
return { ok: true, message };
|
|
197
|
+
}
|
|
198
|
+
function readBridgeNativeCommand(raw) {
|
|
199
|
+
return read(raw, parseBridgeNativeCommand);
|
|
200
|
+
}
|
|
201
|
+
function encodeBridgeMessage(message) {
|
|
202
|
+
const encoded = encodeBase64UrlJson(message);
|
|
203
|
+
if (encoded.length > MAX_BRIDGE_MESSAGE_BYTES) return void 0;
|
|
204
|
+
return encoded;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ../embed-protocol/src/entry-url.ts
|
|
208
|
+
function readParam(search, key) {
|
|
209
|
+
const query = search.charAt(0) === "?" ? search.slice(1) : search;
|
|
210
|
+
if (query.length === 0) return null;
|
|
211
|
+
for (const pair of query.split("&")) {
|
|
212
|
+
const eq = pair.indexOf("=");
|
|
213
|
+
if (eq === -1) continue;
|
|
214
|
+
if (pair.slice(0, eq) === key) return pair.slice(eq + 1);
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
function hasNativeMarker(search) {
|
|
219
|
+
return readParam(search, NATIVE_MARKER_PARAM) === NATIVE_MARKER_VALUE;
|
|
220
|
+
}
|
|
221
|
+
function readNativeEntry(search) {
|
|
222
|
+
if (!hasNativeMarker(search)) return null;
|
|
223
|
+
const attemptId = readParam(search, NATIVE_ATTEMPT_PARAM);
|
|
224
|
+
const nonce = readParam(search, NATIVE_NONCE_PARAM);
|
|
225
|
+
const protocol = Number(readParam(search, NATIVE_PROTOCOL_PARAM));
|
|
226
|
+
if (!isBridgeId(attemptId) || !isBridgeId(nonce)) return null;
|
|
227
|
+
if (protocol !== NATIVE_PROTOCOL_VERSION) return null;
|
|
228
|
+
return { attemptId, nonce, protocol };
|
|
229
|
+
}
|
|
230
|
+
|
|
1
231
|
// src/index.ts
|
|
2
232
|
var PROTOCOL_VERSION = 1;
|
|
3
233
|
var RETRY_FLAG = "genex:embed:retry";
|
|
4
234
|
var POPOVER_DISMISSED_FLAG = "genex:guest:popover-dismissed";
|
|
235
|
+
var LOCAL_AUTH_FLAG = "genex:embed:local-auth";
|
|
236
|
+
var PLAYER_ID_KEY = "genex:player";
|
|
237
|
+
var SDK_VERSION = "0.14.0";
|
|
5
238
|
var config = null;
|
|
6
239
|
var state = "pending";
|
|
7
240
|
var user = null;
|
|
8
241
|
var localTestMode = false;
|
|
242
|
+
var nativeEntry = null;
|
|
243
|
+
var nativeReauthPending = false;
|
|
244
|
+
var nativeReauthTimer;
|
|
245
|
+
var heartbeatTimer;
|
|
246
|
+
var nativeBootAt = 0;
|
|
9
247
|
var embedToken;
|
|
10
248
|
var colyseusUrls;
|
|
11
249
|
var parentOrigin = null;
|
|
@@ -17,6 +255,8 @@ var handshakeTimeoutMs = 1e4;
|
|
|
17
255
|
var refreshDelayMs = 10 * 6e4;
|
|
18
256
|
var refreshRetryMs = 6e4;
|
|
19
257
|
var overlayTextDelayMs = 500;
|
|
258
|
+
var nativeReauthTimeoutMs = 3e4;
|
|
259
|
+
var heartbeatIntervalMs = 3e4;
|
|
20
260
|
var handshakeTimer;
|
|
21
261
|
var refreshTimer;
|
|
22
262
|
var messageHandler;
|
|
@@ -57,6 +297,19 @@ function clearRetryFlag() {
|
|
|
57
297
|
} catch {
|
|
58
298
|
}
|
|
59
299
|
}
|
|
300
|
+
function readLocalAuthFlag() {
|
|
301
|
+
try {
|
|
302
|
+
return win()?.sessionStorage?.getItem(LOCAL_AUTH_FLAG) === "1";
|
|
303
|
+
} catch {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function writeLocalAuthFlag() {
|
|
308
|
+
try {
|
|
309
|
+
win()?.sessionStorage?.setItem(LOCAL_AUTH_FLAG, "1");
|
|
310
|
+
} catch {
|
|
311
|
+
}
|
|
312
|
+
}
|
|
60
313
|
function readPopoverDismissed() {
|
|
61
314
|
try {
|
|
62
315
|
return win()?.sessionStorage?.getItem(POPOVER_DISMISSED_FLAG) === "1";
|
|
@@ -70,6 +323,20 @@ function writePopoverDismissed() {
|
|
|
70
323
|
} catch {
|
|
71
324
|
}
|
|
72
325
|
}
|
|
326
|
+
function playerId() {
|
|
327
|
+
try {
|
|
328
|
+
const store = win()?.localStorage;
|
|
329
|
+
if (!store) return "no-storage";
|
|
330
|
+
let id = store.getItem(PLAYER_ID_KEY);
|
|
331
|
+
if (!id) {
|
|
332
|
+
id = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
333
|
+
store.setItem(PLAYER_ID_KEY, id);
|
|
334
|
+
}
|
|
335
|
+
return id;
|
|
336
|
+
} catch {
|
|
337
|
+
return "no-storage";
|
|
338
|
+
}
|
|
339
|
+
}
|
|
73
340
|
function initEmbed(cfg) {
|
|
74
341
|
const w = win();
|
|
75
342
|
if (!w) return;
|
|
@@ -81,11 +348,16 @@ function initEmbed(cfg) {
|
|
|
81
348
|
dashboardOrigins: [...cfg.dashboardOrigins]
|
|
82
349
|
};
|
|
83
350
|
state = "pending";
|
|
84
|
-
if (
|
|
351
|
+
if (wantsLocalTestMode(w)) {
|
|
85
352
|
enterLocalTestMode(w);
|
|
86
353
|
return;
|
|
87
354
|
}
|
|
88
355
|
showOverlay("connecting");
|
|
356
|
+
const entry = detectNativeEntry(w);
|
|
357
|
+
if (entry) {
|
|
358
|
+
startNativeHandshake(w, entry);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
89
361
|
if (isEmbedded()) {
|
|
90
362
|
startEmbeddedHandshake(w);
|
|
91
363
|
} else {
|
|
@@ -329,6 +601,120 @@ async function handleParentMessage(event) {
|
|
|
329
601
|
}
|
|
330
602
|
await redeemTicket(data.ticket);
|
|
331
603
|
}
|
|
604
|
+
function nativeBridge(w) {
|
|
605
|
+
const candidate = w.ReactNativeWebView;
|
|
606
|
+
if (!candidate || typeof candidate !== "object") return null;
|
|
607
|
+
const post = candidate.postMessage;
|
|
608
|
+
return typeof post === "function" ? candidate : null;
|
|
609
|
+
}
|
|
610
|
+
function detectNativeEntry(w) {
|
|
611
|
+
if (isEmbedded()) return null;
|
|
612
|
+
let search;
|
|
613
|
+
try {
|
|
614
|
+
search = w.location.search ?? "";
|
|
615
|
+
} catch {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
if (!hasNativeMarker(search)) return null;
|
|
619
|
+
if (!nativeBridge(w)) return null;
|
|
620
|
+
return readNativeEntry(search);
|
|
621
|
+
}
|
|
622
|
+
function inNativeMode() {
|
|
623
|
+
return nativeEntry !== null;
|
|
624
|
+
}
|
|
625
|
+
function postToNative(message) {
|
|
626
|
+
const w = win();
|
|
627
|
+
if (!w || !config || !nativeEntry) return;
|
|
628
|
+
const bridge = nativeBridge(w);
|
|
629
|
+
if (!bridge) return;
|
|
630
|
+
const encoded = encodeBridgeMessage({
|
|
631
|
+
channel: NATIVE_CHANNEL,
|
|
632
|
+
v: NATIVE_PROTOCOL_VERSION,
|
|
633
|
+
slug: config.slug,
|
|
634
|
+
attemptId: nativeEntry.attemptId,
|
|
635
|
+
nonce: nativeEntry.nonce,
|
|
636
|
+
...message
|
|
637
|
+
});
|
|
638
|
+
if (!encoded) return;
|
|
639
|
+
try {
|
|
640
|
+
bridge.postMessage(encoded);
|
|
641
|
+
} catch {
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function postNativeError(code) {
|
|
645
|
+
if (inNativeMode()) postToNative({ type: "error", code });
|
|
646
|
+
}
|
|
647
|
+
function startNativeHandshake(w, entry) {
|
|
648
|
+
nativeEntry = entry;
|
|
649
|
+
nativeBootAt = Date.now();
|
|
650
|
+
w[NATIVE_RECEIVER] = (payload) => {
|
|
651
|
+
void handleNativeCommand(payload);
|
|
652
|
+
};
|
|
653
|
+
const capabilities = ["native-ticket", "native-guest", "heartbeat"];
|
|
654
|
+
postToNative({ type: "ready", sdkVersion: SDK_VERSION, capabilities });
|
|
655
|
+
handshakeTimer = setTimeout(() => {
|
|
656
|
+
void requestGuestSession();
|
|
657
|
+
}, handshakeTimeoutMs);
|
|
658
|
+
}
|
|
659
|
+
async function handleNativeCommand(payload) {
|
|
660
|
+
if (!config || !nativeEntry) return;
|
|
661
|
+
const read2 = readBridgeNativeCommand(payload);
|
|
662
|
+
if (!read2.ok) return;
|
|
663
|
+
const command = read2.message;
|
|
664
|
+
if (command.slug !== config.slug || command.attemptId !== nativeEntry.attemptId || command.nonce !== nativeEntry.nonce) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
switch (command.type) {
|
|
668
|
+
case "ticket":
|
|
669
|
+
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
670
|
+
await redeemTicket(command.ticket);
|
|
671
|
+
return;
|
|
672
|
+
case "guest":
|
|
673
|
+
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
674
|
+
await requestGuestSession();
|
|
675
|
+
return;
|
|
676
|
+
case "cancel":
|
|
677
|
+
stopNativeTimers();
|
|
678
|
+
return;
|
|
679
|
+
case "foreground":
|
|
680
|
+
case "network":
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
function stopNativeTimers() {
|
|
685
|
+
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
686
|
+
handshakeTimer = void 0;
|
|
687
|
+
if (refreshTimer !== void 0) clearTimeout(refreshTimer);
|
|
688
|
+
refreshTimer = void 0;
|
|
689
|
+
if (nativeReauthTimer !== void 0) clearTimeout(nativeReauthTimer);
|
|
690
|
+
nativeReauthTimer = void 0;
|
|
691
|
+
if (heartbeatTimer !== void 0) clearInterval(heartbeatTimer);
|
|
692
|
+
heartbeatTimer = void 0;
|
|
693
|
+
}
|
|
694
|
+
function startHeartbeat() {
|
|
695
|
+
if (!inNativeMode() || heartbeatTimer !== void 0) return;
|
|
696
|
+
heartbeatTimer = setInterval(() => {
|
|
697
|
+
postToNative({ type: "heartbeat", uptimeMs: Math.max(0, Date.now() - nativeBootAt) });
|
|
698
|
+
}, heartbeatIntervalMs);
|
|
699
|
+
heartbeatTimer.unref?.();
|
|
700
|
+
}
|
|
701
|
+
function requestNativeReauth() {
|
|
702
|
+
if (!inNativeMode() || nativeReauthPending) return;
|
|
703
|
+
nativeReauthPending = true;
|
|
704
|
+
postToNative({ type: "reauth-required" });
|
|
705
|
+
nativeReauthTimer = setTimeout(() => {
|
|
706
|
+
nativeReauthTimer = void 0;
|
|
707
|
+
if (!nativeReauthPending) return;
|
|
708
|
+
nativeReauthPending = false;
|
|
709
|
+
emit("error", { error: new Error("embed session expired") });
|
|
710
|
+
enterBlocked("session-expired");
|
|
711
|
+
}, nativeReauthTimeoutMs);
|
|
712
|
+
}
|
|
713
|
+
function clearNativeReauth() {
|
|
714
|
+
nativeReauthPending = false;
|
|
715
|
+
if (nativeReauthTimer !== void 0) clearTimeout(nativeReauthTimer);
|
|
716
|
+
nativeReauthTimer = void 0;
|
|
717
|
+
}
|
|
332
718
|
function postToParent(message, targetOrigin) {
|
|
333
719
|
const w = win();
|
|
334
720
|
if (!w || !isEmbedded()) return;
|
|
@@ -396,6 +782,7 @@ function readFragment(w) {
|
|
|
396
782
|
}
|
|
397
783
|
async function redirectToAuthorize(w, opts) {
|
|
398
784
|
if (!config || localTestMode) return;
|
|
785
|
+
if (inNativeMode()) return;
|
|
399
786
|
const origin = opts?.guestOk ? config.dashboardOrigins[0] ?? (await fetchDashboardOrigins())[0] : (await fetchDashboardOrigins())[0] ?? config.dashboardOrigins[0];
|
|
400
787
|
if (!origin) {
|
|
401
788
|
enterBlocked();
|
|
@@ -420,7 +807,9 @@ async function fetchDashboardOrigins() {
|
|
|
420
807
|
return config.dashboardOrigins;
|
|
421
808
|
}
|
|
422
809
|
async function redeemTicket(ticket) {
|
|
423
|
-
if (!config || localTestMode || redeeming
|
|
810
|
+
if (!config || localTestMode || redeeming) return;
|
|
811
|
+
const reauthing = nativeReauthPending && state === "authenticated";
|
|
812
|
+
if (state !== "pending" && state !== "guest" && !reauthing) return;
|
|
424
813
|
const upgradingFromGuest = state === "guest";
|
|
425
814
|
const epoch = ++identityEpoch;
|
|
426
815
|
redeeming = true;
|
|
@@ -449,6 +838,11 @@ async function redeemTicket(ticket) {
|
|
|
449
838
|
if (isEmbedded() && parentOrigin) {
|
|
450
839
|
postToParent({ type: "genex:embed:authenticated", v: PROTOCOL_VERSION }, parentOrigin);
|
|
451
840
|
}
|
|
841
|
+
if (inNativeMode()) {
|
|
842
|
+
clearNativeReauth();
|
|
843
|
+
postToNative({ type: "authenticated", guest: false });
|
|
844
|
+
startHeartbeat();
|
|
845
|
+
}
|
|
452
846
|
if (upgradingFromGuest) await flushGuestQueue();
|
|
453
847
|
const ctx = { user };
|
|
454
848
|
for (const waiter of authWaiters.splice(0)) waiter.resolve(ctx);
|
|
@@ -471,12 +865,16 @@ async function requestGuestSession() {
|
|
|
471
865
|
const res = await doFetch(`${config.apiUrl}/api/embed/guest-session`, {
|
|
472
866
|
method: "POST",
|
|
473
867
|
headers: { "Content-Type": "application/json" },
|
|
474
|
-
|
|
868
|
+
// playerId is analytics-only: the SERVER still mints the session's own
|
|
869
|
+
// guest id and the token is unchanged — this just lets the play event
|
|
870
|
+
// key on something that survives the tab.
|
|
871
|
+
body: JSON.stringify({ slug: config.slug, playerId: playerId() })
|
|
475
872
|
});
|
|
476
873
|
if (epoch !== identityEpoch) return;
|
|
477
874
|
if (!res.ok) {
|
|
478
875
|
emit("error", { error: new Error(`guest session failed (${res.status})`) });
|
|
479
|
-
|
|
876
|
+
postNativeError("guest-failed");
|
|
877
|
+
enterBlocked(res.status === 404 ? "not-available" : "no-identity");
|
|
480
878
|
return;
|
|
481
879
|
}
|
|
482
880
|
const body = await res.json();
|
|
@@ -504,6 +902,9 @@ function enterGuest() {
|
|
|
504
902
|
{ type: "genex:embed:authenticated", v: PROTOCOL_VERSION, guest: true },
|
|
505
903
|
parentOrigin ?? "*"
|
|
506
904
|
);
|
|
905
|
+
} else if (inNativeMode()) {
|
|
906
|
+
postToNative({ type: "authenticated", guest: true });
|
|
907
|
+
startHeartbeat();
|
|
507
908
|
} else if (firstEntry && !readPopoverDismissed()) {
|
|
508
909
|
showGuestPopover();
|
|
509
910
|
}
|
|
@@ -521,14 +922,26 @@ function isLoopbackLocation(w) {
|
|
|
521
922
|
return false;
|
|
522
923
|
}
|
|
523
924
|
}
|
|
524
|
-
function
|
|
925
|
+
function wantsLocalTestMode(w) {
|
|
525
926
|
if (isEmbedded()) return false;
|
|
927
|
+
if (detectNativeEntry(w)) return false;
|
|
526
928
|
if (!isLoopbackLocation(w)) return false;
|
|
929
|
+
let params = null;
|
|
527
930
|
try {
|
|
528
|
-
|
|
931
|
+
params = new URLSearchParams(w.location.search);
|
|
529
932
|
} catch {
|
|
933
|
+
}
|
|
934
|
+
if (params?.get("genex_local_test") === "1") return true;
|
|
935
|
+
if (params?.get("genex_auth") === "1") {
|
|
936
|
+
writeLocalAuthFlag();
|
|
530
937
|
return false;
|
|
531
938
|
}
|
|
939
|
+
if (readLocalAuthFlag()) return false;
|
|
940
|
+
return !hasGenexFragment(w);
|
|
941
|
+
}
|
|
942
|
+
function hasGenexFragment(w) {
|
|
943
|
+
const hash = stashedTicketHash ?? w.location.hash;
|
|
944
|
+
return !!hash && hash.includes("genex_");
|
|
532
945
|
}
|
|
533
946
|
function enterLocalTestMode(w) {
|
|
534
947
|
localTestMode = true;
|
|
@@ -548,6 +961,12 @@ function enterLocalTestMode(w) {
|
|
|
548
961
|
}
|
|
549
962
|
async function handleRedeemFailure() {
|
|
550
963
|
const w = win();
|
|
964
|
+
if (inNativeMode()) {
|
|
965
|
+
clearNativeReauth();
|
|
966
|
+
postNativeError("ticket-rejected");
|
|
967
|
+
enterBlocked();
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
551
970
|
if (w && !isEmbedded() && !readRetryFlag()) {
|
|
552
971
|
writeRetryFlag();
|
|
553
972
|
showOverlay("redirecting");
|
|
@@ -593,8 +1012,12 @@ async function refreshToken() {
|
|
|
593
1012
|
void requestGuestSession();
|
|
594
1013
|
return;
|
|
595
1014
|
}
|
|
1015
|
+
if (inNativeMode()) {
|
|
1016
|
+
requestNativeReauth();
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
596
1019
|
emit("error", { error: new Error("embed session expired") });
|
|
597
|
-
enterBlocked();
|
|
1020
|
+
enterBlocked("session-expired");
|
|
598
1021
|
return;
|
|
599
1022
|
}
|
|
600
1023
|
if (refreshTimer !== void 0) clearTimeout(refreshTimer);
|
|
@@ -602,7 +1025,7 @@ async function refreshToken() {
|
|
|
602
1025
|
void refreshToken();
|
|
603
1026
|
}, refreshRetryMs);
|
|
604
1027
|
}
|
|
605
|
-
function enterBlocked() {
|
|
1028
|
+
function enterBlocked(reason = "no-identity") {
|
|
606
1029
|
if (state === "blocked") return;
|
|
607
1030
|
identityEpoch++;
|
|
608
1031
|
state = "blocked";
|
|
@@ -611,7 +1034,14 @@ function enterBlocked() {
|
|
|
611
1034
|
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
612
1035
|
if (refreshTimer !== void 0) clearTimeout(refreshTimer);
|
|
613
1036
|
removeGuestPopover();
|
|
614
|
-
|
|
1037
|
+
if (inNativeMode()) {
|
|
1038
|
+
stopNativeTimers();
|
|
1039
|
+
clearNativeReauth();
|
|
1040
|
+
removeOverlay();
|
|
1041
|
+
postToNative({ type: "blocked", reason });
|
|
1042
|
+
} else {
|
|
1043
|
+
showOverlay(isEmbedded() ? "blocked-embedded" : "blocked-standalone");
|
|
1044
|
+
}
|
|
615
1045
|
if (isEmbedded()) {
|
|
616
1046
|
postToParent({ type: "genex:embed:blocked", v: PROTOCOL_VERSION }, parentOrigin ?? "*");
|
|
617
1047
|
}
|
|
@@ -926,6 +1356,11 @@ function __resetForTests(overrides) {
|
|
|
926
1356
|
if (refreshTimer !== void 0) clearTimeout(refreshTimer);
|
|
927
1357
|
handshakeTimer = void 0;
|
|
928
1358
|
refreshTimer = void 0;
|
|
1359
|
+
stopNativeTimers();
|
|
1360
|
+
if (w) delete w[NATIVE_RECEIVER];
|
|
1361
|
+
nativeEntry = null;
|
|
1362
|
+
nativeReauthPending = false;
|
|
1363
|
+
nativeBootAt = 0;
|
|
929
1364
|
removeOverlay(true);
|
|
930
1365
|
removeGuestPopover();
|
|
931
1366
|
overlayFontRequested = false;
|
|
@@ -951,6 +1386,8 @@ function __resetForTests(overrides) {
|
|
|
951
1386
|
refreshDelayMs = overrides?.refreshDelayMs ?? 10 * 6e4;
|
|
952
1387
|
refreshRetryMs = overrides?.refreshRetryMs ?? 6e4;
|
|
953
1388
|
overlayTextDelayMs = overrides?.overlayTextDelayMs ?? 500;
|
|
1389
|
+
nativeReauthTimeoutMs = overrides?.nativeReauthTimeoutMs ?? 3e4;
|
|
1390
|
+
heartbeatIntervalMs = overrides?.heartbeatIntervalMs ?? 3e4;
|
|
954
1391
|
}
|
|
955
1392
|
|
|
956
1393
|
export {
|
package/dist/index.d.ts
CHANGED
|
@@ -228,6 +228,8 @@ declare function __resetForTests(overrides?: {
|
|
|
228
228
|
refreshDelayMs?: number;
|
|
229
229
|
refreshRetryMs?: number;
|
|
230
230
|
overlayTextDelayMs?: number;
|
|
231
|
+
nativeReauthTimeoutMs?: number;
|
|
232
|
+
heartbeatIntervalMs?: number;
|
|
231
233
|
}): void;
|
|
232
234
|
|
|
233
235
|
export { type AuthState, type EmbedConfig, type EmbedEvent, type EmbedUser, type Leaderboard, type LeaderboardEntry, type PlayerStateResult, type SaveStateResult, type SubmitScoreResult, type WorldStateResult, __resetForTests, _stashTicketFromUrl, getAuthState, getColyseusAuth, getColyseusUrls, getEmbedToken, getLeaderboard, getUser, initEmbed, isEmbedded, loadPlayerState, loadWorldState, on, savePlayerState, saveWorldState, submitScore, waitForAuth, waitForPlayer };
|
package/dist/index.js
CHANGED
package/dist/sentry.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/embed-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Player identity + durable game state for genex games \u2014 signed-in or guest play, per-player save slots, shared world state, and soft-trust leaderboards.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -40,9 +40,11 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@arethetypeswrong/cli": "^0.18.0",
|
|
43
|
+
"@genex/embed-protocol": "workspace:*",
|
|
43
44
|
"@sentry/browser": "^10.63.0",
|
|
44
45
|
"publint": "^0.3.0",
|
|
45
|
-
"tsup": "^8.0.0"
|
|
46
|
+
"tsup": "^8.0.0",
|
|
47
|
+
"typescript": "^5.8.0"
|
|
46
48
|
},
|
|
47
49
|
"repository": {
|
|
48
50
|
"type": "git",
|