@genex-ai/embed-sdk 0.13.0 → 0.15.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-XGTGIWVQ.js → chunk-DWDBRKRS.js} +550 -12
- package/dist/index.d.ts +110 -1
- package/dist/index.js +9 -1
- package/dist/sentry.js +1 -1
- package/package.json +4 -2
|
@@ -1,12 +1,356 @@
|
|
|
1
|
+
// src/commerce.ts
|
|
2
|
+
var cfg = null;
|
|
3
|
+
function _initCommerce(c) {
|
|
4
|
+
cfg = c;
|
|
5
|
+
}
|
|
6
|
+
function must() {
|
|
7
|
+
if (!cfg) throw new Error("genex commerce: call initEmbed() first");
|
|
8
|
+
return cfg;
|
|
9
|
+
}
|
|
10
|
+
function authHeaders() {
|
|
11
|
+
const token = must().getToken();
|
|
12
|
+
if (!token) throw new Error("genex commerce: no player session");
|
|
13
|
+
return { Authorization: `Bearer ${token}` };
|
|
14
|
+
}
|
|
15
|
+
async function getShop() {
|
|
16
|
+
const c = must();
|
|
17
|
+
if (c.isLocalTest()) return [];
|
|
18
|
+
const res = await fetch(`${c.apiUrl}/api/coin/catalog`, { headers: authHeaders() });
|
|
19
|
+
if (!res.ok) throw new Error(`genex shop failed (${res.status})`);
|
|
20
|
+
return (await res.json()).items;
|
|
21
|
+
}
|
|
22
|
+
async function getEntitlements(opts) {
|
|
23
|
+
const c = must();
|
|
24
|
+
if (c.isLocalTest()) return [];
|
|
25
|
+
const qs = opts?.excludeConsumed ? "?excludeConsumed=true" : "";
|
|
26
|
+
const res = await fetch(`${c.apiUrl}/api/coin/entitlements${qs}`, { headers: authHeaders() });
|
|
27
|
+
if (!res.ok) throw new Error(`genex entitlements failed (${res.status})`);
|
|
28
|
+
return (await res.json()).items;
|
|
29
|
+
}
|
|
30
|
+
async function consumeEntitlement(entitlementId) {
|
|
31
|
+
const c = must();
|
|
32
|
+
if (c.isLocalTest()) return { consumed: true, alreadyConsumed: false };
|
|
33
|
+
const res = await fetch(
|
|
34
|
+
`${c.apiUrl}/api/coin/entitlements/${encodeURIComponent(entitlementId)}/consume`,
|
|
35
|
+
{ method: "POST", headers: authHeaders() }
|
|
36
|
+
);
|
|
37
|
+
if (!res.ok) throw new Error(`genex consume failed (${res.status})`);
|
|
38
|
+
return await res.json();
|
|
39
|
+
}
|
|
40
|
+
async function awaitOutcome(intentId, timeoutMs) {
|
|
41
|
+
const c = must();
|
|
42
|
+
const deadline = Date.now() + timeoutMs;
|
|
43
|
+
let delay = 400;
|
|
44
|
+
while (Date.now() < deadline) {
|
|
45
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
46
|
+
delay = Math.min(delay * 1.4, 2e3);
|
|
47
|
+
const res = await fetch(`${c.apiUrl}/api/coin/intents/${encodeURIComponent(intentId)}`, {
|
|
48
|
+
headers: authHeaders()
|
|
49
|
+
}).catch(() => null);
|
|
50
|
+
if (!res || !res.ok) continue;
|
|
51
|
+
const view = await res.json();
|
|
52
|
+
switch (view.status) {
|
|
53
|
+
case "succeeded":
|
|
54
|
+
case "consumed":
|
|
55
|
+
return { status: "succeeded" };
|
|
56
|
+
case "canceled":
|
|
57
|
+
return { status: "canceled" };
|
|
58
|
+
case "expired":
|
|
59
|
+
return { status: "expired" };
|
|
60
|
+
case "failed":
|
|
61
|
+
return { status: "failed", message: "the purchase could not be completed" };
|
|
62
|
+
default:
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { status: "expired" };
|
|
67
|
+
}
|
|
68
|
+
function openConfirmSurface(view) {
|
|
69
|
+
const c = must();
|
|
70
|
+
const w = typeof window === "undefined" ? null : window;
|
|
71
|
+
if (!w) return { ok: false, reason: "no_window" };
|
|
72
|
+
if (c.isNative()) return { ok: false, reason: "native_unsupported" };
|
|
73
|
+
if (c.isEmbedded() && w.parent) {
|
|
74
|
+
for (const origin of c.dashboardOrigins) {
|
|
75
|
+
try {
|
|
76
|
+
w.parent.postMessage({ type: "genex:commerce:confirm", v: 1, intentId: view.id }, origin);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { ok: true };
|
|
81
|
+
}
|
|
82
|
+
const popup = w.open(view.confirmUrl, `genex_confirm_${view.id}`, "popup,width=420,height=640");
|
|
83
|
+
return popup === null ? { ok: false, reason: "popup_blocked" } : { ok: true };
|
|
84
|
+
}
|
|
85
|
+
async function buy(opts) {
|
|
86
|
+
const c = must();
|
|
87
|
+
if (c.isLocalTest()) {
|
|
88
|
+
return { status: "failed", message: "purchases are unavailable in local test mode" };
|
|
89
|
+
}
|
|
90
|
+
const created = await fetch(`${c.apiUrl}/api/coin/intents`, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: { ...authHeaders(), "Content-Type": "application/json" },
|
|
93
|
+
body: JSON.stringify({ skuId: opts.skuId, quantity: opts.quantity ?? 1 })
|
|
94
|
+
});
|
|
95
|
+
if (!created.ok) {
|
|
96
|
+
const body = await created.json().catch(() => ({}));
|
|
97
|
+
return { status: "failed", message: body.error ?? `intent failed (${created.status})` };
|
|
98
|
+
}
|
|
99
|
+
const view = await created.json();
|
|
100
|
+
const opened = openConfirmSurface(view);
|
|
101
|
+
if (!opened.ok) {
|
|
102
|
+
const message = opened.reason === "popup_blocked" ? "the confirmation window was blocked \u2014 tap again to confirm" : opened.reason === "native_unsupported" ? "purchases are not available in the app yet" : "the confirmation window could not be opened";
|
|
103
|
+
return { status: "failed", message };
|
|
104
|
+
}
|
|
105
|
+
return awaitOutcome(view.id, opts.timeoutMs ?? 18e4);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ../embed-protocol/src/constants.ts
|
|
109
|
+
var NATIVE_CHANNEL = "genex-native";
|
|
110
|
+
var NATIVE_PROTOCOL_VERSION = 1;
|
|
111
|
+
var NATIVE_MARKER_PARAM = "genex_native";
|
|
112
|
+
var NATIVE_MARKER_VALUE = "1";
|
|
113
|
+
var NATIVE_PROTOCOL_PARAM = "genex_protocol";
|
|
114
|
+
var NATIVE_ATTEMPT_PARAM = "genex_attempt";
|
|
115
|
+
var NATIVE_NONCE_PARAM = "genex_nonce";
|
|
116
|
+
var NATIVE_RECEIVER = "__genexNative";
|
|
117
|
+
var MAX_BRIDGE_MESSAGE_BYTES = 4096;
|
|
118
|
+
var BRIDGE_ID_RE = /^[0-9a-f]{32}$/;
|
|
119
|
+
var SLUG_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
120
|
+
function isBridgeId(value) {
|
|
121
|
+
return typeof value === "string" && BRIDGE_ID_RE.test(value);
|
|
122
|
+
}
|
|
123
|
+
function isBridgeSlug(value) {
|
|
124
|
+
return typeof value === "string" && SLUG_RE.test(value);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ../embed-protocol/src/base64url.ts
|
|
128
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
129
|
+
var LOOKUP = (() => {
|
|
130
|
+
const table = new Array(128).fill(-1);
|
|
131
|
+
for (let i = 0; i < ALPHABET.length; i++) table[ALPHABET.charCodeAt(i)] = i;
|
|
132
|
+
return table;
|
|
133
|
+
})();
|
|
134
|
+
function utf8Bytes(text) {
|
|
135
|
+
const out = [];
|
|
136
|
+
for (let i = 0; i < text.length; i++) {
|
|
137
|
+
let code = text.charCodeAt(i);
|
|
138
|
+
if (code >= 55296 && code <= 56319) {
|
|
139
|
+
const next = i + 1 < text.length ? text.charCodeAt(i + 1) : 0;
|
|
140
|
+
if (next >= 56320 && next <= 57343) {
|
|
141
|
+
code = 65536 + (code - 55296 << 10) + (next - 56320);
|
|
142
|
+
i++;
|
|
143
|
+
} else {
|
|
144
|
+
code = 65533;
|
|
145
|
+
}
|
|
146
|
+
} else if (code >= 56320 && code <= 57343) {
|
|
147
|
+
code = 65533;
|
|
148
|
+
}
|
|
149
|
+
if (code < 128) out.push(code);
|
|
150
|
+
else if (code < 2048) out.push(192 | code >> 6, 128 | code & 63);
|
|
151
|
+
else if (code < 65536) {
|
|
152
|
+
out.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
|
|
153
|
+
} else {
|
|
154
|
+
out.push(
|
|
155
|
+
240 | code >> 18,
|
|
156
|
+
128 | code >> 12 & 63,
|
|
157
|
+
128 | code >> 6 & 63,
|
|
158
|
+
128 | code & 63
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
function utf8Text(bytes) {
|
|
165
|
+
let out = "";
|
|
166
|
+
for (let i = 0; i < bytes.length; ) {
|
|
167
|
+
const b0 = bytes[i];
|
|
168
|
+
let code;
|
|
169
|
+
let size;
|
|
170
|
+
if (b0 < 128) {
|
|
171
|
+
code = b0;
|
|
172
|
+
size = 1;
|
|
173
|
+
} else if ((b0 & 224) === 192) {
|
|
174
|
+
code = b0 & 31;
|
|
175
|
+
size = 2;
|
|
176
|
+
} else if ((b0 & 240) === 224) {
|
|
177
|
+
code = b0 & 15;
|
|
178
|
+
size = 3;
|
|
179
|
+
} else if ((b0 & 248) === 240) {
|
|
180
|
+
code = b0 & 7;
|
|
181
|
+
size = 4;
|
|
182
|
+
} else return null;
|
|
183
|
+
if (i + size > bytes.length) return null;
|
|
184
|
+
for (let k = 1; k < size; k++) {
|
|
185
|
+
const b = bytes[i + k];
|
|
186
|
+
if ((b & 192) !== 128) return null;
|
|
187
|
+
code = code << 6 | b & 63;
|
|
188
|
+
}
|
|
189
|
+
if (size === 2 && code < 128) return null;
|
|
190
|
+
if (size === 3 && (code < 2048 || code >= 55296 && code <= 57343)) return null;
|
|
191
|
+
if (size === 4 && (code < 65536 || code > 1114111)) return null;
|
|
192
|
+
if (code > 65535) {
|
|
193
|
+
const v = code - 65536;
|
|
194
|
+
out += String.fromCharCode(55296 + (v >> 10), 56320 + (v & 1023));
|
|
195
|
+
} else {
|
|
196
|
+
out += String.fromCharCode(code);
|
|
197
|
+
}
|
|
198
|
+
i += size;
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
function utf8ByteLength(text) {
|
|
203
|
+
return utf8Bytes(text).length;
|
|
204
|
+
}
|
|
205
|
+
function encodeBase64UrlJson(value) {
|
|
206
|
+
const bytes = utf8Bytes(JSON.stringify(value));
|
|
207
|
+
let out = "";
|
|
208
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
209
|
+
const b0 = bytes[i];
|
|
210
|
+
const b1 = bytes[i + 1];
|
|
211
|
+
const b2 = bytes[i + 2];
|
|
212
|
+
out += ALPHABET[b0 >> 2];
|
|
213
|
+
out += ALPHABET[(b0 & 3) << 4 | (b1 ?? 0) >> 4];
|
|
214
|
+
if (b1 === void 0) break;
|
|
215
|
+
out += ALPHABET[(b1 & 15) << 2 | (b2 ?? 0) >> 6];
|
|
216
|
+
if (b2 === void 0) break;
|
|
217
|
+
out += ALPHABET[b2 & 63];
|
|
218
|
+
}
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
function decodeBase64UrlJson(encoded) {
|
|
222
|
+
if (encoded.length % 4 === 1) return void 0;
|
|
223
|
+
const bytes = [];
|
|
224
|
+
let buffer = 0;
|
|
225
|
+
let bits = 0;
|
|
226
|
+
for (let i = 0; i < encoded.length; i++) {
|
|
227
|
+
const code = encoded.charCodeAt(i);
|
|
228
|
+
const value = code < 128 ? LOOKUP[code] : -1;
|
|
229
|
+
if (value < 0) return void 0;
|
|
230
|
+
buffer = buffer << 6 | value;
|
|
231
|
+
bits += 6;
|
|
232
|
+
if (bits >= 8) {
|
|
233
|
+
bits -= 8;
|
|
234
|
+
bytes.push(buffer >> bits & 255);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (bits > 0 && (buffer & (1 << bits) - 1) !== 0) return void 0;
|
|
238
|
+
const text = utf8Text(bytes);
|
|
239
|
+
if (text === null) return void 0;
|
|
240
|
+
try {
|
|
241
|
+
return JSON.parse(text);
|
|
242
|
+
} catch {
|
|
243
|
+
return void 0;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ../embed-protocol/src/messages.ts
|
|
248
|
+
var MAX_TICKET_LENGTH = 2048;
|
|
249
|
+
var MAX_EXPIRES_AT_LENGTH = 32;
|
|
250
|
+
var ISO_INSTANT_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
|
|
251
|
+
function isRecord(value) {
|
|
252
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
253
|
+
}
|
|
254
|
+
function hasExactKeys(value, keys) {
|
|
255
|
+
const own = Object.keys(value);
|
|
256
|
+
if (own.length !== keys.length) return false;
|
|
257
|
+
for (const key of keys) {
|
|
258
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) return false;
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
var BASE_KEYS = ["channel", "v", "type", "slug", "attemptId", "nonce"];
|
|
263
|
+
function envelopeBaseIsValid(value) {
|
|
264
|
+
return value.channel === NATIVE_CHANNEL && value.v === NATIVE_PROTOCOL_VERSION && isBridgeSlug(value.slug) && isBridgeId(value.attemptId) && isBridgeId(value.nonce);
|
|
265
|
+
}
|
|
266
|
+
function parseBridgeNativeCommand(value) {
|
|
267
|
+
if (!isRecord(value) || !envelopeBaseIsValid(value)) return void 0;
|
|
268
|
+
switch (value.type) {
|
|
269
|
+
case "ticket":
|
|
270
|
+
if (!hasExactKeys(value, [...BASE_KEYS, "ticket", "expiresAt"])) return void 0;
|
|
271
|
+
if (typeof value.ticket !== "string" || value.ticket.length === 0 || value.ticket.length > MAX_TICKET_LENGTH) {
|
|
272
|
+
return void 0;
|
|
273
|
+
}
|
|
274
|
+
if (typeof value.expiresAt !== "string" || value.expiresAt.length > MAX_EXPIRES_AT_LENGTH || !ISO_INSTANT_RE.test(value.expiresAt)) {
|
|
275
|
+
return void 0;
|
|
276
|
+
}
|
|
277
|
+
return value;
|
|
278
|
+
case "guest":
|
|
279
|
+
case "cancel":
|
|
280
|
+
if (!hasExactKeys(value, BASE_KEYS)) return void 0;
|
|
281
|
+
return value;
|
|
282
|
+
case "foreground":
|
|
283
|
+
if (!hasExactKeys(value, [...BASE_KEYS, "active"])) return void 0;
|
|
284
|
+
if (typeof value.active !== "boolean") return void 0;
|
|
285
|
+
return value;
|
|
286
|
+
case "network":
|
|
287
|
+
if (!hasExactKeys(value, [...BASE_KEYS, "online"])) return void 0;
|
|
288
|
+
if (typeof value.online !== "boolean") return void 0;
|
|
289
|
+
return value;
|
|
290
|
+
default:
|
|
291
|
+
return void 0;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ../embed-protocol/src/transport.ts
|
|
296
|
+
function read(raw, parse) {
|
|
297
|
+
if (typeof raw !== "string" || raw.length === 0) return { ok: false, reason: "undecodable" };
|
|
298
|
+
if (utf8ByteLength(raw) > MAX_BRIDGE_MESSAGE_BYTES) return { ok: false, reason: "too-large" };
|
|
299
|
+
const decoded = decodeBase64UrlJson(raw);
|
|
300
|
+
if (decoded === void 0) return { ok: false, reason: "undecodable" };
|
|
301
|
+
const message = parse(decoded);
|
|
302
|
+
if (message === void 0) return { ok: false, reason: "invalid" };
|
|
303
|
+
return { ok: true, message };
|
|
304
|
+
}
|
|
305
|
+
function readBridgeNativeCommand(raw) {
|
|
306
|
+
return read(raw, parseBridgeNativeCommand);
|
|
307
|
+
}
|
|
308
|
+
function encodeBridgeMessage(message) {
|
|
309
|
+
const encoded = encodeBase64UrlJson(message);
|
|
310
|
+
if (encoded.length > MAX_BRIDGE_MESSAGE_BYTES) return void 0;
|
|
311
|
+
return encoded;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ../embed-protocol/src/entry-url.ts
|
|
315
|
+
function readParam(search, key) {
|
|
316
|
+
const query = search.charAt(0) === "?" ? search.slice(1) : search;
|
|
317
|
+
if (query.length === 0) return null;
|
|
318
|
+
for (const pair of query.split("&")) {
|
|
319
|
+
const eq = pair.indexOf("=");
|
|
320
|
+
if (eq === -1) continue;
|
|
321
|
+
if (pair.slice(0, eq) === key) return pair.slice(eq + 1);
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
function hasNativeMarker(search) {
|
|
326
|
+
return readParam(search, NATIVE_MARKER_PARAM) === NATIVE_MARKER_VALUE;
|
|
327
|
+
}
|
|
328
|
+
function readNativeEntry(search) {
|
|
329
|
+
if (!hasNativeMarker(search)) return null;
|
|
330
|
+
const attemptId = readParam(search, NATIVE_ATTEMPT_PARAM);
|
|
331
|
+
const nonce = readParam(search, NATIVE_NONCE_PARAM);
|
|
332
|
+
const protocol = Number(readParam(search, NATIVE_PROTOCOL_PARAM));
|
|
333
|
+
if (!isBridgeId(attemptId) || !isBridgeId(nonce)) return null;
|
|
334
|
+
if (protocol !== NATIVE_PROTOCOL_VERSION) return null;
|
|
335
|
+
return { attemptId, nonce, protocol };
|
|
336
|
+
}
|
|
337
|
+
|
|
1
338
|
// src/index.ts
|
|
2
339
|
var PROTOCOL_VERSION = 1;
|
|
3
340
|
var RETRY_FLAG = "genex:embed:retry";
|
|
4
341
|
var POPOVER_DISMISSED_FLAG = "genex:guest:popover-dismissed";
|
|
342
|
+
var LOCAL_AUTH_FLAG = "genex:embed:local-auth";
|
|
5
343
|
var PLAYER_ID_KEY = "genex:player";
|
|
344
|
+
var SDK_VERSION = "0.15.0";
|
|
6
345
|
var config = null;
|
|
7
346
|
var state = "pending";
|
|
8
347
|
var user = null;
|
|
9
348
|
var localTestMode = false;
|
|
349
|
+
var nativeEntry = null;
|
|
350
|
+
var nativeReauthPending = false;
|
|
351
|
+
var nativeReauthTimer;
|
|
352
|
+
var heartbeatTimer;
|
|
353
|
+
var nativeBootAt = 0;
|
|
10
354
|
var embedToken;
|
|
11
355
|
var colyseusUrls;
|
|
12
356
|
var parentOrigin = null;
|
|
@@ -18,6 +362,8 @@ var handshakeTimeoutMs = 1e4;
|
|
|
18
362
|
var refreshDelayMs = 10 * 6e4;
|
|
19
363
|
var refreshRetryMs = 6e4;
|
|
20
364
|
var overlayTextDelayMs = 500;
|
|
365
|
+
var nativeReauthTimeoutMs = 3e4;
|
|
366
|
+
var heartbeatIntervalMs = 3e4;
|
|
21
367
|
var handshakeTimer;
|
|
22
368
|
var refreshTimer;
|
|
23
369
|
var messageHandler;
|
|
@@ -58,6 +404,19 @@ function clearRetryFlag() {
|
|
|
58
404
|
} catch {
|
|
59
405
|
}
|
|
60
406
|
}
|
|
407
|
+
function readLocalAuthFlag() {
|
|
408
|
+
try {
|
|
409
|
+
return win()?.sessionStorage?.getItem(LOCAL_AUTH_FLAG) === "1";
|
|
410
|
+
} catch {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function writeLocalAuthFlag() {
|
|
415
|
+
try {
|
|
416
|
+
win()?.sessionStorage?.setItem(LOCAL_AUTH_FLAG, "1");
|
|
417
|
+
} catch {
|
|
418
|
+
}
|
|
419
|
+
}
|
|
61
420
|
function readPopoverDismissed() {
|
|
62
421
|
try {
|
|
63
422
|
return win()?.sessionStorage?.getItem(POPOVER_DISMISSED_FLAG) === "1";
|
|
@@ -85,22 +444,35 @@ function playerId() {
|
|
|
85
444
|
return "no-storage";
|
|
86
445
|
}
|
|
87
446
|
}
|
|
88
|
-
function initEmbed(
|
|
447
|
+
function initEmbed(cfg2) {
|
|
89
448
|
const w = win();
|
|
90
449
|
if (!w) return;
|
|
91
450
|
if (initialized) return;
|
|
92
451
|
initialized = true;
|
|
93
452
|
config = {
|
|
94
|
-
slug:
|
|
95
|
-
apiUrl:
|
|
96
|
-
dashboardOrigins: [...
|
|
453
|
+
slug: cfg2.slug,
|
|
454
|
+
apiUrl: cfg2.apiUrl.replace(/\/$/, ""),
|
|
455
|
+
dashboardOrigins: [...cfg2.dashboardOrigins]
|
|
97
456
|
};
|
|
98
457
|
state = "pending";
|
|
99
|
-
|
|
458
|
+
_initCommerce({
|
|
459
|
+
apiUrl: config.apiUrl,
|
|
460
|
+
dashboardOrigins: config.dashboardOrigins,
|
|
461
|
+
getToken: () => embedToken,
|
|
462
|
+
isEmbedded,
|
|
463
|
+
isNative: inNativeMode,
|
|
464
|
+
isLocalTest: () => localTestMode
|
|
465
|
+
});
|
|
466
|
+
if (wantsLocalTestMode(w)) {
|
|
100
467
|
enterLocalTestMode(w);
|
|
101
468
|
return;
|
|
102
469
|
}
|
|
103
470
|
showOverlay("connecting");
|
|
471
|
+
const entry = detectNativeEntry(w);
|
|
472
|
+
if (entry) {
|
|
473
|
+
startNativeHandshake(w, entry);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
104
476
|
if (isEmbedded()) {
|
|
105
477
|
startEmbeddedHandshake(w);
|
|
106
478
|
} else {
|
|
@@ -344,6 +716,120 @@ async function handleParentMessage(event) {
|
|
|
344
716
|
}
|
|
345
717
|
await redeemTicket(data.ticket);
|
|
346
718
|
}
|
|
719
|
+
function nativeBridge(w) {
|
|
720
|
+
const candidate = w.ReactNativeWebView;
|
|
721
|
+
if (!candidate || typeof candidate !== "object") return null;
|
|
722
|
+
const post = candidate.postMessage;
|
|
723
|
+
return typeof post === "function" ? candidate : null;
|
|
724
|
+
}
|
|
725
|
+
function detectNativeEntry(w) {
|
|
726
|
+
if (isEmbedded()) return null;
|
|
727
|
+
let search;
|
|
728
|
+
try {
|
|
729
|
+
search = w.location.search ?? "";
|
|
730
|
+
} catch {
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
if (!hasNativeMarker(search)) return null;
|
|
734
|
+
if (!nativeBridge(w)) return null;
|
|
735
|
+
return readNativeEntry(search);
|
|
736
|
+
}
|
|
737
|
+
function inNativeMode() {
|
|
738
|
+
return nativeEntry !== null;
|
|
739
|
+
}
|
|
740
|
+
function postToNative(message) {
|
|
741
|
+
const w = win();
|
|
742
|
+
if (!w || !config || !nativeEntry) return;
|
|
743
|
+
const bridge = nativeBridge(w);
|
|
744
|
+
if (!bridge) return;
|
|
745
|
+
const encoded = encodeBridgeMessage({
|
|
746
|
+
channel: NATIVE_CHANNEL,
|
|
747
|
+
v: NATIVE_PROTOCOL_VERSION,
|
|
748
|
+
slug: config.slug,
|
|
749
|
+
attemptId: nativeEntry.attemptId,
|
|
750
|
+
nonce: nativeEntry.nonce,
|
|
751
|
+
...message
|
|
752
|
+
});
|
|
753
|
+
if (!encoded) return;
|
|
754
|
+
try {
|
|
755
|
+
bridge.postMessage(encoded);
|
|
756
|
+
} catch {
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
function postNativeError(code) {
|
|
760
|
+
if (inNativeMode()) postToNative({ type: "error", code });
|
|
761
|
+
}
|
|
762
|
+
function startNativeHandshake(w, entry) {
|
|
763
|
+
nativeEntry = entry;
|
|
764
|
+
nativeBootAt = Date.now();
|
|
765
|
+
w[NATIVE_RECEIVER] = (payload) => {
|
|
766
|
+
void handleNativeCommand(payload);
|
|
767
|
+
};
|
|
768
|
+
const capabilities = ["native-ticket", "native-guest", "heartbeat"];
|
|
769
|
+
postToNative({ type: "ready", sdkVersion: SDK_VERSION, capabilities });
|
|
770
|
+
handshakeTimer = setTimeout(() => {
|
|
771
|
+
void requestGuestSession();
|
|
772
|
+
}, handshakeTimeoutMs);
|
|
773
|
+
}
|
|
774
|
+
async function handleNativeCommand(payload) {
|
|
775
|
+
if (!config || !nativeEntry) return;
|
|
776
|
+
const read2 = readBridgeNativeCommand(payload);
|
|
777
|
+
if (!read2.ok) return;
|
|
778
|
+
const command = read2.message;
|
|
779
|
+
if (command.slug !== config.slug || command.attemptId !== nativeEntry.attemptId || command.nonce !== nativeEntry.nonce) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
switch (command.type) {
|
|
783
|
+
case "ticket":
|
|
784
|
+
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
785
|
+
await redeemTicket(command.ticket);
|
|
786
|
+
return;
|
|
787
|
+
case "guest":
|
|
788
|
+
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
789
|
+
await requestGuestSession();
|
|
790
|
+
return;
|
|
791
|
+
case "cancel":
|
|
792
|
+
stopNativeTimers();
|
|
793
|
+
return;
|
|
794
|
+
case "foreground":
|
|
795
|
+
case "network":
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function stopNativeTimers() {
|
|
800
|
+
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
801
|
+
handshakeTimer = void 0;
|
|
802
|
+
if (refreshTimer !== void 0) clearTimeout(refreshTimer);
|
|
803
|
+
refreshTimer = void 0;
|
|
804
|
+
if (nativeReauthTimer !== void 0) clearTimeout(nativeReauthTimer);
|
|
805
|
+
nativeReauthTimer = void 0;
|
|
806
|
+
if (heartbeatTimer !== void 0) clearInterval(heartbeatTimer);
|
|
807
|
+
heartbeatTimer = void 0;
|
|
808
|
+
}
|
|
809
|
+
function startHeartbeat() {
|
|
810
|
+
if (!inNativeMode() || heartbeatTimer !== void 0) return;
|
|
811
|
+
heartbeatTimer = setInterval(() => {
|
|
812
|
+
postToNative({ type: "heartbeat", uptimeMs: Math.max(0, Date.now() - nativeBootAt) });
|
|
813
|
+
}, heartbeatIntervalMs);
|
|
814
|
+
heartbeatTimer.unref?.();
|
|
815
|
+
}
|
|
816
|
+
function requestNativeReauth() {
|
|
817
|
+
if (!inNativeMode() || nativeReauthPending) return;
|
|
818
|
+
nativeReauthPending = true;
|
|
819
|
+
postToNative({ type: "reauth-required" });
|
|
820
|
+
nativeReauthTimer = setTimeout(() => {
|
|
821
|
+
nativeReauthTimer = void 0;
|
|
822
|
+
if (!nativeReauthPending) return;
|
|
823
|
+
nativeReauthPending = false;
|
|
824
|
+
emit("error", { error: new Error("embed session expired") });
|
|
825
|
+
enterBlocked("session-expired");
|
|
826
|
+
}, nativeReauthTimeoutMs);
|
|
827
|
+
}
|
|
828
|
+
function clearNativeReauth() {
|
|
829
|
+
nativeReauthPending = false;
|
|
830
|
+
if (nativeReauthTimer !== void 0) clearTimeout(nativeReauthTimer);
|
|
831
|
+
nativeReauthTimer = void 0;
|
|
832
|
+
}
|
|
347
833
|
function postToParent(message, targetOrigin) {
|
|
348
834
|
const w = win();
|
|
349
835
|
if (!w || !isEmbedded()) return;
|
|
@@ -411,6 +897,7 @@ function readFragment(w) {
|
|
|
411
897
|
}
|
|
412
898
|
async function redirectToAuthorize(w, opts) {
|
|
413
899
|
if (!config || localTestMode) return;
|
|
900
|
+
if (inNativeMode()) return;
|
|
414
901
|
const origin = opts?.guestOk ? config.dashboardOrigins[0] ?? (await fetchDashboardOrigins())[0] : (await fetchDashboardOrigins())[0] ?? config.dashboardOrigins[0];
|
|
415
902
|
if (!origin) {
|
|
416
903
|
enterBlocked();
|
|
@@ -435,7 +922,9 @@ async function fetchDashboardOrigins() {
|
|
|
435
922
|
return config.dashboardOrigins;
|
|
436
923
|
}
|
|
437
924
|
async function redeemTicket(ticket) {
|
|
438
|
-
if (!config || localTestMode || redeeming
|
|
925
|
+
if (!config || localTestMode || redeeming) return;
|
|
926
|
+
const reauthing = nativeReauthPending && state === "authenticated";
|
|
927
|
+
if (state !== "pending" && state !== "guest" && !reauthing) return;
|
|
439
928
|
const upgradingFromGuest = state === "guest";
|
|
440
929
|
const epoch = ++identityEpoch;
|
|
441
930
|
redeeming = true;
|
|
@@ -464,6 +953,11 @@ async function redeemTicket(ticket) {
|
|
|
464
953
|
if (isEmbedded() && parentOrigin) {
|
|
465
954
|
postToParent({ type: "genex:embed:authenticated", v: PROTOCOL_VERSION }, parentOrigin);
|
|
466
955
|
}
|
|
956
|
+
if (inNativeMode()) {
|
|
957
|
+
clearNativeReauth();
|
|
958
|
+
postToNative({ type: "authenticated", guest: false });
|
|
959
|
+
startHeartbeat();
|
|
960
|
+
}
|
|
467
961
|
if (upgradingFromGuest) await flushGuestQueue();
|
|
468
962
|
const ctx = { user };
|
|
469
963
|
for (const waiter of authWaiters.splice(0)) waiter.resolve(ctx);
|
|
@@ -494,7 +988,8 @@ async function requestGuestSession() {
|
|
|
494
988
|
if (epoch !== identityEpoch) return;
|
|
495
989
|
if (!res.ok) {
|
|
496
990
|
emit("error", { error: new Error(`guest session failed (${res.status})`) });
|
|
497
|
-
|
|
991
|
+
postNativeError("guest-failed");
|
|
992
|
+
enterBlocked(res.status === 404 ? "not-available" : "no-identity");
|
|
498
993
|
return;
|
|
499
994
|
}
|
|
500
995
|
const body = await res.json();
|
|
@@ -522,6 +1017,9 @@ function enterGuest() {
|
|
|
522
1017
|
{ type: "genex:embed:authenticated", v: PROTOCOL_VERSION, guest: true },
|
|
523
1018
|
parentOrigin ?? "*"
|
|
524
1019
|
);
|
|
1020
|
+
} else if (inNativeMode()) {
|
|
1021
|
+
postToNative({ type: "authenticated", guest: true });
|
|
1022
|
+
startHeartbeat();
|
|
525
1023
|
} else if (firstEntry && !readPopoverDismissed()) {
|
|
526
1024
|
showGuestPopover();
|
|
527
1025
|
}
|
|
@@ -539,14 +1037,26 @@ function isLoopbackLocation(w) {
|
|
|
539
1037
|
return false;
|
|
540
1038
|
}
|
|
541
1039
|
}
|
|
542
|
-
function
|
|
1040
|
+
function wantsLocalTestMode(w) {
|
|
543
1041
|
if (isEmbedded()) return false;
|
|
1042
|
+
if (detectNativeEntry(w)) return false;
|
|
544
1043
|
if (!isLoopbackLocation(w)) return false;
|
|
1044
|
+
let params = null;
|
|
545
1045
|
try {
|
|
546
|
-
|
|
1046
|
+
params = new URLSearchParams(w.location.search);
|
|
547
1047
|
} catch {
|
|
1048
|
+
}
|
|
1049
|
+
if (params?.get("genex_local_test") === "1") return true;
|
|
1050
|
+
if (params?.get("genex_auth") === "1") {
|
|
1051
|
+
writeLocalAuthFlag();
|
|
548
1052
|
return false;
|
|
549
1053
|
}
|
|
1054
|
+
if (readLocalAuthFlag()) return false;
|
|
1055
|
+
return !hasGenexFragment(w);
|
|
1056
|
+
}
|
|
1057
|
+
function hasGenexFragment(w) {
|
|
1058
|
+
const hash = stashedTicketHash ?? w.location.hash;
|
|
1059
|
+
return !!hash && hash.includes("genex_");
|
|
550
1060
|
}
|
|
551
1061
|
function enterLocalTestMode(w) {
|
|
552
1062
|
localTestMode = true;
|
|
@@ -566,6 +1076,12 @@ function enterLocalTestMode(w) {
|
|
|
566
1076
|
}
|
|
567
1077
|
async function handleRedeemFailure() {
|
|
568
1078
|
const w = win();
|
|
1079
|
+
if (inNativeMode()) {
|
|
1080
|
+
clearNativeReauth();
|
|
1081
|
+
postNativeError("ticket-rejected");
|
|
1082
|
+
enterBlocked();
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
569
1085
|
if (w && !isEmbedded() && !readRetryFlag()) {
|
|
570
1086
|
writeRetryFlag();
|
|
571
1087
|
showOverlay("redirecting");
|
|
@@ -611,8 +1127,12 @@ async function refreshToken() {
|
|
|
611
1127
|
void requestGuestSession();
|
|
612
1128
|
return;
|
|
613
1129
|
}
|
|
1130
|
+
if (inNativeMode()) {
|
|
1131
|
+
requestNativeReauth();
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
614
1134
|
emit("error", { error: new Error("embed session expired") });
|
|
615
|
-
enterBlocked();
|
|
1135
|
+
enterBlocked("session-expired");
|
|
616
1136
|
return;
|
|
617
1137
|
}
|
|
618
1138
|
if (refreshTimer !== void 0) clearTimeout(refreshTimer);
|
|
@@ -620,7 +1140,7 @@ async function refreshToken() {
|
|
|
620
1140
|
void refreshToken();
|
|
621
1141
|
}, refreshRetryMs);
|
|
622
1142
|
}
|
|
623
|
-
function enterBlocked() {
|
|
1143
|
+
function enterBlocked(reason = "no-identity") {
|
|
624
1144
|
if (state === "blocked") return;
|
|
625
1145
|
identityEpoch++;
|
|
626
1146
|
state = "blocked";
|
|
@@ -629,7 +1149,14 @@ function enterBlocked() {
|
|
|
629
1149
|
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
630
1150
|
if (refreshTimer !== void 0) clearTimeout(refreshTimer);
|
|
631
1151
|
removeGuestPopover();
|
|
632
|
-
|
|
1152
|
+
if (inNativeMode()) {
|
|
1153
|
+
stopNativeTimers();
|
|
1154
|
+
clearNativeReauth();
|
|
1155
|
+
removeOverlay();
|
|
1156
|
+
postToNative({ type: "blocked", reason });
|
|
1157
|
+
} else {
|
|
1158
|
+
showOverlay(isEmbedded() ? "blocked-embedded" : "blocked-standalone");
|
|
1159
|
+
}
|
|
633
1160
|
if (isEmbedded()) {
|
|
634
1161
|
postToParent({ type: "genex:embed:blocked", v: PROTOCOL_VERSION }, parentOrigin ?? "*");
|
|
635
1162
|
}
|
|
@@ -944,6 +1471,11 @@ function __resetForTests(overrides) {
|
|
|
944
1471
|
if (refreshTimer !== void 0) clearTimeout(refreshTimer);
|
|
945
1472
|
handshakeTimer = void 0;
|
|
946
1473
|
refreshTimer = void 0;
|
|
1474
|
+
stopNativeTimers();
|
|
1475
|
+
if (w) delete w[NATIVE_RECEIVER];
|
|
1476
|
+
nativeEntry = null;
|
|
1477
|
+
nativeReauthPending = false;
|
|
1478
|
+
nativeBootAt = 0;
|
|
947
1479
|
removeOverlay(true);
|
|
948
1480
|
removeGuestPopover();
|
|
949
1481
|
overlayFontRequested = false;
|
|
@@ -969,9 +1501,15 @@ function __resetForTests(overrides) {
|
|
|
969
1501
|
refreshDelayMs = overrides?.refreshDelayMs ?? 10 * 6e4;
|
|
970
1502
|
refreshRetryMs = overrides?.refreshRetryMs ?? 6e4;
|
|
971
1503
|
overlayTextDelayMs = overrides?.overlayTextDelayMs ?? 500;
|
|
1504
|
+
nativeReauthTimeoutMs = overrides?.nativeReauthTimeoutMs ?? 3e4;
|
|
1505
|
+
heartbeatIntervalMs = overrides?.heartbeatIntervalMs ?? 3e4;
|
|
972
1506
|
}
|
|
973
1507
|
|
|
974
1508
|
export {
|
|
1509
|
+
getShop,
|
|
1510
|
+
getEntitlements,
|
|
1511
|
+
consumeEntitlement,
|
|
1512
|
+
buy,
|
|
975
1513
|
initEmbed,
|
|
976
1514
|
isEmbedded,
|
|
977
1515
|
getAuthState,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,110 @@
|
|
|
1
|
+
/** A thing the game sells. Resolved server-side; the game never sets a price. */
|
|
2
|
+
interface ShopItem {
|
|
3
|
+
id: string;
|
|
4
|
+
/** 'consumable' — spent on use. 'durable' — owned permanently. */
|
|
5
|
+
type: string;
|
|
6
|
+
name: string;
|
|
7
|
+
iconUrl: string | null;
|
|
8
|
+
priceCoins: number;
|
|
9
|
+
/**
|
|
10
|
+
* The real-money equivalent, in USD cents, from the server.
|
|
11
|
+
*
|
|
12
|
+
* Show it next to the coin price. It is not decoration: a currency price
|
|
13
|
+
* without its real-world value is the practice consumer regulators single
|
|
14
|
+
* out first, and the platform sends this so a game never has to compute it —
|
|
15
|
+
* or be tempted to omit it.
|
|
16
|
+
*/
|
|
17
|
+
priceDisplayUsdCents: number;
|
|
18
|
+
}
|
|
19
|
+
interface Entitlement {
|
|
20
|
+
id: string;
|
|
21
|
+
skuId: string;
|
|
22
|
+
name: string;
|
|
23
|
+
type: string;
|
|
24
|
+
quantity: number;
|
|
25
|
+
consumed: boolean;
|
|
26
|
+
createdAt: string;
|
|
27
|
+
}
|
|
28
|
+
type PurchaseStatus =
|
|
29
|
+
/** Settled. The item is owned; consume it, then apply the effect. */
|
|
30
|
+
'succeeded'
|
|
31
|
+
/** The player closed or declined the confirmation. Not an error — say nothing. */
|
|
32
|
+
| 'canceled'
|
|
33
|
+
/** The confirmation was never completed in time. */
|
|
34
|
+
| 'expired'
|
|
35
|
+
/** The player does not have enough coin. */
|
|
36
|
+
| 'insufficient_balance'
|
|
37
|
+
/** Something went wrong. `message` says what, for a log — not for the player. */
|
|
38
|
+
| 'failed';
|
|
39
|
+
interface PurchaseResult {
|
|
40
|
+
status: PurchaseStatus;
|
|
41
|
+
/** Present only on 'succeeded'. */
|
|
42
|
+
entitlementId?: string;
|
|
43
|
+
message?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The items this game sells.
|
|
47
|
+
*
|
|
48
|
+
* Render `name`, `iconUrl`, `priceCoins` AND `priceDisplayUsdCents` from here.
|
|
49
|
+
* Do not hardcode a price in the game: the server charges what its own catalog
|
|
50
|
+
* says, so a hardcoded one is a number that can silently disagree with what the
|
|
51
|
+
* player is actually charged.
|
|
52
|
+
*/
|
|
53
|
+
declare function getShop(): Promise<ShopItem[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Everything this player owns in this game.
|
|
56
|
+
*
|
|
57
|
+
* Call it on EVERY boot with `{ excludeConsumed: true }` and deliver whatever
|
|
58
|
+
* comes back. That is not an optimisation — it is how a purchase survives a
|
|
59
|
+
* crash between paying and receiving.
|
|
60
|
+
*/
|
|
61
|
+
declare function getEntitlements(opts?: {
|
|
62
|
+
excludeConsumed?: boolean;
|
|
63
|
+
}): Promise<Entitlement[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Mark an entitlement used, then apply its effect.
|
|
66
|
+
*
|
|
67
|
+
* That order matters and is worth stating plainly: consume FIRST, apply SECOND.
|
|
68
|
+
* If the game dies in between, the player loses one item — a support ticket. If
|
|
69
|
+
* you apply first and die before consuming, every boot re-delivers it forever —
|
|
70
|
+
* an exploit.
|
|
71
|
+
*
|
|
72
|
+
* Idempotent. `alreadyConsumed: true` means someone else got there first and
|
|
73
|
+
* you must NOT apply the effect again.
|
|
74
|
+
*/
|
|
75
|
+
declare function consumeEntitlement(entitlementId: string): Promise<{
|
|
76
|
+
consumed: boolean;
|
|
77
|
+
alreadyConsumed: boolean;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* Buy something.
|
|
81
|
+
*
|
|
82
|
+
* **Call this synchronously from a real click or tap handler.** On a game's own
|
|
83
|
+
* origin the confirmation is a popup, and browsers only allow one while a user
|
|
84
|
+
* gesture is live — an `await` before this call loses that gesture and the
|
|
85
|
+
* purchase cannot open.
|
|
86
|
+
*
|
|
87
|
+
* Resolves once the SERVER says what happened. A closed window proves nothing:
|
|
88
|
+
* this waits for the ledger, not for the UI.
|
|
89
|
+
*
|
|
90
|
+
* ```ts
|
|
91
|
+
* button.addEventListener('click', async () => {
|
|
92
|
+
* const result = await buy({ skuId: 'sku_potion' });
|
|
93
|
+
* if (result.status !== 'succeeded') return; // canceled is normal
|
|
94
|
+
* for (const e of await getEntitlements({ excludeConsumed: true })) {
|
|
95
|
+
* const { alreadyConsumed } = await consumeEntitlement(e.id);
|
|
96
|
+
* if (!alreadyConsumed) grantPotion(); // consume, THEN apply
|
|
97
|
+
* }
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
declare function buy(opts: {
|
|
102
|
+
skuId: string;
|
|
103
|
+
quantity?: number;
|
|
104
|
+
/** How long to wait for the player to decide. Default 3 minutes. */
|
|
105
|
+
timeoutMs?: number;
|
|
106
|
+
}): Promise<PurchaseResult>;
|
|
107
|
+
|
|
1
108
|
interface EmbedConfig {
|
|
2
109
|
/** This game's own slug (GENEX.slug) — identifies the project to /play/authorize. */
|
|
3
110
|
slug: string;
|
|
@@ -228,6 +335,8 @@ declare function __resetForTests(overrides?: {
|
|
|
228
335
|
refreshDelayMs?: number;
|
|
229
336
|
refreshRetryMs?: number;
|
|
230
337
|
overlayTextDelayMs?: number;
|
|
338
|
+
nativeReauthTimeoutMs?: number;
|
|
339
|
+
heartbeatIntervalMs?: number;
|
|
231
340
|
}): void;
|
|
232
341
|
|
|
233
|
-
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 };
|
|
342
|
+
export { type AuthState, type EmbedConfig, type EmbedEvent, type EmbedUser, type Entitlement, type Leaderboard, type LeaderboardEntry, type PlayerStateResult, type PurchaseResult, type PurchaseStatus, type SaveStateResult, type ShopItem, type SubmitScoreResult, type WorldStateResult, __resetForTests, _stashTicketFromUrl, buy, consumeEntitlement, getAuthState, getColyseusAuth, getColyseusUrls, getEmbedToken, getEntitlements, getLeaderboard, getShop, getUser, initEmbed, isEmbedded, loadPlayerState, loadWorldState, on, savePlayerState, saveWorldState, submitScore, waitForAuth, waitForPlayer };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__resetForTests,
|
|
3
3
|
_stashTicketFromUrl,
|
|
4
|
+
buy,
|
|
5
|
+
consumeEntitlement,
|
|
4
6
|
getAuthState,
|
|
5
7
|
getColyseusAuth,
|
|
6
8
|
getColyseusUrls,
|
|
7
9
|
getEmbedToken,
|
|
10
|
+
getEntitlements,
|
|
8
11
|
getLeaderboard,
|
|
12
|
+
getShop,
|
|
9
13
|
getUser,
|
|
10
14
|
initEmbed,
|
|
11
15
|
isEmbedded,
|
|
@@ -17,15 +21,19 @@ import {
|
|
|
17
21
|
submitScore,
|
|
18
22
|
waitForAuth,
|
|
19
23
|
waitForPlayer
|
|
20
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-DWDBRKRS.js";
|
|
21
25
|
export {
|
|
22
26
|
__resetForTests,
|
|
23
27
|
_stashTicketFromUrl,
|
|
28
|
+
buy,
|
|
29
|
+
consumeEntitlement,
|
|
24
30
|
getAuthState,
|
|
25
31
|
getColyseusAuth,
|
|
26
32
|
getColyseusUrls,
|
|
27
33
|
getEmbedToken,
|
|
34
|
+
getEntitlements,
|
|
28
35
|
getLeaderboard,
|
|
36
|
+
getShop,
|
|
29
37
|
getUser,
|
|
30
38
|
initEmbed,
|
|
31
39
|
isEmbedded,
|
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.15.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",
|