@genex-ai/embed-sdk 0.16.0 → 0.21.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/README.md +243 -0
- package/dist/chunk-Q476GJHI.js +249 -0
- package/dist/{chunk-ALBFRX3L.js → chunk-WJ4KFQLC.js} +284 -257
- package/dist/development.d.ts +44 -0
- package/dist/development.js +99 -0
- package/dist/index.d.ts +80 -3
- package/dist/index.js +16 -1
- package/dist/sentry.js +2 -1
- package/package.json +8 -2
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NATIVE_CHANNEL,
|
|
3
|
+
NATIVE_PROTOCOL_VERSION,
|
|
4
|
+
NATIVE_RECEIVER,
|
|
5
|
+
encodeBridgeMessage,
|
|
6
|
+
hasNativeMarker,
|
|
7
|
+
isGenerationId,
|
|
8
|
+
isGenerationTerminal,
|
|
9
|
+
readBridgeNativeCommand,
|
|
10
|
+
readNativeEntry
|
|
11
|
+
} from "./chunk-Q476GJHI.js";
|
|
12
|
+
|
|
1
13
|
// src/commerce.ts
|
|
2
14
|
var cfg = null;
|
|
3
15
|
function _initCommerce(c) {
|
|
@@ -105,234 +117,179 @@ async function buy(opts) {
|
|
|
105
117
|
return awaitOutcome(view.id, opts.timeoutMs ?? 18e4);
|
|
106
118
|
}
|
|
107
119
|
|
|
108
|
-
//
|
|
109
|
-
var
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
120
|
+
// src/generation.ts
|
|
121
|
+
var config = null;
|
|
122
|
+
function _initGeneration(value) {
|
|
123
|
+
config = value;
|
|
124
|
+
}
|
|
125
|
+
function must2() {
|
|
126
|
+
if (!config) throw new Error("genex generation: call initEmbed() first");
|
|
127
|
+
return config;
|
|
128
|
+
}
|
|
129
|
+
async function request(path, body) {
|
|
130
|
+
const c = must2();
|
|
131
|
+
const token = c.getToken();
|
|
132
|
+
if (!token) throw new Error("no_player_session");
|
|
133
|
+
const response = await fetch(`${c.apiUrl}/api/runtime${path}`, {
|
|
134
|
+
method: body === void 0 ? "GET" : "POST",
|
|
135
|
+
credentials: "omit",
|
|
136
|
+
cache: "no-store",
|
|
137
|
+
headers: { Authorization: `Bearer ${token}`, ...body === void 0 ? {} : { "Content-Type": "application/json" } },
|
|
138
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
139
|
+
});
|
|
140
|
+
const result = await response.json().catch(() => ({}));
|
|
141
|
+
if (!response.ok) throw new Error(typeof result.error === "string" ? result.error : "generation_unavailable");
|
|
142
|
+
return result;
|
|
125
143
|
}
|
|
126
|
-
|
|
127
|
-
|
|
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;
|
|
144
|
+
function validEstimateCoins(value) {
|
|
145
|
+
return Number.isSafeInteger(value) && value >= 0 && value <= 1e6;
|
|
163
146
|
}
|
|
164
|
-
function
|
|
165
|
-
|
|
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;
|
|
147
|
+
function getGenerationModels() {
|
|
148
|
+
return request("/models");
|
|
201
149
|
}
|
|
202
|
-
function
|
|
203
|
-
return
|
|
150
|
+
function isWorkflowIdentifier(value) {
|
|
151
|
+
return typeof value === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(value) && value !== "." && value !== "..";
|
|
204
152
|
}
|
|
205
|
-
function
|
|
206
|
-
|
|
207
|
-
|
|
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;
|
|
153
|
+
function getWorkflowOfferings(workflowId) {
|
|
154
|
+
if (!isWorkflowIdentifier(workflowId)) return Promise.reject(new Error("invalid_workflow_id"));
|
|
155
|
+
return request(`/workflows/${encodeURIComponent(workflowId)}/offerings`);
|
|
220
156
|
}
|
|
221
|
-
function
|
|
222
|
-
if (
|
|
223
|
-
|
|
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
|
-
}
|
|
157
|
+
function getGeneration(generationId) {
|
|
158
|
+
if (!isGenerationId(generationId)) return Promise.reject(new Error("invalid_generation_id"));
|
|
159
|
+
return request(`/generations/${encodeURIComponent(generationId)}`);
|
|
245
160
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
}
|
|
161
|
+
function outcome(view) {
|
|
162
|
+
if (!isGenerationTerminal(view.status)) return void 0;
|
|
163
|
+
return {
|
|
164
|
+
status: view.status,
|
|
165
|
+
generationId: view.id,
|
|
166
|
+
...billingReceipt(view),
|
|
167
|
+
...view.status === "succeeded" ? {
|
|
168
|
+
output: view.output,
|
|
169
|
+
source: view.source,
|
|
170
|
+
...view.modelProvenance ? { modelProvenance: view.modelProvenance } : {}
|
|
171
|
+
} : {},
|
|
172
|
+
...view.error ? { error: view.error } : {}
|
|
173
|
+
};
|
|
293
174
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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;
|
|
175
|
+
function billingReceipt(view) {
|
|
176
|
+
return {
|
|
177
|
+
...view.billingStatus !== void 0 ? { billingStatus: view.billingStatus } : {},
|
|
178
|
+
...view.reservedCoins !== void 0 ? { reservedCoins: view.reservedCoins } : {},
|
|
179
|
+
...view.reservedDisplayUsdCents !== void 0 ? { reservedDisplayUsdCents: view.reservedDisplayUsdCents } : {},
|
|
180
|
+
...view.chargedCoins !== void 0 ? { chargedCoins: view.chargedCoins } : {},
|
|
181
|
+
...view.chargedDisplayUsdCents !== void 0 ? { chargedDisplayUsdCents: view.chargedDisplayUsdCents } : {}
|
|
182
|
+
};
|
|
312
183
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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);
|
|
184
|
+
function authorizationOutcome(view) {
|
|
185
|
+
if (["queued", "dispatching", "awaiting_external", "succeeded"].includes(view.status)) {
|
|
186
|
+
return { status: "authorized", generationId: view.id, ...billingReceipt(view), ...view.funding ? { funding: view.funding } : {} };
|
|
322
187
|
}
|
|
323
|
-
return
|
|
188
|
+
if (!isGenerationTerminal(view.status)) return void 0;
|
|
189
|
+
return {
|
|
190
|
+
status: view.status === "canceled" || view.status === "expired" ? view.status : "failed",
|
|
191
|
+
generationId: view.id,
|
|
192
|
+
...billingReceipt(view),
|
|
193
|
+
...view.error ? { error: view.error } : view.status === "unknown" ? { error: "unknown_execution" } : {}
|
|
194
|
+
};
|
|
324
195
|
}
|
|
325
|
-
function
|
|
326
|
-
|
|
196
|
+
async function poll(generationId, timeoutMs, readOutcome, popup) {
|
|
197
|
+
const deadline = Date.now() + timeoutMs;
|
|
198
|
+
while (Date.now() < deadline) {
|
|
199
|
+
try {
|
|
200
|
+
const view = await getGeneration(generationId);
|
|
201
|
+
const result = readOutcome(view);
|
|
202
|
+
if (result) return result;
|
|
203
|
+
if (popup?.closed && view.status === "requires_confirmation") {
|
|
204
|
+
const canceled = await request(`/generations/${generationId}/cancel`, {});
|
|
205
|
+
const cancelResult = readOutcome(canceled);
|
|
206
|
+
if (cancelResult) return cancelResult;
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
}
|
|
210
|
+
await new Promise((resolve) => setTimeout(resolve, 750));
|
|
211
|
+
}
|
|
212
|
+
return { status: "pending", generationId, error: "wait_timeout" };
|
|
213
|
+
}
|
|
214
|
+
function waitForGeneration(generationId, timeoutMs = 6e5) {
|
|
215
|
+
if (!isGenerationId(generationId)) return Promise.resolve({ status: "failed", error: "invalid_generation_id" });
|
|
216
|
+
return poll(generationId, timeoutMs, outcome);
|
|
217
|
+
}
|
|
218
|
+
async function generate(options) {
|
|
219
|
+
if (!validEstimateCoins(options.estimateCoins)) return { status: "failed", error: "invalid_estimate_coins" };
|
|
220
|
+
const { timeoutMs, idempotencyKey, ...input } = options;
|
|
221
|
+
return startGeneration(
|
|
222
|
+
"/generations",
|
|
223
|
+
{ ...input, idempotencyKey },
|
|
224
|
+
timeoutMs ?? 6e5,
|
|
225
|
+
outcome
|
|
226
|
+
);
|
|
327
227
|
}
|
|
328
|
-
function
|
|
329
|
-
if (!
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
228
|
+
async function requestWorkflow(options) {
|
|
229
|
+
if (!validEstimateCoins(options.estimateCoins)) return { status: "failed", error: "invalid_estimate_coins" };
|
|
230
|
+
if (!isWorkflowIdentifier(options.workflowId)) return { status: "failed", error: "invalid_workflow_id" };
|
|
231
|
+
if (!isWorkflowIdentifier(options.offeringId)) return { status: "failed", error: "invalid_offering_id" };
|
|
232
|
+
return startGeneration(`/workflows/${encodeURIComponent(options.workflowId)}/requests`, {
|
|
233
|
+
offeringId: options.offeringId,
|
|
234
|
+
input: options.input,
|
|
235
|
+
estimateCoins: options.estimateCoins,
|
|
236
|
+
...options.allowExternal !== void 0 ? { allowExternal: options.allowExternal } : {},
|
|
237
|
+
idempotencyKey: options.idempotencyKey
|
|
238
|
+
}, options.timeoutMs ?? 6e5, authorizationOutcome, options);
|
|
239
|
+
}
|
|
240
|
+
async function resumeWorkflow(generationId, options = {}) {
|
|
241
|
+
if (!isGenerationId(generationId)) return { status: "failed", error: "invalid_generation_id" };
|
|
242
|
+
return startGeneration("", {}, options.timeoutMs ?? 6e5, authorizationOutcome, void 0, generationId);
|
|
243
|
+
}
|
|
244
|
+
async function startGeneration(path, body, timeoutMs, readOutcome, workflow, resumeId) {
|
|
245
|
+
const c = must2();
|
|
246
|
+
if (c.isLocalTest()) return { status: "failed", error: "local_test_unsupported" };
|
|
247
|
+
if (c.isNative()) return { status: "failed", error: "native_unsupported" };
|
|
248
|
+
if (typeof window === "undefined") return { status: "failed", error: "no_window" };
|
|
249
|
+
if (!c.getToken()) return { status: "failed", error: "no_player_session" };
|
|
250
|
+
const parentOrigin2 = c.getParentOrigin();
|
|
251
|
+
if (c.isEmbedded() && (!parentOrigin2 || !c.dashboardOrigins.includes(parentOrigin2))) {
|
|
252
|
+
return { status: "failed", error: "untrusted_parent" };
|
|
253
|
+
}
|
|
254
|
+
let popup = null;
|
|
255
|
+
let dashboard;
|
|
256
|
+
if (!c.isEmbedded()) {
|
|
257
|
+
try {
|
|
258
|
+
dashboard = new URL(c.dashboardOrigins[0]);
|
|
259
|
+
if (dashboard.protocol !== "https:" && !(dashboard.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(dashboard.hostname))) throw new Error();
|
|
260
|
+
} catch {
|
|
261
|
+
return { status: "failed", error: "invalid_dashboard_origin" };
|
|
262
|
+
}
|
|
263
|
+
popup = window.open(`${dashboard.origin}/play/generate`, "_blank", "popup,width=480,height=760");
|
|
264
|
+
if (!popup) return { status: "failed", error: "popup_blocked" };
|
|
265
|
+
try {
|
|
266
|
+
popup.opener = null;
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
const view = resumeId ? await getGeneration(resumeId) : await request(path, { ...body, idempotencyKey: body.idempotencyKey ?? crypto.randomUUID() });
|
|
272
|
+
if (resumeId && (view.id !== resumeId || view.kind !== "workflow")) throw new Error("invalid_generation_response");
|
|
273
|
+
if (!isGenerationId(view.id) || workflow && (view.kind !== "workflow" || view.workflow?.id !== workflow.workflowId || view.workflow.offeringId !== workflow.offeringId)) {
|
|
274
|
+
throw new Error("invalid_generation_response");
|
|
275
|
+
}
|
|
276
|
+
const result = readOutcome(view);
|
|
277
|
+
const needsReceipt = view.billingStatus === "pending" || view.quote?.billingPolicy !== void 0 && (view.chargedCoins ?? 0) > 0;
|
|
278
|
+
if (result && result.status !== "authorized" && !needsReceipt) {
|
|
279
|
+
popup?.close();
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
if (popup && dashboard) {
|
|
283
|
+
if (!popup.closed) popup.location.href = `${dashboard.origin}/play/generate/${encodeURIComponent(view.id)}`;
|
|
284
|
+
} else if (parentOrigin2) {
|
|
285
|
+
window.parent.postMessage({ type: "genex:generation:confirm", v: 1, generationId: view.id }, parentOrigin2);
|
|
286
|
+
}
|
|
287
|
+
if (result) return result;
|
|
288
|
+
return poll(view.id, timeoutMs, readOutcome, popup);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
popup?.close();
|
|
291
|
+
return { status: "failed", error: error instanceof Error ? error.message : "generation_unavailable" };
|
|
292
|
+
}
|
|
336
293
|
}
|
|
337
294
|
|
|
338
295
|
// src/index.ts
|
|
@@ -341,8 +298,8 @@ var RETRY_FLAG = "genex:embed:retry";
|
|
|
341
298
|
var POPOVER_DISMISSED_FLAG = "genex:guest:popover-dismissed";
|
|
342
299
|
var LOCAL_AUTH_FLAG = "genex:embed:local-auth";
|
|
343
300
|
var PLAYER_ID_KEY = "genex:player";
|
|
344
|
-
var SDK_VERSION = "0.
|
|
345
|
-
var
|
|
301
|
+
var SDK_VERSION = "0.21.0";
|
|
302
|
+
var config2 = null;
|
|
346
303
|
var state = "pending";
|
|
347
304
|
var user = null;
|
|
348
305
|
var localTestMode = false;
|
|
@@ -366,6 +323,8 @@ var nativeReauthTimeoutMs = 3e4;
|
|
|
366
323
|
var heartbeatIntervalMs = 3e4;
|
|
367
324
|
var handshakeTimer;
|
|
368
325
|
var refreshTimer;
|
|
326
|
+
var unattendedWaiterTimer;
|
|
327
|
+
var earlyWaiterWarned = false;
|
|
369
328
|
var messageHandler;
|
|
370
329
|
var listeners = /* @__PURE__ */ new Map();
|
|
371
330
|
var authWaiters = [];
|
|
@@ -382,8 +341,16 @@ function win() {
|
|
|
382
341
|
function doc() {
|
|
383
342
|
return globalThis.document;
|
|
384
343
|
}
|
|
385
|
-
function doFetch(input, init) {
|
|
386
|
-
|
|
344
|
+
function doFetch(input, init, timeoutMs) {
|
|
345
|
+
let signal;
|
|
346
|
+
if (timeoutMs !== void 0) {
|
|
347
|
+
try {
|
|
348
|
+
const ctor = globalThis.AbortSignal;
|
|
349
|
+
if (ctor && typeof ctor.timeout === "function") signal = ctor.timeout(timeoutMs);
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return globalThis.fetch(input, signal ? { ...init, signal } : init);
|
|
387
354
|
}
|
|
388
355
|
function readRetryFlag() {
|
|
389
356
|
try {
|
|
@@ -449,16 +416,29 @@ function initEmbed(cfg2) {
|
|
|
449
416
|
if (!w) return;
|
|
450
417
|
if (initialized) return;
|
|
451
418
|
initialized = true;
|
|
452
|
-
|
|
419
|
+
if (unattendedWaiterTimer !== void 0) {
|
|
420
|
+
clearTimeout(unattendedWaiterTimer);
|
|
421
|
+
unattendedWaiterTimer = void 0;
|
|
422
|
+
}
|
|
423
|
+
config2 = {
|
|
453
424
|
slug: cfg2.slug,
|
|
454
425
|
apiUrl: cfg2.apiUrl.replace(/\/$/, ""),
|
|
455
426
|
dashboardOrigins: [...cfg2.dashboardOrigins]
|
|
456
427
|
};
|
|
457
428
|
state = "pending";
|
|
458
429
|
_initCommerce({
|
|
459
|
-
apiUrl:
|
|
460
|
-
dashboardOrigins:
|
|
430
|
+
apiUrl: config2.apiUrl,
|
|
431
|
+
dashboardOrigins: config2.dashboardOrigins,
|
|
432
|
+
getToken: () => embedToken,
|
|
433
|
+
isEmbedded,
|
|
434
|
+
isNative: inNativeMode,
|
|
435
|
+
isLocalTest: () => localTestMode
|
|
436
|
+
});
|
|
437
|
+
_initGeneration({
|
|
438
|
+
apiUrl: config2.apiUrl,
|
|
439
|
+
dashboardOrigins: config2.dashboardOrigins,
|
|
461
440
|
getToken: () => embedToken,
|
|
441
|
+
getParentOrigin: () => parentOrigin,
|
|
462
442
|
isEmbedded,
|
|
463
443
|
isNative: inNativeMode,
|
|
464
444
|
isLocalTest: () => localTestMode
|
|
@@ -506,6 +486,7 @@ function getColyseusUrls() {
|
|
|
506
486
|
function waitForAuth() {
|
|
507
487
|
if (state === "authenticated" && user) return Promise.resolve({ user });
|
|
508
488
|
if (state === "blocked") return Promise.reject(new Error("genex embed auth: blocked"));
|
|
489
|
+
noteWaiterBeforeInit("waitForAuth");
|
|
509
490
|
return new Promise((resolve, reject) => {
|
|
510
491
|
authWaiters.push({ resolve, reject });
|
|
511
492
|
});
|
|
@@ -515,10 +496,29 @@ function waitForPlayer() {
|
|
|
515
496
|
return Promise.resolve({ user, guest: state === "guest" });
|
|
516
497
|
}
|
|
517
498
|
if (state === "blocked") return Promise.reject(new Error("genex embed auth: blocked"));
|
|
499
|
+
noteWaiterBeforeInit("waitForPlayer");
|
|
518
500
|
return new Promise((resolve, reject) => {
|
|
519
501
|
playerWaiters.push({ resolve, reject });
|
|
520
502
|
});
|
|
521
503
|
}
|
|
504
|
+
function noteWaiterBeforeInit(fn) {
|
|
505
|
+
if (initialized) return;
|
|
506
|
+
if (!earlyWaiterWarned) {
|
|
507
|
+
earlyWaiterWarned = true;
|
|
508
|
+
try {
|
|
509
|
+
console.error(
|
|
510
|
+
`[genex] ${fn}() called before initEmbed() \u2014 identity can never resolve and the game will sit black. Call initEmbed({ slug, apiUrl, dashboardOrigins }) first (genex-threejs-embed-auth), and draw a frame before you await identity.`
|
|
511
|
+
);
|
|
512
|
+
} catch {
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (unattendedWaiterTimer === void 0) {
|
|
516
|
+
unattendedWaiterTimer = setTimeout(() => {
|
|
517
|
+
unattendedWaiterTimer = void 0;
|
|
518
|
+
if (!initialized) enterBlocked("no-identity");
|
|
519
|
+
}, handshakeTimeoutMs);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
522
|
function on(event, cb) {
|
|
523
523
|
let set = listeners.get(event);
|
|
524
524
|
if (!set) {
|
|
@@ -532,7 +532,7 @@ function on(event, cb) {
|
|
|
532
532
|
}
|
|
533
533
|
var KEEPALIVE_MAX_BYTES = 32 * 1024;
|
|
534
534
|
async function ensurePlayer() {
|
|
535
|
-
if (!
|
|
535
|
+
if (!config2) throw new Error("genex embed: call initEmbed() first");
|
|
536
536
|
if (state === "pending") await waitForPlayer();
|
|
537
537
|
if (state === "blocked") throw new Error("genex embed auth: blocked");
|
|
538
538
|
}
|
|
@@ -541,7 +541,7 @@ function stateFetch(path, init = {}) {
|
|
|
541
541
|
const headers = { Authorization: `Bearer ${embedToken}` };
|
|
542
542
|
if (init.body !== void 0) headers["Content-Type"] = "application/json";
|
|
543
543
|
if (init.ifVersion !== void 0) headers["If-Match"] = String(init.ifVersion);
|
|
544
|
-
return doFetch(`${
|
|
544
|
+
return doFetch(`${config2.apiUrl}/api/projects/${encodeURIComponent(config2.slug)}${path}`, {
|
|
545
545
|
method: init.method ?? "GET",
|
|
546
546
|
headers,
|
|
547
547
|
body: init.body,
|
|
@@ -631,7 +631,7 @@ async function submitScore(score, opts) {
|
|
|
631
631
|
return { submitted: true, best: body.best, improved: body.improved };
|
|
632
632
|
}
|
|
633
633
|
async function getLeaderboard(opts) {
|
|
634
|
-
if (!
|
|
634
|
+
if (!config2) throw new Error("genex embed: call initEmbed() first");
|
|
635
635
|
if (localTestMode) return { items: [], me: null };
|
|
636
636
|
if (state === "pending") {
|
|
637
637
|
try {
|
|
@@ -647,7 +647,7 @@ async function getLeaderboard(opts) {
|
|
|
647
647
|
const headers = {};
|
|
648
648
|
if (embedToken) headers.Authorization = `Bearer ${embedToken}`;
|
|
649
649
|
const res = await doFetch(
|
|
650
|
-
`${
|
|
650
|
+
`${config2.apiUrl}/api/projects/${encodeURIComponent(config2.slug)}/leaderboard${qs ? `?${qs}` : ""}`,
|
|
651
651
|
{ headers }
|
|
652
652
|
);
|
|
653
653
|
if (!res.ok) throw new Error(`genex leaderboard failed (${res.status})`);
|
|
@@ -694,7 +694,7 @@ function acceptsTicket() {
|
|
|
694
694
|
return (state === "pending" || state === "guest") && !redeeming;
|
|
695
695
|
}
|
|
696
696
|
async function handleParentMessage(event) {
|
|
697
|
-
if (!
|
|
697
|
+
if (!config2) return;
|
|
698
698
|
const data = event.data;
|
|
699
699
|
if (!data || data.v !== PROTOCOL_VERSION) return;
|
|
700
700
|
const isTicket = data.type === "genex:embed:ticket";
|
|
@@ -702,7 +702,7 @@ async function handleParentMessage(event) {
|
|
|
702
702
|
if (!isTicket && !isGuest) return;
|
|
703
703
|
if (isTicket && (!acceptsTicket() || typeof data.ticket !== "string" || !data.ticket)) return;
|
|
704
704
|
if (isGuest && (state !== "pending" || redeeming || requestingGuest)) return;
|
|
705
|
-
if (!
|
|
705
|
+
if (!config2.dashboardOrigins.includes(event.origin)) {
|
|
706
706
|
const fresh = await fetchDashboardOrigins();
|
|
707
707
|
if (isTicket && !acceptsTicket()) return;
|
|
708
708
|
if (isGuest && (state !== "pending" || redeeming || requestingGuest)) return;
|
|
@@ -739,13 +739,13 @@ function inNativeMode() {
|
|
|
739
739
|
}
|
|
740
740
|
function postToNative(message) {
|
|
741
741
|
const w = win();
|
|
742
|
-
if (!w || !
|
|
742
|
+
if (!w || !config2 || !nativeEntry) return;
|
|
743
743
|
const bridge = nativeBridge(w);
|
|
744
744
|
if (!bridge) return;
|
|
745
745
|
const encoded = encodeBridgeMessage({
|
|
746
746
|
channel: NATIVE_CHANNEL,
|
|
747
747
|
v: NATIVE_PROTOCOL_VERSION,
|
|
748
|
-
slug:
|
|
748
|
+
slug: config2.slug,
|
|
749
749
|
attemptId: nativeEntry.attemptId,
|
|
750
750
|
nonce: nativeEntry.nonce,
|
|
751
751
|
...message
|
|
@@ -772,11 +772,11 @@ function startNativeHandshake(w, entry) {
|
|
|
772
772
|
}, handshakeTimeoutMs);
|
|
773
773
|
}
|
|
774
774
|
async function handleNativeCommand(payload) {
|
|
775
|
-
if (!
|
|
776
|
-
const
|
|
777
|
-
if (!
|
|
778
|
-
const command =
|
|
779
|
-
if (command.slug !==
|
|
775
|
+
if (!config2 || !nativeEntry) return;
|
|
776
|
+
const read = readBridgeNativeCommand(payload);
|
|
777
|
+
if (!read.ok) return;
|
|
778
|
+
const command = read.message;
|
|
779
|
+
if (command.slug !== config2.slug || command.attemptId !== nativeEntry.attemptId || command.nonce !== nativeEntry.nonce) {
|
|
780
780
|
return;
|
|
781
781
|
}
|
|
782
782
|
switch (command.type) {
|
|
@@ -841,16 +841,25 @@ function postToParent(message, targetOrigin) {
|
|
|
841
841
|
async function startStandaloneFlow(w) {
|
|
842
842
|
const frag = readFragment(w);
|
|
843
843
|
if (frag.ticket) {
|
|
844
|
+
armStandaloneWatchdog();
|
|
844
845
|
await redeemTicket(frag.ticket);
|
|
845
846
|
return;
|
|
846
847
|
}
|
|
847
848
|
if (frag.guest) {
|
|
849
|
+
armStandaloneWatchdog();
|
|
848
850
|
await requestGuestSession();
|
|
849
851
|
return;
|
|
850
852
|
}
|
|
851
853
|
showOverlay("redirecting");
|
|
852
854
|
await redirectToAuthorize(w, { guestOk: true });
|
|
853
855
|
}
|
|
856
|
+
function armStandaloneWatchdog() {
|
|
857
|
+
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
858
|
+
handshakeTimer = setTimeout(() => {
|
|
859
|
+
handshakeTimer = void 0;
|
|
860
|
+
if (state === "pending") enterBlocked("no-identity");
|
|
861
|
+
}, handshakeTimeoutMs);
|
|
862
|
+
}
|
|
854
863
|
var stashedTicketHash = null;
|
|
855
864
|
function _stashTicketFromUrl() {
|
|
856
865
|
const w = win();
|
|
@@ -896,44 +905,48 @@ function readFragment(w) {
|
|
|
896
905
|
return { ticket, guest };
|
|
897
906
|
}
|
|
898
907
|
async function redirectToAuthorize(w, opts) {
|
|
899
|
-
if (!
|
|
908
|
+
if (!config2 || localTestMode) return;
|
|
900
909
|
if (inNativeMode()) return;
|
|
901
|
-
const origin = opts?.guestOk ?
|
|
910
|
+
const origin = opts?.guestOk ? config2.dashboardOrigins[0] ?? (await fetchDashboardOrigins())[0] : (await fetchDashboardOrigins())[0] ?? config2.dashboardOrigins[0];
|
|
902
911
|
if (!origin) {
|
|
903
912
|
enterBlocked();
|
|
904
913
|
return;
|
|
905
914
|
}
|
|
906
915
|
const returnTo = w.location.origin + w.location.pathname;
|
|
907
916
|
const guestParam = opts?.guestOk ? "&guest=ok" : "";
|
|
908
|
-
const url = `${origin}/play/authorize?returnTo=${encodeURIComponent(returnTo)}&slug=${encodeURIComponent(
|
|
917
|
+
const url = `${origin}/play/authorize?returnTo=${encodeURIComponent(returnTo)}&slug=${encodeURIComponent(config2.slug)}${guestParam}`;
|
|
909
918
|
w.location.replace(url);
|
|
910
919
|
}
|
|
911
920
|
async function fetchDashboardOrigins() {
|
|
912
|
-
if (!
|
|
921
|
+
if (!config2) return [];
|
|
913
922
|
try {
|
|
914
|
-
const res = await doFetch(`${
|
|
915
|
-
if (!res.ok) return
|
|
923
|
+
const res = await doFetch(`${config2.apiUrl}/api/embed/dashboard-origins`);
|
|
924
|
+
if (!res.ok) return config2.dashboardOrigins;
|
|
916
925
|
const body = await res.json();
|
|
917
926
|
if (Array.isArray(body.origins) && body.origins.every((o) => typeof o === "string")) {
|
|
918
927
|
return body.origins;
|
|
919
928
|
}
|
|
920
929
|
} catch {
|
|
921
930
|
}
|
|
922
|
-
return
|
|
931
|
+
return config2.dashboardOrigins;
|
|
923
932
|
}
|
|
924
933
|
async function redeemTicket(ticket) {
|
|
925
|
-
if (!
|
|
934
|
+
if (!config2 || localTestMode || redeeming) return;
|
|
926
935
|
const reauthing = nativeReauthPending && state === "authenticated";
|
|
927
936
|
if (state !== "pending" && state !== "guest" && !reauthing) return;
|
|
928
937
|
const upgradingFromGuest = state === "guest";
|
|
929
938
|
const epoch = ++identityEpoch;
|
|
930
939
|
redeeming = true;
|
|
931
940
|
try {
|
|
932
|
-
const res = await doFetch(
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
941
|
+
const res = await doFetch(
|
|
942
|
+
`${config2.apiUrl}/api/embed/session`,
|
|
943
|
+
{
|
|
944
|
+
method: "POST",
|
|
945
|
+
headers: { "Content-Type": "application/json" },
|
|
946
|
+
body: JSON.stringify({ ticket })
|
|
947
|
+
},
|
|
948
|
+
handshakeTimeoutMs
|
|
949
|
+
);
|
|
937
950
|
if (epoch !== identityEpoch) return;
|
|
938
951
|
if (!res.ok) {
|
|
939
952
|
emit("error", { error: new Error(`ticket redemption failed (${res.status})`) });
|
|
@@ -946,6 +959,8 @@ async function redeemTicket(ticket) {
|
|
|
946
959
|
user = body.user;
|
|
947
960
|
colyseusUrls = body.colyseus?.urls;
|
|
948
961
|
state = "authenticated";
|
|
962
|
+
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
963
|
+
handshakeTimer = void 0;
|
|
949
964
|
clearRetryFlag();
|
|
950
965
|
removeOverlay();
|
|
951
966
|
removeGuestPopover();
|
|
@@ -972,19 +987,19 @@ async function redeemTicket(ticket) {
|
|
|
972
987
|
}
|
|
973
988
|
}
|
|
974
989
|
async function requestGuestSession() {
|
|
975
|
-
if (!
|
|
990
|
+
if (!config2 || localTestMode || requestingGuest || redeeming) return;
|
|
976
991
|
if (state !== "pending" && state !== "guest") return;
|
|
977
992
|
const epoch = ++identityEpoch;
|
|
978
993
|
requestingGuest = true;
|
|
979
994
|
try {
|
|
980
|
-
const res = await doFetch(`${
|
|
995
|
+
const res = await doFetch(`${config2.apiUrl}/api/embed/guest-session`, {
|
|
981
996
|
method: "POST",
|
|
982
997
|
headers: { "Content-Type": "application/json" },
|
|
983
998
|
// playerId is analytics-only: the SERVER still mints the session's own
|
|
984
999
|
// guest id and the token is unchanged — this just lets the play event
|
|
985
1000
|
// key on something that survives the tab.
|
|
986
|
-
body: JSON.stringify({ slug:
|
|
987
|
-
});
|
|
1001
|
+
body: JSON.stringify({ slug: config2.slug, playerId: playerId() })
|
|
1002
|
+
}, handshakeTimeoutMs);
|
|
988
1003
|
if (epoch !== identityEpoch) return;
|
|
989
1004
|
if (!res.ok) {
|
|
990
1005
|
emit("error", { error: new Error(`guest session failed (${res.status})`) });
|
|
@@ -1076,6 +1091,8 @@ function enterLocalTestMode(w) {
|
|
|
1076
1091
|
}
|
|
1077
1092
|
async function handleRedeemFailure() {
|
|
1078
1093
|
const w = win();
|
|
1094
|
+
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
1095
|
+
handshakeTimer = void 0;
|
|
1079
1096
|
if (inNativeMode()) {
|
|
1080
1097
|
clearNativeReauth();
|
|
1081
1098
|
postNativeError("ticket-rejected");
|
|
@@ -1097,12 +1114,12 @@ function scheduleRefresh() {
|
|
|
1097
1114
|
}, refreshDelayMs);
|
|
1098
1115
|
}
|
|
1099
1116
|
async function refreshToken() {
|
|
1100
|
-
if (!
|
|
1117
|
+
if (!config2 || state !== "authenticated" && state !== "guest" || !embedToken) return;
|
|
1101
1118
|
const epoch = identityEpoch;
|
|
1102
1119
|
const tokenAtStart = embedToken;
|
|
1103
1120
|
let status;
|
|
1104
1121
|
try {
|
|
1105
|
-
const res = await doFetch(`${
|
|
1122
|
+
const res = await doFetch(`${config2.apiUrl}/api/embed/session/refresh`, {
|
|
1106
1123
|
method: "POST",
|
|
1107
1124
|
headers: { Authorization: `Bearer ${tokenAtStart}` },
|
|
1108
1125
|
// Best-effort completion if the tab starts unloading mid-refresh; zero
|
|
@@ -1469,8 +1486,11 @@ function __resetForTests(overrides) {
|
|
|
1469
1486
|
messageHandler = void 0;
|
|
1470
1487
|
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
1471
1488
|
if (refreshTimer !== void 0) clearTimeout(refreshTimer);
|
|
1489
|
+
if (unattendedWaiterTimer !== void 0) clearTimeout(unattendedWaiterTimer);
|
|
1472
1490
|
handshakeTimer = void 0;
|
|
1473
1491
|
refreshTimer = void 0;
|
|
1492
|
+
unattendedWaiterTimer = void 0;
|
|
1493
|
+
earlyWaiterWarned = false;
|
|
1474
1494
|
stopNativeTimers();
|
|
1475
1495
|
if (w) delete w[NATIVE_RECEIVER];
|
|
1476
1496
|
nativeEntry = null;
|
|
@@ -1479,7 +1499,7 @@ function __resetForTests(overrides) {
|
|
|
1479
1499
|
removeOverlay(true);
|
|
1480
1500
|
removeGuestPopover();
|
|
1481
1501
|
overlayFontRequested = false;
|
|
1482
|
-
|
|
1502
|
+
config2 = null;
|
|
1483
1503
|
state = "pending";
|
|
1484
1504
|
user = null;
|
|
1485
1505
|
localTestMode = false;
|
|
@@ -1510,6 +1530,13 @@ export {
|
|
|
1510
1530
|
getEntitlements,
|
|
1511
1531
|
consumeEntitlement,
|
|
1512
1532
|
buy,
|
|
1533
|
+
getGenerationModels,
|
|
1534
|
+
getWorkflowOfferings,
|
|
1535
|
+
getGeneration,
|
|
1536
|
+
waitForGeneration,
|
|
1537
|
+
generate,
|
|
1538
|
+
requestWorkflow,
|
|
1539
|
+
resumeWorkflow,
|
|
1513
1540
|
initEmbed,
|
|
1514
1541
|
isEmbedded,
|
|
1515
1542
|
getAuthState,
|