@puzzmo/sdk 1.0.31 → 1.0.33
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/contracts.cjs +0 -0
- package/dist/contracts.d.ts +95 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +0 -0
- package/dist/{createSimulator-xa9RclNq.js → createSimulator-Bo6J_KC_.js} +422 -296
- package/dist/createSimulator-Bo6J_KC_.js.map +1 -0
- package/dist/{createSimulator-GzKfEQWQ.cjs → createSimulator-EBKHk3L6.cjs} +55 -34
- package/dist/createSimulator-EBKHk3L6.cjs.map +1 -0
- package/dist/fonts.cjs +1 -1
- package/dist/fonts.cjs.map +1 -1
- package/dist/fonts.d.ts +13 -13
- package/dist/fonts.js +1 -1
- package/dist/fonts.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +100 -90
- package/dist/index.js.map +1 -1
- package/dist/plugins.d.ts +2 -0
- package/dist/plugins.d.ts.map +1 -0
- package/dist/plugins.spec.d.ts +2 -0
- package/dist/plugins.spec.d.ts.map +1 -0
- package/dist/sdk.d.ts +7 -2
- package/dist/sdk.d.ts.map +1 -1
- package/dist/simulator/createSimulator.d.ts.map +1 -1
- package/dist/simulator/index.cjs +1 -1
- package/dist/simulator/index.d.ts +1 -1
- package/dist/simulator/index.d.ts.map +1 -1
- package/dist/simulator/index.js +1 -1
- package/dist/simulator/standalone.cjs +1 -1
- package/dist/simulator/standalone.js +1 -1
- package/dist/simulator/state.d.ts +3 -1
- package/dist/simulator/state.d.ts.map +1 -1
- package/dist/simulator/types.d.ts +28 -1
- package/dist/simulator/types.d.ts.map +1 -1
- package/dist/simulator/views/HostView.d.ts +9 -0
- package/dist/simulator/views/HostView.d.ts.map +1 -0
- package/dist/simulator/views/index.d.ts +1 -0
- package/dist/simulator/views/index.d.ts.map +1 -1
- package/dist/types.d.ts +31 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.d.ts +19 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js.map +1 -1
- package/package.json +6 -1
- package/dist/createSimulator-GzKfEQWQ.cjs.map +0 -1
- package/dist/createSimulator-xa9RclNq.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -199,107 +199,117 @@ function m() {
|
|
|
199
199
|
var h = m();
|
|
200
200
|
/** Creates a Puzzmo SDK instance for communicating with the Puzzmo host */
|
|
201
201
|
const g = (n = {}) => {
|
|
202
|
-
|
|
202
|
+
var r;
|
|
203
|
+
let o = null, s = null, c = () => {
|
|
203
204
|
var e;
|
|
204
|
-
return
|
|
205
|
-
}, c = () => {
|
|
206
|
-
var e, t;
|
|
207
|
-
return (e = (t = s()) == null ? void 0 : t.id) == null ? null : e;
|
|
205
|
+
return o == null || (e = o.startOrFindGameplay) == null ? void 0 : e.gamePlayed;
|
|
208
206
|
}, l = () => {
|
|
209
207
|
var e, t;
|
|
210
|
-
return (e = (t =
|
|
208
|
+
return (e = (t = c()) == null ? void 0 : t.id) == null ? null : e;
|
|
211
209
|
}, u = () => {
|
|
212
210
|
var e, t;
|
|
213
|
-
return (e = (t =
|
|
211
|
+
return (e = (t = c()) == null ? void 0 : t.puzzle.puzzle) == null ? null : e;
|
|
214
212
|
}, d = () => {
|
|
215
|
-
var e;
|
|
216
|
-
return (e =
|
|
213
|
+
var e, t;
|
|
214
|
+
return (e = (t = c()) == null ? void 0 : t.boardState) == null ? null : e;
|
|
217
215
|
}, f = () => {
|
|
216
|
+
var e;
|
|
217
|
+
return (e = o == null ? void 0 : o.theme) == null ? null : e;
|
|
218
|
+
}, m = () => {
|
|
218
219
|
var e, t;
|
|
219
|
-
return (e = (t =
|
|
220
|
-
},
|
|
221
|
-
|
|
222
|
-
},
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
let [e, t] =
|
|
220
|
+
return (e = (t = c()) == null ? void 0 : t.completed) == null ? !1 : e;
|
|
221
|
+
}, g = /* @__PURE__ */ new Map(), v = null, y = null, b = !1, x = (e, t) => {
|
|
222
|
+
b || y == null || y.trackEvent(e, t);
|
|
223
|
+
}, S = p(), C = null, w = null, T = () => {
|
|
224
|
+
C || (C = setInterval(() => {
|
|
225
|
+
if (S.isPaused()) return;
|
|
226
|
+
let [e, t] = S.display();
|
|
226
227
|
h.sendMessage("TIMER_TICK", { display: [e, t] });
|
|
227
|
-
}, 500),
|
|
228
|
-
|
|
228
|
+
}, 500), w = setInterval(() => {
|
|
229
|
+
S.isPaused() || h.sendMessage("TIMER_SYNC", Math.floor(S.timeWithoutPenaltySecs()));
|
|
229
230
|
}, 1e4));
|
|
230
|
-
},
|
|
231
|
-
|
|
232
|
-
},
|
|
233
|
-
let n =
|
|
231
|
+
}, E = () => {
|
|
232
|
+
C && (clearInterval(C), C = null), w && (clearInterval(w), w = null);
|
|
233
|
+
}, D = (e, t) => {
|
|
234
|
+
let n = g.get(e);
|
|
234
235
|
n && n.forEach((e) => e(t));
|
|
235
236
|
};
|
|
236
|
-
|
|
237
|
-
|
|
237
|
+
h.onMessage("START_GAME", () => {
|
|
238
|
+
S._init(), T(), D("start");
|
|
238
239
|
}), h.onMessage("PAUSE_GAME", () => {
|
|
239
|
-
|
|
240
|
+
S._pause(), D("pause");
|
|
240
241
|
}), h.onMessage("RESUME_GAME", () => {
|
|
241
|
-
|
|
242
|
+
S._resume(), D("resume");
|
|
242
243
|
}), h.onMessage("SETTINGS_UPDATE", (t) => {
|
|
243
|
-
|
|
244
|
-
}), h.onMessage("KEYBOARD_KEY_PRESS", (e) =>
|
|
245
|
-
|
|
244
|
+
v = e(e({}, v), t), D("settingsUpdate", v);
|
|
245
|
+
}), h.onMessage("KEYBOARD_KEY_PRESS", (e) => D("keyboardKeyPress", e)), h.onMessage("KEYBOARD_CURSOR_CHANGE", (e) => D("keyboardCursorChange", e)), h.onMessage("KEYBOARD_CURSOR_END", () => D("keyboardCursorEnd")), h.onMessage("RETRY_PUZZLE", () => {
|
|
246
|
+
S._reset(), E(), D("retry");
|
|
246
247
|
}), h.onMessage("READY_DATA", (e) => {
|
|
247
|
-
var t, n,
|
|
248
|
+
var t, n, r;
|
|
248
249
|
let c = e;
|
|
249
|
-
|
|
250
|
-
let l = (
|
|
250
|
+
o = c, v === null && (v = (t = (n = c.userState) == null ? void 0 : n.gameSettings) == null ? {} : t);
|
|
251
|
+
let l = (r = c.startOrFindGameplay) == null ? void 0 : r.gamePlayed;
|
|
251
252
|
if (l) {
|
|
252
253
|
var u, d;
|
|
253
254
|
let e = ((u = l.elapsedTimeSecs) == null ? 0 : u) * 1e3, t = ((d = l.additionalTimeAddedSecs) == null ? 0 : d) * 1e3;
|
|
254
|
-
|
|
255
|
+
S._reset(e, t);
|
|
255
256
|
}
|
|
256
|
-
if (!
|
|
257
|
+
if (!y) {
|
|
257
258
|
var f;
|
|
258
|
-
|
|
259
|
+
y = i(c), b = (f = l == null ? void 0 : l.completed) == null ? !1 : f, y && a((e) => y.trackLinkClick(e));
|
|
259
260
|
}
|
|
260
|
-
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
261
|
+
s && (s(c), s = null);
|
|
262
|
+
});
|
|
263
|
+
let O = {
|
|
264
|
+
timeMs: () => S.timeMs(),
|
|
265
|
+
timeSecs: () => S.timeSecs(),
|
|
266
|
+
addedTimeMs: () => S.addedTimeMs(),
|
|
267
|
+
addedTimeSecs: () => S.addedTimeSecs(),
|
|
268
|
+
timeWithoutPenaltySecs: () => S.timeWithoutPenaltySecs(),
|
|
269
|
+
display: () => S.display(),
|
|
270
|
+
addPenalty: (e) => S.addPenalty(e),
|
|
271
|
+
isPaused: () => S.isPaused(),
|
|
272
|
+
isRunning: () => S.isRunning()
|
|
273
|
+
}, k = {
|
|
274
|
+
send: (e, t) => h.sendMessage(e, t),
|
|
275
|
+
on: (e, t) => h.onMessage(e, t),
|
|
276
|
+
timer: O,
|
|
277
|
+
bootstrap: () => o
|
|
278
|
+
}, A = {};
|
|
279
|
+
for (let e of (r = n.plugins) == null ? [] : r) A[e.name] = e.setup(k);
|
|
280
|
+
return {
|
|
281
|
+
timer: O,
|
|
282
|
+
plugins: A,
|
|
273
283
|
gameReady: function() {
|
|
274
284
|
var e = t(function* () {
|
|
275
285
|
var e;
|
|
276
|
-
if (h.sendMessage("READY", {}),
|
|
277
|
-
let e =
|
|
286
|
+
if (h.sendMessage("READY", {}), u()) {
|
|
287
|
+
let e = d();
|
|
278
288
|
return {
|
|
279
|
-
puzzleString:
|
|
289
|
+
puzzleString: u(),
|
|
280
290
|
inputString: e,
|
|
281
291
|
boardState: e,
|
|
282
|
-
theme:
|
|
283
|
-
completed:
|
|
284
|
-
readyData:
|
|
292
|
+
theme: f(),
|
|
293
|
+
completed: m(),
|
|
294
|
+
readyData: o
|
|
285
295
|
};
|
|
286
296
|
}
|
|
287
297
|
let t = (e = n.timeout) == null ? 5e3 : e;
|
|
288
298
|
yield new Promise((e, n) => {
|
|
289
|
-
|
|
290
|
-
|
|
299
|
+
s = e, setTimeout(() => {
|
|
300
|
+
s && (s = null, n(/* @__PURE__ */ Error(`Timeout waiting for READY_DATA after ${t}ms`)));
|
|
291
301
|
}, t);
|
|
292
302
|
});
|
|
293
|
-
let
|
|
294
|
-
if (!
|
|
295
|
-
let
|
|
303
|
+
let r = u();
|
|
304
|
+
if (!r) throw Error("READY_DATA received but no puzzle data found");
|
|
305
|
+
let i = d();
|
|
296
306
|
return {
|
|
297
|
-
puzzleString:
|
|
298
|
-
inputString:
|
|
299
|
-
boardState:
|
|
300
|
-
theme:
|
|
301
|
-
completed:
|
|
302
|
-
readyData:
|
|
307
|
+
puzzleString: r,
|
|
308
|
+
inputString: i,
|
|
309
|
+
boardState: i,
|
|
310
|
+
theme: f(),
|
|
311
|
+
completed: m(),
|
|
312
|
+
readyData: o
|
|
303
313
|
};
|
|
304
314
|
});
|
|
305
315
|
return function() {
|
|
@@ -311,49 +321,49 @@ const g = (n = {}) => {
|
|
|
311
321
|
state: e,
|
|
312
322
|
gameRuntimeContract: "1.0",
|
|
313
323
|
embedRuntimeContract: "1.0"
|
|
314
|
-
}),
|
|
324
|
+
}), x("READY_GAME_LOADED");
|
|
315
325
|
},
|
|
316
|
-
on: (e, t) => (
|
|
326
|
+
on: (e, t) => (g.has(e) || g.set(e, /* @__PURE__ */ new Set()), g.get(e).add(t), () => {
|
|
317
327
|
var n;
|
|
318
|
-
(n =
|
|
328
|
+
(n = g.get(e)) == null || n.delete(t);
|
|
319
329
|
}),
|
|
320
330
|
off: (e, t) => {
|
|
321
331
|
var n;
|
|
322
|
-
(n =
|
|
332
|
+
(n = g.get(e)) == null || n.delete(t);
|
|
323
333
|
},
|
|
324
334
|
updateGameState: (e, t) => {
|
|
325
335
|
var n, r;
|
|
326
|
-
let i =
|
|
336
|
+
let i = l();
|
|
327
337
|
i && (h.sendMessage("UPLOAD_NEW_GAME_STATE", {
|
|
328
338
|
id: i,
|
|
329
339
|
input: {
|
|
330
340
|
boardState: e,
|
|
331
|
-
elapsedTimeSecs: (n = t == null ? void 0 : t.elapsedTimeSecs) == null ?
|
|
332
|
-
additionalTimeAddedSecs: (r = t == null ? void 0 : t.additionalTimeAddedSecs) == null ?
|
|
341
|
+
elapsedTimeSecs: (n = t == null ? void 0 : t.elapsedTimeSecs) == null ? S.timeWithoutPenaltySecs() : n,
|
|
342
|
+
additionalTimeAddedSecs: (r = t == null ? void 0 : t.additionalTimeAddedSecs) == null ? S.addedTimeSecs() : r,
|
|
333
343
|
collabUserReferences: []
|
|
334
344
|
}
|
|
335
|
-
}),
|
|
345
|
+
}), x("UPLOAD_NEW_GAME_STATE"));
|
|
336
346
|
},
|
|
337
347
|
gameCompleted: (t, n) => {
|
|
338
348
|
var r, i, a, o, s;
|
|
339
|
-
|
|
340
|
-
let
|
|
341
|
-
elapsedTimeSecs: (r = t.elapsedTimeSecs) == null ?
|
|
342
|
-
additionalTimeAddedSecs: (i = t.additionalTimeAddedSecs) == null ?
|
|
349
|
+
S._conclude(), E();
|
|
350
|
+
let c = e(e({}, t), {}, {
|
|
351
|
+
elapsedTimeSecs: (r = t.elapsedTimeSecs) == null ? S.timeWithoutPenaltySecs() : r,
|
|
352
|
+
additionalTimeAddedSecs: (i = t.additionalTimeAddedSecs) == null ? S.addedTimeSecs() : i
|
|
343
353
|
}), u = (a = n == null ? void 0 : n.deeds) == null ? [] : a;
|
|
344
354
|
u.push({
|
|
345
355
|
id: "points",
|
|
346
356
|
value: t.pointsAwarded
|
|
347
357
|
}), u.push({
|
|
348
358
|
id: "time",
|
|
349
|
-
value: Math.round((o =
|
|
359
|
+
value: Math.round((o = c.elapsedTimeSecs) == null ? 0 : o) + Math.round((s = c.additionalTimeAddedSecs) == null ? 0 : s)
|
|
350
360
|
});
|
|
351
|
-
let d =
|
|
361
|
+
let d = l();
|
|
352
362
|
d && (h.sendMessage("GAME_COMPLETED", {
|
|
353
363
|
id: d,
|
|
354
|
-
input:
|
|
364
|
+
input: c,
|
|
355
365
|
config: n
|
|
356
|
-
}),
|
|
366
|
+
}), x("GAME_COMPLETED", { input: c }));
|
|
357
367
|
},
|
|
358
368
|
showCompletionScreen: (e, t, n = !0) => {
|
|
359
369
|
h.sendMessage("SHOW_GAME_COMPLETE_SCREEN", {
|
|
@@ -364,10 +374,10 @@ const g = (n = {}) => {
|
|
|
364
374
|
},
|
|
365
375
|
hitCheckpoint: (e, t, n) => {
|
|
366
376
|
var r;
|
|
367
|
-
if (!
|
|
368
|
-
let i = (r =
|
|
369
|
-
elapsedTimeSecs:
|
|
370
|
-
additionalTimeAddedSecs:
|
|
377
|
+
if (!l()) return;
|
|
378
|
+
let i = (r = d()) == null ? "" : r, a = {
|
|
379
|
+
elapsedTimeSecs: S.timeWithoutPenaltySecs(),
|
|
380
|
+
additionalTimeAddedSecs: S.addedTimeSecs()
|
|
371
381
|
};
|
|
372
382
|
h.sendMessage("HIT_CHECKPOINT", {
|
|
373
383
|
checkpointName: e,
|
|
@@ -380,15 +390,15 @@ const g = (n = {}) => {
|
|
|
380
390
|
});
|
|
381
391
|
},
|
|
382
392
|
settings: {
|
|
383
|
-
initialize: (t) => (
|
|
393
|
+
initialize: (t) => (v = e(e({}, _(t)), v), h.sendMessage("INITIALIZE_SETTINGS", {
|
|
384
394
|
components: t,
|
|
385
|
-
settings:
|
|
386
|
-
}),
|
|
395
|
+
settings: v
|
|
396
|
+
}), v),
|
|
387
397
|
get: () => {
|
|
388
398
|
var e;
|
|
389
|
-
return (e =
|
|
399
|
+
return (e = v) == null ? {} : e;
|
|
390
400
|
},
|
|
391
|
-
update: (t) => (
|
|
401
|
+
update: (t) => (v = e(e({}, v), t), h.sendMessage("UPDATE_SETTINGS_FROM_EMBED", { settings: v }), v)
|
|
392
402
|
},
|
|
393
403
|
keyboard: {
|
|
394
404
|
show: (e) => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/gameTracker.ts","../src/analytics.ts","../src/sdk.ts","../src/keyboard.ts","../src/editor.ts"],"sourcesContent":["import type { GameAnalyticsContext, LinkClickedInfo } from \"./analytics\"\n\n/**\n * Game analytics event-firing logic for the SDK.\n *\n * This mirrors the runtime's tracker (and the canonical copy in @puzzmo-com/clickhouse/gameTracker\n * used server-side): which host messages map to which events, the engagement thresholds, the\n * payload shape, and the /events endpoint. It is kept here, dependency-free, so the published SDK\n * pulls in none of the clickhouse package's server deps (kysely / @clickhouse/client) and so the\n * public OSS mirror can build it without the private workspace packages present.\n */\n\nexport type GameAnalyticsEventType = \"page_view\" | \"gameplay_active\" | \"active_30s\" | \"completed\" | \"link_click\"\n\ntype GameAnalyticsState = {\n context: GameAnalyticsContext\n updateCount: number\n startTime: Date\n tracked: {\n pageView: boolean\n gameplayActive: boolean\n active30s: boolean\n completed: boolean\n }\n}\n\ntype MessagesSent =\n | \"READY_GAME_LOADED\"\n | \"UPLOAD_NEW_GAME_STATE\"\n | \"GAME_COMPLETED\"\n | \"SHOW_GAME_COMPLETE_SCREEN\"\n | \"UPDATE_GAME_STATE\"\n | string\n\nconst sendGameAnalyticsEvent = async (\n context: GameAnalyticsContext,\n eventType: GameAnalyticsEventType,\n metadata?: Record<string, string | number | boolean>,\n elapsedTimeOnComplete?: number,\n) => {\n try {\n const body = {\n ...context,\n eventType,\n elapsedTimeOnComplete,\n metadata,\n }\n\n const response = await fetch(`${context.apiRoot}events`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n // Ensures the event still fires if the user navigates away (e.g. clicking a _blank link)\n keepalive: true,\n })\n\n if (!response.ok) {\n console.warn(`Analytics event failed: ${eventType} - ${response.status} ${response.statusText}`)\n }\n } catch (error) {\n console.warn(`Analytics event error: ${eventType}`, error)\n }\n}\n\nexport const createGameAnalyticsTracker = (context: GameAnalyticsContext) => {\n const state: GameAnalyticsState = {\n context,\n updateCount: 0,\n startTime: new Date(),\n tracked: {\n pageView: false,\n gameplayActive: false,\n active30s: false,\n completed: false,\n },\n }\n\n const trackEvent = (type: MessagesSent, json?: any) => {\n const now = new Date()\n const timeDiff = now.getTime() - state.startTime.getTime()\n const seconds = timeDiff / 1000\n\n switch (type) {\n case \"READY_GAME_LOADED\": {\n if (!state.tracked.pageView) {\n sendGameAnalyticsEvent(state.context, \"page_view\")\n state.tracked.pageView = true\n }\n break\n }\n\n case \"UPLOAD_NEW_GAME_STATE\": {\n state.updateCount++\n\n // Fire gameplay_active early (after 2 state changes) to indicate user engagement\n if (state.updateCount > 2 && !state.tracked.gameplayActive) {\n sendGameAnalyticsEvent(state.context, \"gameplay_active\")\n state.tracked.gameplayActive = true\n }\n\n // Fire active_30s for sustained engagement\n if (seconds > 30 && !state.tracked.active30s) {\n sendGameAnalyticsEvent(state.context, \"active_30s\")\n state.tracked.active30s = true\n }\n break\n }\n\n case \"GAME_COMPLETED\": {\n if (!state.tracked.completed && json?.input) {\n sendGameAnalyticsEvent(state.context, \"completed\", {}, json.input.elapsedTimeSecs)\n state.tracked.completed = true\n }\n break\n }\n }\n }\n\n const trackLinkClick = (info: LinkClickedInfo) => {\n sendGameAnalyticsEvent(state.context, \"link_click\", info)\n }\n\n return {\n trackEvent,\n trackLinkClick,\n }\n}\n","import { createGameAnalyticsTracker } from \"./gameTracker\"\n\nimport type { BootstrapGameData } from \"./types\"\n\n/**\n * Game analytics for the SDK.\n *\n * The event-firing logic — which host messages map to which ClickHouse events, the\n * engagement thresholds, the payload, and the /events endpoint — lives in ./gameTracker,\n * a self-contained mirror of the runtime's tracker. It is kept local (rather than imported\n * from @puzzmo-com/clickhouse) so the published SDK pulls in none of the clickhouse package's\n * server deps, and so the public OSS mirror can build the SDK without the private workspace\n * packages present.\n *\n * This file only adds the SDK-specific glue: deciding whether analytics should run,\n * building the context from bootstrap data, and watching for link clicks. Events are\n * never sent from localhost (dev + the SDK simulator).\n */\n\nexport type LinkClickedInfo = {\n href: string\n text: string\n target: string\n}\n\ntype UserType = \"anon\" | \"user\" | \"paid\"\ntype RuntimeType = \"puzzmo_com\" | \"iframe\" | \"simple_embed\" | \"app_embed\" | \"ios_native\"\n\n/** Everything the events endpoint needs to attribute an event, mirrors the runtime's context. */\nexport type GameAnalyticsContext = {\n gameSlug: string\n puzzleID: string\n gameplayOwnerID: string\n apiRoot: string\n userType: UserType\n runtimeType: RuntimeType\n userID?: string\n teamSlug?: string\n partnerSlug?: string\n dailyDateKey?: string\n embedID?: string\n}\n\nexport type GameAnalyticsTracker = {\n /** Map an outgoing host message to its analytics event(s), if any. */\n trackEvent: (type: string, json?: any) => void\n /** Report an anchor click inside the game. */\n trackLinkClick: (info: LinkClickedInfo) => void\n}\n\n/**\n * Build the analytics tracker for a game, or null when analytics shouldn't run\n * (the game doesn't bring its own runtime, no browser, running on localhost, or\n * missing bootstrap data).\n */\nexport const createGameAnalytics = (readyData: BootstrapGameData | null): GameAnalyticsTracker | null => {\n // Only games that bring their own runtime track via the SDK — see gameBringsOwnRuntime.\n if (!gameBringsOwnRuntime(readyData)) return null\n\n const endpoint = resolveAnalyticsEndpoint()\n if (!endpoint.enabled) return null\n\n const context = buildAnalyticsContext(readyData, endpoint.apiRoot)\n if (!context) return null\n\n return createGameAnalyticsTracker(context)\n}\n\n/** Delegates document clicks to report anchor navigations. Returns a cleanup function. */\nexport const setupLinkTracking = (onLinkClicked: (info: LinkClickedInfo) => void): (() => void) => {\n if (typeof document === \"undefined\") return () => {}\n\n const handler = (event: MouseEvent) => {\n const node = event.target\n if (!(node instanceof Element)) return\n const anchor = node.closest(\"a[href]\")\n if (!(anchor instanceof HTMLAnchorElement)) return\n\n onLinkClicked({\n href: anchor.href,\n text: anchor.textContent?.trim() ?? \"\",\n target: anchor.target,\n })\n }\n\n document.addEventListener(\"click\", handler)\n return () => document.removeEventListener(\"click\", handler)\n}\n\n/** Picks the events endpoint, and disables analytics entirely on localhost (dev + simulator). */\nconst resolveAnalyticsEndpoint = (): { apiRoot: string; enabled: boolean } => {\n if (typeof window === \"undefined\" || !window.location) return { apiRoot: \"\", enabled: false }\n\n const hostname = window.location.hostname\n const isLocalhost =\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"0.0.0.0\" ||\n hostname === \"[::1]\" ||\n hostname.endsWith(\".localhost\")\n\n if (isLocalhost) return { apiRoot: \"\", enabled: false }\n\n const isStaging = hostname.includes(\"staging\")\n return { apiRoot: isStaging ? \"https://api-staging.puzzmo.com/\" : \"https://api.puzzmo.com/\", enabled: true }\n}\n\n/** Derive the analytics context from the host's bootstrap data, or null if a game isn't present. */\nconst buildAnalyticsContext = (readyData: BootstrapGameData | null, apiRoot: string): GameAnalyticsContext | null => {\n const gameplay = readyData?.startOrFindGameplay?.gamePlayed\n if (!gameplay) return null\n\n const puzzle: any = gameplay.puzzle\n const hostContext: any[] = (readyData?.hostContext as any[]) ?? []\n\n return {\n gameSlug: puzzle.game?.slug ?? \"unknown\",\n puzzleID: puzzle.id,\n gameplayOwnerID: gameplay.ownerID || readyData?.userState?.id || \"unknown\",\n apiRoot,\n userType: mapUserType(readyData?.currentUser?.type),\n runtimeType: getRuntimeType(hostContext),\n userID: readyData?.currentUser?.id,\n teamSlug: puzzle.team?.slug || undefined,\n partnerSlug: hostContext.find((hc) => hc?.type === \"embed\")?.partner?.slug || undefined,\n dailyDateKey: puzzle.mostRecentDaily?.daily?.dateKey,\n embedID: hostContext.find((hc) => hc?.type === \"embed\")?.embedID || undefined,\n }\n}\n\n/** Map the user's subscription tier to the analytics user bucket. */\nconst mapUserType = (type: string | null | undefined): UserType => {\n if (!type || type === \"Unverified\") return \"anon\"\n if (type === \"Paid\") return \"paid\"\n return \"user\"\n}\n\n// GameFlags1.bringsOwnRuntime — see packages/shared/flags/gameFlags.ts. Inlined to keep the SDK\n// free of the @puzzmo-com/shared workspace dep (the OSS mirror doesn't have that package).\nconst bringsOwnRuntimeFlag = 1 << 9\n\n/**\n * Whether the bootstrapped game has the \"brings own runtime\" flag set. SDK analytics only\n * run for these games; the standard Puzzmo runtime tracks the same events itself, so gating\n * here avoids double-counting for games that don't bring their own runtime.\n */\nconst gameBringsOwnRuntime = (readyData: BootstrapGameData | null): boolean => {\n const game: any = readyData?.startOrFindGameplay?.gamePlayed?.puzzle?.game\n const flags = game?.flagsArr\n const flagValue = Array.isArray(flags) ? (flags[0] ?? 0) : typeof flags === \"number\" ? flags : 0\n return (flagValue & bringsOwnRuntimeFlag) === bringsOwnRuntimeFlag\n}\n\n/** Derive the runtime bucket from host context, mirroring the runtime's getRuntimeType. */\nconst getRuntimeType = (hostContext: any[]): RuntimeType => {\n if (hostContext.find((hc) => hc?.type === \"embed\")) return \"iframe\"\n\n const app = hostContext.find((hc) => hc?.type === \"app\")\n if (app?.host === \"__embed\") return \"simple_embed\"\n if (app?.host === \"app-embed\") return \"app_embed\"\n if (app?.host === \"ios-app\") return \"ios_native\"\n\n return \"puzzmo_com\"\n}\n","import { createGameAnalytics, setupLinkTracking, type GameAnalyticsTracker } from \"./analytics\"\nimport type {\n MessagesSentFromEmbed,\n MessagesReceived,\n GamePlay,\n AugmentationConfig,\n CheckpointConfig,\n Theme,\n Deed,\n KeyboardConfig,\n GameSettingsUIComponents,\n} from \"./types\"\n\nexport type SDK = ReturnType<typeof createPuzzmoSDK>\n\nexport interface PuzzmoSDKOptions {\n /** Optional timeout in ms to wait for READY_DATA (default: 5000) */\n timeout?: number\n}\n\ntype SupportedOutgoingMessages = Pick<\n MessagesSentFromEmbed,\n | \"READY\"\n | \"READY_GAME_LOADED\"\n | \"GAME_COMPLETED\"\n | \"SHOW_GAME_COMPLETE_SCREEN\"\n | \"TIMER_TICK\"\n | \"TIMER_SYNC\"\n | \"UPLOAD_NEW_GAME_STATE\"\n | \"HIT_CHECKPOINT\"\n | \"KEYBOARD_UPDATE_CONFIG\"\n | \"INITIALIZE_SETTINGS\"\n | \"UPDATE_SETTINGS_FROM_EMBED\"\n>\n\ntype SupportedIncomingMessages = Pick<\n MessagesReceived,\n | \"READY_DATA\"\n | \"START_GAME\"\n | \"PAUSE_GAME\"\n | \"RESUME_GAME\"\n | \"SETTINGS_UPDATE\"\n | \"RETRY_PUZZLE\"\n | \"KEYBOARD_KEY_PRESS\"\n | \"KEYBOARD_CURSOR_CHANGE\"\n | \"KEYBOARD_CURSOR_END\"\n>\n\ntype MessageHandler<T extends keyof SupportedIncomingMessages> = (data: SupportedIncomingMessages[T]) => void\n\nexport type SDKEventMap = {\n start: void\n pause: void\n resume: void\n retry: void\n /** The player changed a setting in the host UI. The payload is the full resolved settings object. */\n settingsUpdate: any\n /** A key on the on-screen keyboard was tapped. */\n keyboardKeyPress: { key: string }\n /** The drag cursor moved across the keyboard. Only fires when `supportsDragCursor` is true. */\n keyboardCursorChange: { position: [number, number] }\n /** The drag cursor was released. Only fires when `supportsDragCursor` is true. */\n keyboardCursorEnd: void\n}\n\nexport type SDKEventType = keyof SDKEventMap\n\nexport interface SDKTimer {\n /** Get elapsed time in milliseconds */\n timeMs: () => number\n /** Get elapsed time in seconds */\n timeSecs: () => number\n /** Get added/penalty time in milliseconds */\n addedTimeMs: () => number\n /** Get added/penalty time in seconds */\n addedTimeSecs: () => number\n /** Get elapsed time without penalties in seconds */\n timeWithoutPenaltySecs: () => number\n /** Get formatted display strings [elapsed, added] */\n display: () => [string, string]\n /** Add penalty time in milliseconds */\n addPenalty: (ms: number) => void\n /** Check if timer is paused */\n isPaused: () => boolean\n /** Check if timer has been started */\n isRunning: () => boolean\n}\n\nfunction formatTime(timeMs: number): string {\n const isAnHourOrMore = timeMs >= 60 * 60 * 1000\n return new Date(timeMs)\n .toISOString()\n .slice(isAnHourOrMore ? 11 : 14, -1)\n .split(\".\")[0]\n}\n\nfunction createTimer(\n initialTimeMs = 0,\n initialAddedTimeMs = 0,\n): SDKTimer & {\n _init: () => void\n _pause: () => void\n _resume: () => void\n _reset: (initialTimeMs?: number, initialAddedTimeMs?: number) => void\n _conclude: () => void\n} {\n let baseTime = initialTimeMs\n let addedTime = initialAddedTimeMs\n let pausedTime = 0\n let concludeTime: number | undefined\n\n let startDate: number | undefined = undefined\n let pausedDate: number | undefined = undefined\n\n const getTime = (): number => {\n if (startDate === undefined) return baseTime + addedTime\n if (concludeTime !== undefined) return concludeTime\n const now = pausedDate ?? performance.now()\n const elapsed = now - startDate - pausedTime\n return baseTime + addedTime + elapsed\n }\n\n return {\n _init: () => {\n if (startDate === undefined) {\n startDate = performance.now()\n pausedDate = undefined\n }\n },\n _pause: () => {\n if (pausedDate !== undefined || startDate === undefined) return\n pausedDate = performance.now()\n },\n _resume: () => {\n if (pausedDate === undefined) return\n pausedTime += performance.now() - pausedDate\n pausedDate = undefined\n },\n _reset: (newInitialTimeMs = 0, newInitialAddedTimeMs = 0) => {\n baseTime = newInitialTimeMs\n addedTime = newInitialAddedTimeMs\n pausedTime = 0\n concludeTime = undefined\n startDate = undefined\n pausedDate = undefined\n },\n _conclude: () => {\n if (pausedDate !== undefined) {\n concludeTime = getTime()\n return\n }\n if (startDate === undefined) {\n concludeTime = baseTime + addedTime\n return\n }\n concludeTime = getTime()\n },\n timeMs: () => getTime(),\n timeSecs: () => getTime() / 1000,\n addedTimeMs: () => addedTime,\n addedTimeSecs: () => addedTime / 1000,\n timeWithoutPenaltySecs: () => (getTime() - addedTime) / 1000,\n addPenalty: (ms: number) => {\n addedTime += ms\n },\n isPaused: () => pausedDate !== undefined || startDate === undefined,\n isRunning: () => startDate !== undefined && pausedDate === undefined,\n display: () => {\n const elapsed = getTime() - addedTime\n const elapsedStr = formatTime(Math.max(0, elapsed))\n const addedStr = addedTime === 0 ? \"\" : formatTime(addedTime)\n return [elapsedStr, addedStr]\n },\n }\n}\n\nfunction createHostAPI() {\n const messageHandlers = new Map<string, Set<(data: any) => void>>()\n\n const sendMessage = <T extends keyof SupportedOutgoingMessages>(type: T, json: SupportedOutgoingMessages[T]) => {\n const message = { type, json, _: \"p\", __: \"mp\", private: true }\n\n if (\"parent\" in window && window.parent !== window) window.parent.postMessage(message, \"*\")\n\n window.postMessage(message, \"*\")\n\n if (\"webkit\" in window && (window as any).webkit?.messageHandlers?.app) (window as any).webkit.messageHandlers.app.postMessage(message)\n\n if (\"puzzmoMessageString\" in window) (window as any).puzzmoMessageString(JSON.stringify(message))\n\n if (\"ReactNativeWebView\" in window && (window as any).ReactNativeWebView?.postMessage)\n (window as any).ReactNativeWebView.postMessage(JSON.stringify(message))\n\n if (type !== \"TIMER_TICK\" && type !== \"TIMER_SYNC\") console.log(\"[Puzzmo SDK] sent:\", type, json)\n }\n\n const onMessage = <T extends keyof SupportedIncomingMessages>(type: T, handler: MessageHandler<T>) => {\n if (!messageHandlers.has(type)) messageHandlers.set(type, new Set())\n messageHandlers.get(type)!.add(handler)\n\n return () => {\n messageHandlers.get(type)?.delete(handler)\n }\n }\n\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"message\", (event) => {\n if (!event?.data?.type) return\n const msgType = event.data.type as string\n const handlers = messageHandlers.get(msgType)\n if (handlers) {\n const msgData = event.data.data ?? event.data.json ?? {}\n if (msgType !== \"TIMER_TICK\" && msgType !== \"TIMER_SYNC\") console.log(\"[Puzzmo SDK] received:\", msgType, msgData)\n handlers.forEach((handler) => handler(msgData))\n }\n })\n }\n\n return { sendMessage, onMessage }\n}\n\nconst hostAPI = createHostAPI()\n\n/** Creates a Puzzmo SDK instance for communicating with the Puzzmo host */\nexport const createPuzzmoSDK = (options: PuzzmoSDKOptions = {}) => {\n let readyData: MessagesReceived[\"READY_DATA\"] | null = null\n let readyDataResolve: ((data: MessagesReceived[\"READY_DATA\"]) => void) | null = null\n\n const getGameplay = () => readyData?.startOrFindGameplay?.gamePlayed\n const getGameplayID = () => getGameplay()?.id ?? null\n const getPuzzleString = () => getGameplay()?.puzzle.puzzle ?? null\n const getBoardState = () => getGameplay()?.boardState ?? null\n const getTheme = () => readyData?.theme ?? null\n const getCompleted = () => getGameplay()?.completed ?? false\n\n const eventListeners = new Map<SDKEventType, Set<(data?: any) => void>>()\n\n // The resolved settings object — seeded from READY_DATA's saved values, then component\n // defaults are merged underneath when the game calls settings.initialize\n let currentSettings: any | null = null\n\n // ClickHouse game analytics — sends the same lifecycle events the runtime tracks\n // (page_view, gameplay_active, active_30s, completed, link_click). Disabled on localhost.\n let analytics: GameAnalyticsTracker | null = null\n let analyticsCompletedAtStart = false\n\n const trackAnalyticsEvent = (type: string, json?: any) => {\n // Don't re-send progression events for a game that was already completed when loaded\n if (analyticsCompletedAtStart) return\n analytics?.trackEvent(type, json)\n }\n\n const internalTimer = createTimer()\n let timerTickInterval: ReturnType<typeof setInterval> | null = null\n let timerSyncInterval: ReturnType<typeof setInterval> | null = null\n\n const startTimerIntervals = () => {\n if (timerTickInterval) return\n\n timerTickInterval = setInterval(() => {\n if (internalTimer.isPaused()) return\n const [elapsed, added] = internalTimer.display()\n hostAPI.sendMessage(\"TIMER_TICK\", { display: [elapsed, added] })\n }, 500)\n\n timerSyncInterval = setInterval(() => {\n if (internalTimer.isPaused()) return\n hostAPI.sendMessage(\"TIMER_SYNC\", Math.floor(internalTimer.timeWithoutPenaltySecs()))\n }, 10000)\n }\n\n const stopTimerIntervals = () => {\n if (timerTickInterval) {\n clearInterval(timerTickInterval)\n timerTickInterval = null\n }\n if (timerSyncInterval) {\n clearInterval(timerSyncInterval)\n timerSyncInterval = null\n }\n }\n\n const emit = <T extends SDKEventType>(event: T, data?: SDKEventMap[T]) => {\n const listeners = eventListeners.get(event)\n if (listeners) listeners.forEach((listener) => listener(data))\n }\n\n hostAPI.onMessage(\"START_GAME\", () => {\n internalTimer._init()\n startTimerIntervals()\n emit(\"start\")\n })\n\n hostAPI.onMessage(\"PAUSE_GAME\", () => {\n internalTimer._pause()\n emit(\"pause\")\n })\n\n hostAPI.onMessage(\"RESUME_GAME\", () => {\n internalTimer._resume()\n emit(\"resume\")\n })\n\n hostAPI.onMessage(\"SETTINGS_UPDATE\", (data) => {\n currentSettings = { ...currentSettings, ...data }\n emit(\"settingsUpdate\", currentSettings)\n })\n\n hostAPI.onMessage(\"KEYBOARD_KEY_PRESS\", (data) => emit(\"keyboardKeyPress\", data))\n hostAPI.onMessage(\"KEYBOARD_CURSOR_CHANGE\", (data) => emit(\"keyboardCursorChange\", data))\n hostAPI.onMessage(\"KEYBOARD_CURSOR_END\", () => emit(\"keyboardCursorEnd\"))\n\n hostAPI.onMessage(\"RETRY_PUZZLE\", () => {\n internalTimer._reset()\n stopTimerIntervals()\n emit(\"retry\")\n })\n\n hostAPI.onMessage(\"READY_DATA\", (data) => {\n const bootstrapData = data as MessagesReceived[\"READY_DATA\"]\n readyData = bootstrapData\n\n if (currentSettings === null) currentSettings = bootstrapData.userState?.gameSettings ?? {}\n\n const gamePlayed = bootstrapData.startOrFindGameplay?.gamePlayed\n if (gamePlayed) {\n const existingTime = (gamePlayed.elapsedTimeSecs ?? 0) * 1000\n const existingAddedTime = (gamePlayed.additionalTimeAddedSecs ?? 0) * 1000\n internalTimer._reset(existingTime, existingAddedTime)\n }\n\n // Wire up analytics once, from the first bootstrap payload\n if (!analytics) {\n analytics = createGameAnalytics(bootstrapData)\n analyticsCompletedAtStart = gamePlayed?.completed ?? false\n // Link clicks are tracked even for already-completed games, matching the runtime\n if (analytics) setupLinkTracking((info) => analytics!.trackLinkClick(info))\n }\n\n if (readyDataResolve) {\n readyDataResolve(bootstrapData)\n readyDataResolve = null\n }\n })\n\n const timer: SDKTimer = {\n timeMs: () => internalTimer.timeMs(),\n timeSecs: () => internalTimer.timeSecs(),\n addedTimeMs: () => internalTimer.addedTimeMs(),\n addedTimeSecs: () => internalTimer.addedTimeSecs(),\n timeWithoutPenaltySecs: () => internalTimer.timeWithoutPenaltySecs(),\n display: () => internalTimer.display(),\n addPenalty: (ms: number) => internalTimer.addPenalty(ms),\n isPaused: () => internalTimer.isPaused(),\n isRunning: () => internalTimer.isRunning(),\n }\n\n return {\n timer,\n\n gameReady: async (): Promise<{\n puzzleString: string\n inputString: string | null\n theme: Theme | null\n completed: boolean\n readyData: MessagesReceived[\"READY_DATA\"] | null\n }> => {\n hostAPI.sendMessage(\"READY\", {})\n\n if (getPuzzleString()) {\n const inputString = getBoardState()\n return {\n puzzleString: getPuzzleString()!,\n inputString,\n // @ts-expect-error - this is backwards compat\n boardState: inputString,\n theme: getTheme(),\n completed: getCompleted(),\n readyData,\n }\n }\n\n const timeout = options.timeout ?? 5000\n const readyDataPromise = new Promise<MessagesReceived[\"READY_DATA\"]>((resolve, reject) => {\n readyDataResolve = resolve\n setTimeout(() => {\n if (readyDataResolve) {\n readyDataResolve = null\n reject(new Error(`Timeout waiting for READY_DATA after ${timeout}ms`))\n }\n }, timeout)\n })\n\n await readyDataPromise\n\n const puzzleString = getPuzzleString()\n if (!puzzleString) throw new Error(\"READY_DATA received but no puzzle data found\")\n\n const inputString = getBoardState()\n return {\n puzzleString,\n inputString,\n // @ts-expect-error - this is backwards compat\n boardState: inputString,\n theme: getTheme(),\n completed: getCompleted(),\n readyData,\n }\n },\n\n gameLoaded: (state: any = {}) => {\n hostAPI.sendMessage(\"READY_GAME_LOADED\", {\n state,\n gameRuntimeContract: \"1.0\",\n embedRuntimeContract: \"1.0\",\n })\n trackAnalyticsEvent(\"READY_GAME_LOADED\")\n },\n\n on: <T extends SDKEventType>(event: T, listener: (data?: SDKEventMap[T]) => void): (() => void) => {\n if (!eventListeners.has(event)) eventListeners.set(event, new Set())\n eventListeners.get(event)!.add(listener)\n return () => {\n eventListeners.get(event)?.delete(listener)\n }\n },\n\n off: <T extends SDKEventType>(event: T, listener: (data?: SDKEventMap[T]) => void) => {\n eventListeners.get(event)?.delete(listener)\n },\n\n updateGameState: (inputString: string, play?: Partial<GamePlay>) => {\n const gameplayID = getGameplayID()\n if (!gameplayID) return\n\n hostAPI.sendMessage(\"UPLOAD_NEW_GAME_STATE\", {\n id: gameplayID,\n input: {\n boardState: inputString,\n elapsedTimeSecs: play?.elapsedTimeSecs ?? internalTimer.timeWithoutPenaltySecs(),\n additionalTimeAddedSecs: play?.additionalTimeAddedSecs ?? internalTimer.addedTimeSecs(),\n collabUserReferences: [],\n },\n })\n trackAnalyticsEvent(\"UPLOAD_NEW_GAME_STATE\")\n },\n\n gameCompleted: (\n play: Omit<GamePlay, \"elapsedTimeSecs\" | \"additionalTimeAddedSecs\"> &\n Partial<Pick<GamePlay, \"elapsedTimeSecs\" | \"additionalTimeAddedSecs\">>,\n config?: AugmentationConfig,\n ) => {\n internalTimer._conclude()\n stopTimerIntervals()\n\n const finalPlay: Partial<GamePlay> = {\n ...play,\n elapsedTimeSecs: play.elapsedTimeSecs ?? internalTimer.timeWithoutPenaltySecs(),\n additionalTimeAddedSecs: play.additionalTimeAddedSecs ?? internalTimer.addedTimeSecs(),\n }\n\n const deeds: Deed[] = (config?.deeds as any) ?? []\n deeds.push({ id: \"points\", value: play.pointsAwarded })\n deeds.push({\n id: \"time\",\n value: Math.round(finalPlay.elapsedTimeSecs ?? 0) + Math.round(finalPlay.additionalTimeAddedSecs ?? 0),\n })\n\n const gameplayID = getGameplayID()\n if (gameplayID) {\n hostAPI.sendMessage(\"GAME_COMPLETED\", {\n id: gameplayID,\n input: finalPlay,\n config,\n })\n trackAnalyticsEvent(\"GAME_COMPLETED\", { input: finalPlay })\n }\n },\n\n showCompletionScreen: (results: any[], gameplay: GamePlay, showRetry = true) => {\n hostAPI.sendMessage(\"SHOW_GAME_COMPLETE_SCREEN\", {\n results,\n showRetry,\n gameplay,\n })\n },\n\n hitCheckpoint: (checkpointName: string, checkpointConfig: CheckpointConfig, config?: AugmentationConfig) => {\n const gameplayID = getGameplayID()\n if (!gameplayID) return\n\n const inputStr = getBoardState() ?? \"\"\n const play: Partial<GamePlay> = {\n elapsedTimeSecs: internalTimer.timeWithoutPenaltySecs(),\n additionalTimeAddedSecs: internalTimer.addedTimeSecs(),\n }\n\n hostAPI.sendMessage(\"HIT_CHECKPOINT\", {\n checkpointName,\n gameplay: { inputStr, play },\n checkpointConfig,\n augConfig: config ?? {},\n })\n },\n\n settings: {\n /**\n * Register the game's settings UI with the host. Component `defaultValue`s are merged\n * underneath the player's saved values, the host shows the components in its settings panel,\n * and the resolved settings object is returned. Listen for the `settingsUpdate` event to\n * react to the player changing values. Call after `gameReady()`.\n */\n initialize: (components: GameSettingsUIComponents[]) => {\n currentSettings = { ...settingsDefaultsFromComponents(components), ...currentSettings }\n hostAPI.sendMessage(\"INITIALIZE_SETTINGS\", { components, settings: currentSettings })\n return currentSettings\n },\n /** Get the current resolved settings object. */\n get: () => currentSettings ?? {},\n /** Merge changes into the current settings and persist them to the host. Returns the updated settings. */\n update: (changes: any) => {\n currentSettings = { ...currentSettings, ...changes }\n hostAPI.sendMessage(\"UPDATE_SETTINGS_FROM_EMBED\", { settings: currentSettings })\n return currentSettings\n },\n },\n\n keyboard: {\n /** Show the on-screen keyboard with the given config. Call again to update state (e.g. to change disabled keys). */\n show: (config: KeyboardConfig) => {\n hostAPI.sendMessage(\"KEYBOARD_UPDATE_CONFIG\", config)\n },\n /** Hide the on-screen keyboard. */\n hide: () => {\n hostAPI.sendMessage(\"KEYBOARD_UPDATE_CONFIG\", {\n layout: [],\n symbols: {},\n highlight: [],\n disabled: [],\n xl: [],\n l: [],\n supportsDragCursor: false,\n })\n },\n },\n\n _hostAPI: hostAPI,\n }\n}\n\n/** Walks settings UI components (recursing into split groups) collecting name → defaultValue pairs */\nfunction settingsDefaultsFromComponents(components: GameSettingsUIComponents[]): Record<string, unknown> {\n const defaults: Record<string, unknown> = {}\n for (const component of components) {\n if (component.type === \"split\") Object.assign(defaults, settingsDefaultsFromComponents(component.content))\n else if (\"name\" in component) defaults[component.name] = component.defaultValue\n }\n return defaults\n}\n","import type { KeyboardConfig } from \"./types\"\n\n/**\n * A standard QWERTY layout with Enter and Backspace — a reasonable default for\n * any game that needs text input. Customize from here by spreading and overriding.\n *\n * @example\n * // Use as-is\n * sdk.keyboard.show(defaultKeyboardConfig)\n *\n * @example\n * // Extend with dynamic disabled letters\n * sdk.keyboard.show({ ...defaultKeyboardConfig, disabled: usedLetters })\n */\nexport const defaultKeyboardConfig: KeyboardConfig = {\n layout: [\"qwertyuiop\", \"asdfghjkl\", \"↵zxcvbnm⌫\", undefined],\n symbols: { \"↵\": \"Enter\", \"⌫\": \"bsp\" },\n highlight: [\"↵\", \"⌫\"],\n disabled: [],\n xl: [],\n l: [\"↵\", \"⌫\"],\n supportsDragCursor: false,\n}\n","// For now, we will keep these types in here but not mark them as something which should appear in the public SDK docs\n\nimport type { Theme } from \"./types\"\n\n/**\n * The theme passed to an editor bundle. The host sends its full {@link Theme} so editors derive\n * their colors from it rather than making their own theme decisions. Use `theme.type` for\n * light/dark bucketing.\n *\n * The bare `\"light\" | \"dark\"` string form is deprecated and will be removed in a future SDK\n * release — editors should handle the full `Theme` object.\n */\nexport type EditorTheme = Theme | \"light\" | \"dark\"\n\n/** Severity level for validation issues */\nexport type ValidationLevel = \"error\" | \"warning\" | \"info\"\n\n/** Represents a single validation issue found during puzzle validation */\nexport interface ValidationIssue {\n level: ValidationLevel\n message: string\n line?: number\n col?: number\n length?: number\n}\n\n/** Complete validation report for a puzzle */\nexport interface ValidationReport {\n success: boolean\n issues: ValidationIssue[]\n}\n\nexport type ImportErrorType = \"invalid_format\" | \"parsing_error\" | \"unknown\"\n\n/** Custom error class for workshop import failures */\nexport class EditorImportError extends Error {\n constructor(\n public type: ImportErrorType,\n message: string,\n public originalError?: unknown,\n ) {\n super(message)\n this.name = \"EditorImportError\"\n }\n}\n\n/** Result of a successful puzzle import operation */\nexport interface ImportResult {\n data: string\n warnings?: ValidationIssue[]\n title?: string\n authors?: string[]\n editors?: string[]\n}\n\n/** Settings UI descriptor returned by an editor bundle */\nexport interface EditorBundleSettings<TComponent = unknown> {\n components: TComponent[]\n defaults: Record<string, unknown>\n}\n\n/** A preceding/following phrase for a word, returned by the getRelatedWords editor callback */\nexport interface RelatedWord {\n word: string\n frequency: number\n position: \"preceding\" | \"following\"\n}\n\n/** Result of fetching a URL from within the editor */\nexport interface EditorFetchURLResult {\n status: number\n body: string\n}\n\n/** Config passed to an editor bundle's mount() */\nexport interface EditorMountConfig {\n puzzleString: string\n onChange: (puzzleString: string) => void\n theme: EditorTheme\n width: number\n height: number\n /**\n * Optional function for making chat completions from the editor (e.g. for AI-assisted puzzle\n * editing). This needs to be enabled for a team explicitly.\n */\n chatCompletion?: (prompt: string) => Promise<string>\n /**\n * Optional function for fetching URLs from the editor (e.g. for fetching article content).\n * This needs to be enabled for a team explicitly.\n */\n fetchURL?: (url: string) => Promise<EditorFetchURLResult>\n /** Optional function for looking up a word's preceding/following phrases from wordvault. */\n getRelatedWords?: (word: string, limit?: number) => Promise<RelatedWord[]>\n /** Pre-configured editor settings values from the queue's editorSettings. */\n settings?: Record<string, unknown>\n}\n\n/** Handle returned by an editor bundle's mount() */\nexport interface EditorMountHandle {\n unmount: () => void\n /**\n * Called whenever the puzzle string is updated in the editor from the outside, or when the theme\n * or dimensions update. Workshop will rely on the validator to reject / accept updates.\n */\n update: (config: { puzzleString?: string; theme?: EditorTheme; width?: number; height?: number }) => void\n}\n\n/** Main interface for a Workshop editor bundle */\nexport interface EditorBundle<TSettingsComponent = unknown> {\n /** Required validator for puzzle data validation */\n validator: {\n validate(data: string): Promise<ValidationReport> | ValidationReport\n }\n /** Optional importer for converting external puzzle file formats */\n importer?: {\n onImport(filename: string, contents: string | ArrayBuffer): Promise<ImportResult> | ImportResult\n }\n /** Embed-level settings UI, populated from the bundle's declared settings */\n settings?: EditorBundleSettings<TSettingsComponent>\n /** Editor-level settings UI, populated from the bundle's declared editor settings */\n editorSettings?: EditorBundleSettings<TSettingsComponent>\n /** Custom puzzle editor, if provided by the bundle */\n editor?: {\n /** Called when first visiting a puzzle page. */\n mount(element: HTMLElement, config: EditorMountConfig): Promise<EditorMountHandle>\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/gameTracker.ts","../src/analytics.ts","../src/sdk.ts","../src/keyboard.ts","../src/editor.ts"],"sourcesContent":["import type { GameAnalyticsContext, LinkClickedInfo } from \"./analytics\"\n\n/**\n * Game analytics event-firing logic for the SDK.\n *\n * This mirrors the runtime's tracker (and the canonical copy in @puzzmo-com/clickhouse/gameTracker\n * used server-side): which host messages map to which events, the engagement thresholds, the\n * payload shape, and the /events endpoint. It is kept here, dependency-free, so the published SDK\n * pulls in none of the clickhouse package's server deps (kysely / @clickhouse/client) and so the\n * public OSS mirror can build it without the private workspace packages present.\n */\n\nexport type GameAnalyticsEventType = \"page_view\" | \"gameplay_active\" | \"active_30s\" | \"completed\" | \"link_click\"\n\ntype GameAnalyticsState = {\n context: GameAnalyticsContext\n updateCount: number\n startTime: Date\n tracked: {\n pageView: boolean\n gameplayActive: boolean\n active30s: boolean\n completed: boolean\n }\n}\n\ntype MessagesSent =\n | \"READY_GAME_LOADED\"\n | \"UPLOAD_NEW_GAME_STATE\"\n | \"GAME_COMPLETED\"\n | \"SHOW_GAME_COMPLETE_SCREEN\"\n | \"UPDATE_GAME_STATE\"\n | string\n\nconst sendGameAnalyticsEvent = async (\n context: GameAnalyticsContext,\n eventType: GameAnalyticsEventType,\n metadata?: Record<string, string | number | boolean>,\n elapsedTimeOnComplete?: number,\n) => {\n try {\n const body = {\n ...context,\n eventType,\n elapsedTimeOnComplete,\n metadata,\n }\n\n const response = await fetch(`${context.apiRoot}events`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n // Ensures the event still fires if the user navigates away (e.g. clicking a _blank link)\n keepalive: true,\n })\n\n if (!response.ok) {\n console.warn(`Analytics event failed: ${eventType} - ${response.status} ${response.statusText}`)\n }\n } catch (error) {\n console.warn(`Analytics event error: ${eventType}`, error)\n }\n}\n\nexport const createGameAnalyticsTracker = (context: GameAnalyticsContext) => {\n const state: GameAnalyticsState = {\n context,\n updateCount: 0,\n startTime: new Date(),\n tracked: {\n pageView: false,\n gameplayActive: false,\n active30s: false,\n completed: false,\n },\n }\n\n const trackEvent = (type: MessagesSent, json?: any) => {\n const now = new Date()\n const timeDiff = now.getTime() - state.startTime.getTime()\n const seconds = timeDiff / 1000\n\n switch (type) {\n case \"READY_GAME_LOADED\": {\n if (!state.tracked.pageView) {\n sendGameAnalyticsEvent(state.context, \"page_view\")\n state.tracked.pageView = true\n }\n break\n }\n\n case \"UPLOAD_NEW_GAME_STATE\": {\n state.updateCount++\n\n // Fire gameplay_active early (after 2 state changes) to indicate user engagement\n if (state.updateCount > 2 && !state.tracked.gameplayActive) {\n sendGameAnalyticsEvent(state.context, \"gameplay_active\")\n state.tracked.gameplayActive = true\n }\n\n // Fire active_30s for sustained engagement\n if (seconds > 30 && !state.tracked.active30s) {\n sendGameAnalyticsEvent(state.context, \"active_30s\")\n state.tracked.active30s = true\n }\n break\n }\n\n case \"GAME_COMPLETED\": {\n if (!state.tracked.completed && json?.input) {\n sendGameAnalyticsEvent(state.context, \"completed\", {}, json.input.elapsedTimeSecs)\n state.tracked.completed = true\n }\n break\n }\n }\n }\n\n const trackLinkClick = (info: LinkClickedInfo) => {\n sendGameAnalyticsEvent(state.context, \"link_click\", info)\n }\n\n return {\n trackEvent,\n trackLinkClick,\n }\n}\n","import { createGameAnalyticsTracker } from \"./gameTracker\"\n\nimport type { BootstrapGameData } from \"./types\"\n\n/**\n * Game analytics for the SDK.\n *\n * The event-firing logic — which host messages map to which ClickHouse events, the\n * engagement thresholds, the payload, and the /events endpoint — lives in ./gameTracker,\n * a self-contained mirror of the runtime's tracker. It is kept local (rather than imported\n * from @puzzmo-com/clickhouse) so the published SDK pulls in none of the clickhouse package's\n * server deps, and so the public OSS mirror can build the SDK without the private workspace\n * packages present.\n *\n * This file only adds the SDK-specific glue: deciding whether analytics should run,\n * building the context from bootstrap data, and watching for link clicks. Events are\n * never sent from localhost (dev + the SDK simulator).\n */\n\nexport type LinkClickedInfo = {\n href: string\n text: string\n target: string\n}\n\ntype UserType = \"anon\" | \"user\" | \"paid\"\ntype RuntimeType = \"puzzmo_com\" | \"iframe\" | \"simple_embed\" | \"app_embed\" | \"ios_native\"\n\n/** Everything the events endpoint needs to attribute an event, mirrors the runtime's context. */\nexport type GameAnalyticsContext = {\n gameSlug: string\n puzzleID: string\n gameplayOwnerID: string\n apiRoot: string\n userType: UserType\n runtimeType: RuntimeType\n userID?: string\n teamSlug?: string\n partnerSlug?: string\n dailyDateKey?: string\n embedID?: string\n}\n\nexport type GameAnalyticsTracker = {\n /** Map an outgoing host message to its analytics event(s), if any. */\n trackEvent: (type: string, json?: any) => void\n /** Report an anchor click inside the game. */\n trackLinkClick: (info: LinkClickedInfo) => void\n}\n\n/**\n * Build the analytics tracker for a game, or null when analytics shouldn't run\n * (the game doesn't bring its own runtime, no browser, running on localhost, or\n * missing bootstrap data).\n */\nexport const createGameAnalytics = (readyData: BootstrapGameData | null): GameAnalyticsTracker | null => {\n // Only games that bring their own runtime track via the SDK — see gameBringsOwnRuntime.\n if (!gameBringsOwnRuntime(readyData)) return null\n\n const endpoint = resolveAnalyticsEndpoint()\n if (!endpoint.enabled) return null\n\n const context = buildAnalyticsContext(readyData, endpoint.apiRoot)\n if (!context) return null\n\n return createGameAnalyticsTracker(context)\n}\n\n/** Delegates document clicks to report anchor navigations. Returns a cleanup function. */\nexport const setupLinkTracking = (onLinkClicked: (info: LinkClickedInfo) => void): (() => void) => {\n if (typeof document === \"undefined\") return () => {}\n\n const handler = (event: MouseEvent) => {\n const node = event.target\n if (!(node instanceof Element)) return\n const anchor = node.closest(\"a[href]\")\n if (!(anchor instanceof HTMLAnchorElement)) return\n\n onLinkClicked({\n href: anchor.href,\n text: anchor.textContent?.trim() ?? \"\",\n target: anchor.target,\n })\n }\n\n document.addEventListener(\"click\", handler)\n return () => document.removeEventListener(\"click\", handler)\n}\n\n/** Picks the events endpoint, and disables analytics entirely on localhost (dev + simulator). */\nconst resolveAnalyticsEndpoint = (): { apiRoot: string; enabled: boolean } => {\n if (typeof window === \"undefined\" || !window.location) return { apiRoot: \"\", enabled: false }\n\n const hostname = window.location.hostname\n const isLocalhost =\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"0.0.0.0\" ||\n hostname === \"[::1]\" ||\n hostname.endsWith(\".localhost\")\n\n if (isLocalhost) return { apiRoot: \"\", enabled: false }\n\n const isStaging = hostname.includes(\"staging\")\n return { apiRoot: isStaging ? \"https://api-staging.puzzmo.com/\" : \"https://api.puzzmo.com/\", enabled: true }\n}\n\n/** Derive the analytics context from the host's bootstrap data, or null if a game isn't present. */\nconst buildAnalyticsContext = (readyData: BootstrapGameData | null, apiRoot: string): GameAnalyticsContext | null => {\n const gameplay = readyData?.startOrFindGameplay?.gamePlayed\n if (!gameplay) return null\n\n const puzzle: any = gameplay.puzzle\n const hostContext: any[] = (readyData?.hostContext as any[]) ?? []\n\n return {\n gameSlug: puzzle.game?.slug ?? \"unknown\",\n puzzleID: puzzle.id,\n gameplayOwnerID: gameplay.ownerID || readyData?.userState?.id || \"unknown\",\n apiRoot,\n userType: mapUserType(readyData?.currentUser?.type),\n runtimeType: getRuntimeType(hostContext),\n userID: readyData?.currentUser?.id,\n teamSlug: puzzle.team?.slug || undefined,\n partnerSlug: hostContext.find((hc) => hc?.type === \"embed\")?.partner?.slug || undefined,\n dailyDateKey: puzzle.mostRecentDaily?.daily?.dateKey,\n embedID: hostContext.find((hc) => hc?.type === \"embed\")?.embedID || undefined,\n }\n}\n\n/** Map the user's subscription tier to the analytics user bucket. */\nconst mapUserType = (type: string | null | undefined): UserType => {\n if (!type || type === \"Unverified\") return \"anon\"\n if (type === \"Paid\") return \"paid\"\n return \"user\"\n}\n\n// GameFlags1.bringsOwnRuntime — see packages/shared/flags/gameFlags.ts. Inlined to keep the SDK\n// free of the @puzzmo-com/shared workspace dep (the OSS mirror doesn't have that package).\nconst bringsOwnRuntimeFlag = 1 << 9\n\n/**\n * Whether the bootstrapped game has the \"brings own runtime\" flag set. SDK analytics only\n * run for these games; the standard Puzzmo runtime tracks the same events itself, so gating\n * here avoids double-counting for games that don't bring their own runtime.\n */\nconst gameBringsOwnRuntime = (readyData: BootstrapGameData | null): boolean => {\n const game: any = readyData?.startOrFindGameplay?.gamePlayed?.puzzle?.game\n const flags = game?.flagsArr\n const flagValue = Array.isArray(flags) ? (flags[0] ?? 0) : typeof flags === \"number\" ? flags : 0\n return (flagValue & bringsOwnRuntimeFlag) === bringsOwnRuntimeFlag\n}\n\n/** Derive the runtime bucket from host context, mirroring the runtime's getRuntimeType. */\nconst getRuntimeType = (hostContext: any[]): RuntimeType => {\n if (hostContext.find((hc) => hc?.type === \"embed\")) return \"iframe\"\n\n const app = hostContext.find((hc) => hc?.type === \"app\")\n if (app?.host === \"__embed\") return \"simple_embed\"\n if (app?.host === \"app-embed\") return \"app_embed\"\n if (app?.host === \"ios-app\") return \"ios_native\"\n\n return \"puzzmo_com\"\n}\n","import { createGameAnalytics, setupLinkTracking, type GameAnalyticsTracker } from \"./analytics\"\nimport type { PluginAPIs, SDKPlugin, SDKPluginContext } from \"./plugins\"\nimport type {\n MessagesSentFromEmbed,\n MessagesReceived,\n GamePlay,\n AugmentationConfig,\n CheckpointConfig,\n Theme,\n Deed,\n KeyboardConfig,\n GameSettingsUIComponents,\n} from \"./types\"\n\nexport type SDK = ReturnType<typeof createPuzzmoSDK>\n\nexport interface PuzzmoSDKOptions<Plugins extends readonly SDKPlugin[] = []> {\n /** Optional timeout in ms to wait for READY_DATA (default: 5000) */\n timeout?: number\n /** Plugins to extend the SDK with; each contributes a namespaced API at `sdk.plugins[name]`. */\n plugins?: Plugins\n}\n\ntype SupportedOutgoingMessages = Pick<\n MessagesSentFromEmbed,\n | \"READY\"\n | \"READY_GAME_LOADED\"\n | \"GAME_COMPLETED\"\n | \"SHOW_GAME_COMPLETE_SCREEN\"\n | \"TIMER_TICK\"\n | \"TIMER_SYNC\"\n | \"UPLOAD_NEW_GAME_STATE\"\n | \"HIT_CHECKPOINT\"\n | \"KEYBOARD_UPDATE_CONFIG\"\n | \"INITIALIZE_SETTINGS\"\n | \"UPDATE_SETTINGS_FROM_EMBED\"\n>\n\ntype SupportedIncomingMessages = Pick<\n MessagesReceived,\n | \"READY_DATA\"\n | \"START_GAME\"\n | \"PAUSE_GAME\"\n | \"RESUME_GAME\"\n | \"SETTINGS_UPDATE\"\n | \"RETRY_PUZZLE\"\n | \"KEYBOARD_KEY_PRESS\"\n | \"KEYBOARD_CURSOR_CHANGE\"\n | \"KEYBOARD_CURSOR_END\"\n>\n\ntype MessageHandler<T extends keyof SupportedIncomingMessages> = (data: SupportedIncomingMessages[T]) => void\n\nexport type SDKEventMap = {\n start: void\n pause: void\n resume: void\n retry: void\n /** The player changed a setting in the host UI. The payload is the full resolved settings object. */\n settingsUpdate: any\n /** A key on the on-screen keyboard was tapped. */\n keyboardKeyPress: { key: string }\n /** The drag cursor moved across the keyboard. Only fires when `supportsDragCursor` is true. */\n keyboardCursorChange: { position: [number, number] }\n /** The drag cursor was released. Only fires when `supportsDragCursor` is true. */\n keyboardCursorEnd: void\n}\n\nexport type SDKEventType = keyof SDKEventMap\n\nexport interface SDKTimer {\n /** Get elapsed time in milliseconds */\n timeMs: () => number\n /** Get elapsed time in seconds */\n timeSecs: () => number\n /** Get added/penalty time in milliseconds */\n addedTimeMs: () => number\n /** Get added/penalty time in seconds */\n addedTimeSecs: () => number\n /** Get elapsed time without penalties in seconds */\n timeWithoutPenaltySecs: () => number\n /** Get formatted display strings [elapsed, added] */\n display: () => [string, string]\n /** Add penalty time in milliseconds */\n addPenalty: (ms: number) => void\n /** Check if timer is paused */\n isPaused: () => boolean\n /** Check if timer has been started */\n isRunning: () => boolean\n}\n\nfunction formatTime(timeMs: number): string {\n const isAnHourOrMore = timeMs >= 60 * 60 * 1000\n return new Date(timeMs)\n .toISOString()\n .slice(isAnHourOrMore ? 11 : 14, -1)\n .split(\".\")[0]\n}\n\nfunction createTimer(\n initialTimeMs = 0,\n initialAddedTimeMs = 0,\n): SDKTimer & {\n _init: () => void\n _pause: () => void\n _resume: () => void\n _reset: (initialTimeMs?: number, initialAddedTimeMs?: number) => void\n _conclude: () => void\n} {\n let baseTime = initialTimeMs\n let addedTime = initialAddedTimeMs\n let pausedTime = 0\n let concludeTime: number | undefined\n\n let startDate: number | undefined = undefined\n let pausedDate: number | undefined = undefined\n\n const getTime = (): number => {\n if (startDate === undefined) return baseTime + addedTime\n if (concludeTime !== undefined) return concludeTime\n const now = pausedDate ?? performance.now()\n const elapsed = now - startDate - pausedTime\n return baseTime + addedTime + elapsed\n }\n\n return {\n _init: () => {\n if (startDate === undefined) {\n startDate = performance.now()\n pausedDate = undefined\n }\n },\n _pause: () => {\n if (pausedDate !== undefined || startDate === undefined) return\n pausedDate = performance.now()\n },\n _resume: () => {\n if (pausedDate === undefined) return\n pausedTime += performance.now() - pausedDate\n pausedDate = undefined\n },\n _reset: (newInitialTimeMs = 0, newInitialAddedTimeMs = 0) => {\n baseTime = newInitialTimeMs\n addedTime = newInitialAddedTimeMs\n pausedTime = 0\n concludeTime = undefined\n startDate = undefined\n pausedDate = undefined\n },\n _conclude: () => {\n if (pausedDate !== undefined) {\n concludeTime = getTime()\n return\n }\n if (startDate === undefined) {\n concludeTime = baseTime + addedTime\n return\n }\n concludeTime = getTime()\n },\n timeMs: () => getTime(),\n timeSecs: () => getTime() / 1000,\n addedTimeMs: () => addedTime,\n addedTimeSecs: () => addedTime / 1000,\n timeWithoutPenaltySecs: () => (getTime() - addedTime) / 1000,\n addPenalty: (ms: number) => {\n addedTime += ms\n },\n isPaused: () => pausedDate !== undefined || startDate === undefined,\n isRunning: () => startDate !== undefined && pausedDate === undefined,\n display: () => {\n const elapsed = getTime() - addedTime\n const elapsedStr = formatTime(Math.max(0, elapsed))\n const addedStr = addedTime === 0 ? \"\" : formatTime(addedTime)\n return [elapsedStr, addedStr]\n },\n }\n}\n\nfunction createHostAPI() {\n const messageHandlers = new Map<string, Set<(data: any) => void>>()\n\n const sendMessage = <T extends keyof SupportedOutgoingMessages>(type: T, json: SupportedOutgoingMessages[T]) => {\n const message = { type, json, _: \"p\", __: \"mp\", private: true }\n\n if (\"parent\" in window && window.parent !== window) window.parent.postMessage(message, \"*\")\n\n window.postMessage(message, \"*\")\n\n if (\"webkit\" in window && (window as any).webkit?.messageHandlers?.app) (window as any).webkit.messageHandlers.app.postMessage(message)\n\n if (\"puzzmoMessageString\" in window) (window as any).puzzmoMessageString(JSON.stringify(message))\n\n if (\"ReactNativeWebView\" in window && (window as any).ReactNativeWebView?.postMessage)\n (window as any).ReactNativeWebView.postMessage(JSON.stringify(message))\n\n if (type !== \"TIMER_TICK\" && type !== \"TIMER_SYNC\") console.log(\"[Puzzmo SDK] sent:\", type, json)\n }\n\n const onMessage = <T extends keyof SupportedIncomingMessages>(type: T, handler: MessageHandler<T>) => {\n if (!messageHandlers.has(type)) messageHandlers.set(type, new Set())\n messageHandlers.get(type)!.add(handler)\n\n return () => {\n messageHandlers.get(type)?.delete(handler)\n }\n }\n\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"message\", (event) => {\n if (!event?.data?.type) return\n const msgType = event.data.type as string\n const handlers = messageHandlers.get(msgType)\n if (handlers) {\n const msgData = event.data.data ?? event.data.json ?? {}\n if (msgType !== \"TIMER_TICK\" && msgType !== \"TIMER_SYNC\") console.log(\"[Puzzmo SDK] received:\", msgType, msgData)\n handlers.forEach((handler) => handler(msgData))\n }\n })\n }\n\n return { sendMessage, onMessage }\n}\n\nconst hostAPI = createHostAPI()\n\n/** Creates a Puzzmo SDK instance for communicating with the Puzzmo host */\nexport const createPuzzmoSDK = <const Plugins extends readonly SDKPlugin[] = []>(options: PuzzmoSDKOptions<Plugins> = {}) => {\n let readyData: MessagesReceived[\"READY_DATA\"] | null = null\n let readyDataResolve: ((data: MessagesReceived[\"READY_DATA\"]) => void) | null = null\n\n const getGameplay = () => readyData?.startOrFindGameplay?.gamePlayed\n const getGameplayID = () => getGameplay()?.id ?? null\n const getPuzzleString = () => getGameplay()?.puzzle.puzzle ?? null\n const getBoardState = () => getGameplay()?.boardState ?? null\n const getTheme = () => readyData?.theme ?? null\n const getCompleted = () => getGameplay()?.completed ?? false\n\n const eventListeners = new Map<SDKEventType, Set<(data?: any) => void>>()\n\n // The resolved settings object — seeded from READY_DATA's saved values, then component\n // defaults are merged underneath when the game calls settings.initialize\n let currentSettings: any | null = null\n\n // ClickHouse game analytics — sends the same lifecycle events the runtime tracks\n // (page_view, gameplay_active, active_30s, completed, link_click). Disabled on localhost.\n let analytics: GameAnalyticsTracker | null = null\n let analyticsCompletedAtStart = false\n\n const trackAnalyticsEvent = (type: string, json?: any) => {\n // Don't re-send progression events for a game that was already completed when loaded\n if (analyticsCompletedAtStart) return\n analytics?.trackEvent(type, json)\n }\n\n const internalTimer = createTimer()\n let timerTickInterval: ReturnType<typeof setInterval> | null = null\n let timerSyncInterval: ReturnType<typeof setInterval> | null = null\n\n const startTimerIntervals = () => {\n if (timerTickInterval) return\n\n timerTickInterval = setInterval(() => {\n if (internalTimer.isPaused()) return\n const [elapsed, added] = internalTimer.display()\n hostAPI.sendMessage(\"TIMER_TICK\", { display: [elapsed, added] })\n }, 500)\n\n timerSyncInterval = setInterval(() => {\n if (internalTimer.isPaused()) return\n hostAPI.sendMessage(\"TIMER_SYNC\", Math.floor(internalTimer.timeWithoutPenaltySecs()))\n }, 10000)\n }\n\n const stopTimerIntervals = () => {\n if (timerTickInterval) {\n clearInterval(timerTickInterval)\n timerTickInterval = null\n }\n if (timerSyncInterval) {\n clearInterval(timerSyncInterval)\n timerSyncInterval = null\n }\n }\n\n const emit = <T extends SDKEventType>(event: T, data?: SDKEventMap[T]) => {\n const listeners = eventListeners.get(event)\n if (listeners) listeners.forEach((listener) => listener(data))\n }\n\n hostAPI.onMessage(\"START_GAME\", () => {\n internalTimer._init()\n startTimerIntervals()\n emit(\"start\")\n })\n\n hostAPI.onMessage(\"PAUSE_GAME\", () => {\n internalTimer._pause()\n emit(\"pause\")\n })\n\n hostAPI.onMessage(\"RESUME_GAME\", () => {\n internalTimer._resume()\n emit(\"resume\")\n })\n\n hostAPI.onMessage(\"SETTINGS_UPDATE\", (data) => {\n currentSettings = { ...currentSettings, ...data }\n emit(\"settingsUpdate\", currentSettings)\n })\n\n hostAPI.onMessage(\"KEYBOARD_KEY_PRESS\", (data) => emit(\"keyboardKeyPress\", data))\n hostAPI.onMessage(\"KEYBOARD_CURSOR_CHANGE\", (data) => emit(\"keyboardCursorChange\", data))\n hostAPI.onMessage(\"KEYBOARD_CURSOR_END\", () => emit(\"keyboardCursorEnd\"))\n\n hostAPI.onMessage(\"RETRY_PUZZLE\", () => {\n internalTimer._reset()\n stopTimerIntervals()\n emit(\"retry\")\n })\n\n hostAPI.onMessage(\"READY_DATA\", (data) => {\n const bootstrapData = data as MessagesReceived[\"READY_DATA\"]\n readyData = bootstrapData\n\n if (currentSettings === null) currentSettings = bootstrapData.userState?.gameSettings ?? {}\n\n const gamePlayed = bootstrapData.startOrFindGameplay?.gamePlayed\n if (gamePlayed) {\n const existingTime = (gamePlayed.elapsedTimeSecs ?? 0) * 1000\n const existingAddedTime = (gamePlayed.additionalTimeAddedSecs ?? 0) * 1000\n internalTimer._reset(existingTime, existingAddedTime)\n }\n\n // Wire up analytics once, from the first bootstrap payload\n if (!analytics) {\n analytics = createGameAnalytics(bootstrapData)\n analyticsCompletedAtStart = gamePlayed?.completed ?? false\n // Link clicks are tracked even for already-completed games, matching the runtime\n if (analytics) setupLinkTracking((info) => analytics!.trackLinkClick(info))\n }\n\n if (readyDataResolve) {\n readyDataResolve(bootstrapData)\n readyDataResolve = null\n }\n })\n\n const timer: SDKTimer = {\n timeMs: () => internalTimer.timeMs(),\n timeSecs: () => internalTimer.timeSecs(),\n addedTimeMs: () => internalTimer.addedTimeMs(),\n addedTimeSecs: () => internalTimer.addedTimeSecs(),\n timeWithoutPenaltySecs: () => internalTimer.timeWithoutPenaltySecs(),\n display: () => internalTimer.display(),\n addPenalty: (ms: number) => internalTimer.addPenalty(ms),\n isPaused: () => internalTimer.isPaused(),\n isRunning: () => internalTimer.isRunning(),\n }\n\n // Wire up plugins. Each gets the host transport (open to the full protocol), the timer, and\n // the bootstrap payload, and contributes a namespaced API onto `sdk.plugins[name]`.\n const pluginContext: SDKPluginContext = {\n send: (type, json) => hostAPI.sendMessage(type as keyof SupportedOutgoingMessages, json as never),\n on: (type, handler) => hostAPI.onMessage(type as keyof SupportedIncomingMessages, handler),\n timer,\n bootstrap: () => readyData,\n }\n const pluginAPIs: Record<string, unknown> = {}\n for (const plugin of options.plugins ?? []) pluginAPIs[plugin.name] = plugin.setup(pluginContext)\n\n return {\n timer,\n\n /** APIs contributed by the plugins passed to `createPuzzmoSDK`, keyed by plugin name. */\n plugins: pluginAPIs as PluginAPIs<Plugins>,\n\n gameReady: async (): Promise<{\n puzzleString: string\n inputString: string | null\n theme: Theme | null\n completed: boolean\n readyData: MessagesReceived[\"READY_DATA\"] | null\n }> => {\n hostAPI.sendMessage(\"READY\", {})\n\n if (getPuzzleString()) {\n const inputString = getBoardState()\n return {\n puzzleString: getPuzzleString()!,\n inputString,\n // @ts-expect-error - this is backwards compat\n boardState: inputString,\n theme: getTheme(),\n completed: getCompleted(),\n readyData,\n }\n }\n\n const timeout = options.timeout ?? 5000\n const readyDataPromise = new Promise<MessagesReceived[\"READY_DATA\"]>((resolve, reject) => {\n readyDataResolve = resolve\n setTimeout(() => {\n if (readyDataResolve) {\n readyDataResolve = null\n reject(new Error(`Timeout waiting for READY_DATA after ${timeout}ms`))\n }\n }, timeout)\n })\n\n await readyDataPromise\n\n const puzzleString = getPuzzleString()\n if (!puzzleString) throw new Error(\"READY_DATA received but no puzzle data found\")\n\n const inputString = getBoardState()\n return {\n puzzleString,\n inputString,\n // @ts-expect-error - this is backwards compat\n boardState: inputString,\n theme: getTheme(),\n completed: getCompleted(),\n readyData,\n }\n },\n\n gameLoaded: (state: any = {}) => {\n hostAPI.sendMessage(\"READY_GAME_LOADED\", {\n state,\n gameRuntimeContract: \"1.0\",\n embedRuntimeContract: \"1.0\",\n })\n trackAnalyticsEvent(\"READY_GAME_LOADED\")\n },\n\n on: <T extends SDKEventType>(event: T, listener: (data?: SDKEventMap[T]) => void): (() => void) => {\n if (!eventListeners.has(event)) eventListeners.set(event, new Set())\n eventListeners.get(event)!.add(listener)\n return () => {\n eventListeners.get(event)?.delete(listener)\n }\n },\n\n off: <T extends SDKEventType>(event: T, listener: (data?: SDKEventMap[T]) => void) => {\n eventListeners.get(event)?.delete(listener)\n },\n\n updateGameState: (inputString: string, play?: Partial<GamePlay>) => {\n const gameplayID = getGameplayID()\n if (!gameplayID) return\n\n hostAPI.sendMessage(\"UPLOAD_NEW_GAME_STATE\", {\n id: gameplayID,\n input: {\n boardState: inputString,\n elapsedTimeSecs: play?.elapsedTimeSecs ?? internalTimer.timeWithoutPenaltySecs(),\n additionalTimeAddedSecs: play?.additionalTimeAddedSecs ?? internalTimer.addedTimeSecs(),\n collabUserReferences: [],\n },\n })\n trackAnalyticsEvent(\"UPLOAD_NEW_GAME_STATE\")\n },\n\n gameCompleted: (\n play: Omit<GamePlay, \"elapsedTimeSecs\" | \"additionalTimeAddedSecs\"> &\n Partial<Pick<GamePlay, \"elapsedTimeSecs\" | \"additionalTimeAddedSecs\">>,\n config?: AugmentationConfig,\n ) => {\n internalTimer._conclude()\n stopTimerIntervals()\n\n const finalPlay: Partial<GamePlay> = {\n ...play,\n elapsedTimeSecs: play.elapsedTimeSecs ?? internalTimer.timeWithoutPenaltySecs(),\n additionalTimeAddedSecs: play.additionalTimeAddedSecs ?? internalTimer.addedTimeSecs(),\n }\n\n const deeds: Deed[] = (config?.deeds as any) ?? []\n deeds.push({ id: \"points\", value: play.pointsAwarded })\n deeds.push({\n id: \"time\",\n value: Math.round(finalPlay.elapsedTimeSecs ?? 0) + Math.round(finalPlay.additionalTimeAddedSecs ?? 0),\n })\n\n const gameplayID = getGameplayID()\n if (gameplayID) {\n hostAPI.sendMessage(\"GAME_COMPLETED\", {\n id: gameplayID,\n input: finalPlay,\n config,\n })\n trackAnalyticsEvent(\"GAME_COMPLETED\", { input: finalPlay })\n }\n },\n\n showCompletionScreen: (results: any[], gameplay: GamePlay, showRetry = true) => {\n hostAPI.sendMessage(\"SHOW_GAME_COMPLETE_SCREEN\", {\n results,\n showRetry,\n gameplay,\n })\n },\n\n hitCheckpoint: (checkpointName: string, checkpointConfig: CheckpointConfig, config?: AugmentationConfig) => {\n const gameplayID = getGameplayID()\n if (!gameplayID) return\n\n const inputStr = getBoardState() ?? \"\"\n const play: Partial<GamePlay> = {\n elapsedTimeSecs: internalTimer.timeWithoutPenaltySecs(),\n additionalTimeAddedSecs: internalTimer.addedTimeSecs(),\n }\n\n hostAPI.sendMessage(\"HIT_CHECKPOINT\", {\n checkpointName,\n gameplay: { inputStr, play },\n checkpointConfig,\n augConfig: config ?? {},\n })\n },\n\n settings: {\n /**\n * Register the game's settings UI with the host. Component `defaultValue`s are merged\n * underneath the player's saved values, the host shows the components in its settings panel,\n * and the resolved settings object is returned. Listen for the `settingsUpdate` event to\n * react to the player changing values. Call after `gameReady()`.\n */\n initialize: (components: GameSettingsUIComponents[]) => {\n currentSettings = { ...settingsDefaultsFromComponents(components), ...currentSettings }\n hostAPI.sendMessage(\"INITIALIZE_SETTINGS\", { components, settings: currentSettings })\n return currentSettings\n },\n /** Get the current resolved settings object. */\n get: () => currentSettings ?? {},\n /** Merge changes into the current settings and persist them to the host. Returns the updated settings. */\n update: (changes: any) => {\n currentSettings = { ...currentSettings, ...changes }\n hostAPI.sendMessage(\"UPDATE_SETTINGS_FROM_EMBED\", { settings: currentSettings })\n return currentSettings\n },\n },\n\n keyboard: {\n /** Show the on-screen keyboard with the given config. Call again to update state (e.g. to change disabled keys). */\n show: (config: KeyboardConfig) => {\n hostAPI.sendMessage(\"KEYBOARD_UPDATE_CONFIG\", config)\n },\n /** Hide the on-screen keyboard. */\n hide: () => {\n hostAPI.sendMessage(\"KEYBOARD_UPDATE_CONFIG\", {\n layout: [],\n symbols: {},\n highlight: [],\n disabled: [],\n xl: [],\n l: [],\n supportsDragCursor: false,\n })\n },\n },\n\n _hostAPI: hostAPI,\n }\n}\n\n/** Walks settings UI components (recursing into split groups) collecting name → defaultValue pairs */\nfunction settingsDefaultsFromComponents(components: GameSettingsUIComponents[]): Record<string, unknown> {\n const defaults: Record<string, unknown> = {}\n for (const component of components) {\n if (component.type === \"split\") Object.assign(defaults, settingsDefaultsFromComponents(component.content))\n else if (\"name\" in component) defaults[component.name] = component.defaultValue\n }\n return defaults\n}\n","import type { KeyboardConfig } from \"./types\"\n\n/**\n * A standard QWERTY layout with Enter and Backspace — a reasonable default for\n * any game that needs text input. Customize from here by spreading and overriding.\n *\n * @example\n * // Use as-is\n * sdk.keyboard.show(defaultKeyboardConfig)\n *\n * @example\n * // Extend with dynamic disabled letters\n * sdk.keyboard.show({ ...defaultKeyboardConfig, disabled: usedLetters })\n */\nexport const defaultKeyboardConfig: KeyboardConfig = {\n layout: [\"qwertyuiop\", \"asdfghjkl\", \"↵zxcvbnm⌫\", undefined],\n symbols: { \"↵\": \"Enter\", \"⌫\": \"bsp\" },\n highlight: [\"↵\", \"⌫\"],\n disabled: [],\n xl: [],\n l: [\"↵\", \"⌫\"],\n supportsDragCursor: false,\n}\n","// For now, we will keep these types in here but not mark them as something which should appear in the public SDK docs\n\nimport type { Theme } from \"./types\"\n\n/**\n * The theme passed to an editor bundle. The host sends its full {@link Theme} so editors derive\n * their colors from it rather than making their own theme decisions. Use `theme.type` for\n * light/dark bucketing.\n *\n * The bare `\"light\" | \"dark\"` string form is deprecated and will be removed in a future SDK\n * release — editors should handle the full `Theme` object.\n */\nexport type EditorTheme = Theme | \"light\" | \"dark\"\n\n/** Severity level for validation issues */\nexport type ValidationLevel = \"error\" | \"warning\" | \"info\"\n\n/** Represents a single validation issue found during puzzle validation */\nexport interface ValidationIssue {\n level: ValidationLevel\n message: string\n line?: number\n col?: number\n length?: number\n}\n\n/** Complete validation report for a puzzle */\nexport interface ValidationReport {\n success: boolean\n issues: ValidationIssue[]\n}\n\nexport type ImportErrorType = \"invalid_format\" | \"parsing_error\" | \"unknown\"\n\n/** Custom error class for workshop import failures */\nexport class EditorImportError extends Error {\n constructor(\n public type: ImportErrorType,\n message: string,\n public originalError?: unknown,\n ) {\n super(message)\n this.name = \"EditorImportError\"\n }\n}\n\n/** Result of a successful puzzle import operation */\nexport interface ImportResult {\n data: string\n warnings?: ValidationIssue[]\n title?: string\n authors?: string[]\n editors?: string[]\n}\n\n/** Settings UI descriptor returned by an editor bundle */\nexport interface EditorBundleSettings<TComponent = unknown> {\n components: TComponent[]\n defaults: Record<string, unknown>\n}\n\n/** A preceding/following phrase for a word, returned by the getRelatedWords editor callback */\nexport interface RelatedWord {\n word: string\n frequency: number\n position: \"preceding\" | \"following\"\n}\n\n/** Result of fetching a URL from within the editor */\nexport interface EditorFetchURLResult {\n status: number\n body: string\n}\n\n/** Config passed to an editor bundle's mount() */\nexport interface EditorMountConfig {\n puzzleString: string\n onChange: (puzzleString: string) => void\n theme: EditorTheme\n width: number\n height: number\n /**\n * Optional function for making chat completions from the editor (e.g. for AI-assisted puzzle\n * editing). This needs to be enabled for a team explicitly.\n */\n chatCompletion?: (prompt: string) => Promise<string>\n /**\n * Optional function for fetching URLs from the editor (e.g. for fetching article content).\n * This needs to be enabled for a team explicitly.\n */\n fetchURL?: (url: string) => Promise<EditorFetchURLResult>\n /** Optional function for looking up a word's preceding/following phrases from wordvault. */\n getRelatedWords?: (word: string, limit?: number) => Promise<RelatedWord[]>\n /** Pre-configured editor settings values from the queue's editorSettings. */\n settings?: Record<string, unknown>\n}\n\n/** Handle returned by an editor bundle's mount() */\nexport interface EditorMountHandle {\n unmount: () => void\n /**\n * Called whenever the puzzle string is updated in the editor from the outside, or when the theme\n * or dimensions update. Workshop will rely on the validator to reject / accept updates.\n */\n update: (config: { puzzleString?: string; theme?: EditorTheme; width?: number; height?: number }) => void\n}\n\n/** Main interface for a Workshop editor bundle */\nexport interface EditorBundle<TSettingsComponent = unknown> {\n /** Required validator for puzzle data validation */\n validator: {\n validate(data: string): Promise<ValidationReport> | ValidationReport\n }\n /** Optional importer for converting external puzzle file formats */\n importer?: {\n onImport(filename: string, contents: string | ArrayBuffer): Promise<ImportResult> | ImportResult\n }\n /** Embed-level settings UI, populated from the bundle's declared settings */\n settings?: EditorBundleSettings<TSettingsComponent>\n /** Editor-level settings UI, populated from the bundle's declared editor settings */\n editorSettings?: EditorBundleSettings<TSettingsComponent>\n /** Custom puzzle editor, if provided by the bundle */\n editor?: {\n /** Called when first visiting a puzzle page. */\n mount(element: HTMLElement, config: EditorMountConfig): Promise<EditorMountHandle>\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,UAAU,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugins.spec.d.ts","sourceRoot":"","sources":["../src/plugins.spec.ts"],"names":[],"mappings":""}
|
package/dist/sdk.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import type { PluginAPIs, SDKPlugin } from "./plugins";
|
|
1
2
|
import type { MessagesSentFromEmbed, MessagesReceived, GamePlay, AugmentationConfig, CheckpointConfig, Theme, KeyboardConfig, GameSettingsUIComponents } from "./types";
|
|
2
3
|
export type SDK = ReturnType<typeof createPuzzmoSDK>;
|
|
3
|
-
export interface PuzzmoSDKOptions {
|
|
4
|
+
export interface PuzzmoSDKOptions<Plugins extends readonly SDKPlugin[] = []> {
|
|
4
5
|
/** Optional timeout in ms to wait for READY_DATA (default: 5000) */
|
|
5
6
|
timeout?: number;
|
|
7
|
+
/** Plugins to extend the SDK with; each contributes a namespaced API at `sdk.plugins[name]`. */
|
|
8
|
+
plugins?: Plugins;
|
|
6
9
|
}
|
|
7
10
|
type SupportedOutgoingMessages = Pick<MessagesSentFromEmbed, "READY" | "READY_GAME_LOADED" | "GAME_COMPLETED" | "SHOW_GAME_COMPLETE_SCREEN" | "TIMER_TICK" | "TIMER_SYNC" | "UPLOAD_NEW_GAME_STATE" | "HIT_CHECKPOINT" | "KEYBOARD_UPDATE_CONFIG" | "INITIALIZE_SETTINGS" | "UPDATE_SETTINGS_FROM_EMBED">;
|
|
8
11
|
type SupportedIncomingMessages = Pick<MessagesReceived, "READY_DATA" | "START_GAME" | "PAUSE_GAME" | "RESUME_GAME" | "SETTINGS_UPDATE" | "RETRY_PUZZLE" | "KEYBOARD_KEY_PRESS" | "KEYBOARD_CURSOR_CHANGE" | "KEYBOARD_CURSOR_END">;
|
|
@@ -47,8 +50,10 @@ export interface SDKTimer {
|
|
|
47
50
|
isRunning: () => boolean;
|
|
48
51
|
}
|
|
49
52
|
/** Creates a Puzzmo SDK instance for communicating with the Puzzmo host */
|
|
50
|
-
export declare const createPuzzmoSDK: (options?: PuzzmoSDKOptions) => {
|
|
53
|
+
export declare const createPuzzmoSDK: <const Plugins extends readonly SDKPlugin<string, unknown>[] = []>(options?: PuzzmoSDKOptions<Plugins>) => {
|
|
51
54
|
timer: SDKTimer;
|
|
55
|
+
/** APIs contributed by the plugins passed to `createPuzzmoSDK`, keyed by plugin name. */
|
|
56
|
+
plugins: PluginAPIs<Plugins>;
|
|
52
57
|
gameReady: () => Promise<{
|
|
53
58
|
puzzleString: string;
|
|
54
59
|
inputString: string | null;
|
package/dist/sdk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,gBAAgB,EAChB,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,EAEL,cAAc,EACd,wBAAwB,EACzB,MAAM,SAAS,CAAA;AAEhB,MAAM,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAA;AAEpD,MAAM,WAAW,gBAAgB;
|
|
1
|
+
{"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAoB,MAAM,WAAW,CAAA;AACxE,OAAO,KAAK,EACV,qBAAqB,EACrB,gBAAgB,EAChB,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,EAEL,cAAc,EACd,wBAAwB,EACzB,MAAM,SAAS,CAAA;AAEhB,MAAM,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAA;AAEpD,MAAM,WAAW,gBAAgB,CAAC,OAAO,SAAS,SAAS,SAAS,EAAE,GAAG,EAAE;IACzE,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gGAAgG;IAChG,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,KAAK,yBAAyB,GAAG,IAAI,CACnC,qBAAqB,EACnB,OAAO,GACP,mBAAmB,GACnB,gBAAgB,GAChB,2BAA2B,GAC3B,YAAY,GACZ,YAAY,GACZ,uBAAuB,GACvB,gBAAgB,GAChB,wBAAwB,GACxB,qBAAqB,GACrB,4BAA4B,CAC/B,CAAA;AAED,KAAK,yBAAyB,GAAG,IAAI,CACnC,gBAAgB,EACd,YAAY,GACZ,YAAY,GACZ,YAAY,GACZ,aAAa,GACb,iBAAiB,GACjB,cAAc,GACd,oBAAoB,GACpB,wBAAwB,GACxB,qBAAqB,CACxB,CAAA;AAED,KAAK,cAAc,CAAC,CAAC,SAAS,MAAM,yBAAyB,IAAI,CAAC,IAAI,EAAE,yBAAyB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;AAE7G,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,IAAI,CAAA;IACX,KAAK,EAAE,IAAI,CAAA;IACX,MAAM,EAAE,IAAI,CAAA;IACZ,KAAK,EAAE,IAAI,CAAA;IACX,qGAAqG;IACrG,cAAc,EAAE,GAAG,CAAA;IACnB,kDAAkD;IAClD,gBAAgB,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACjC,+FAA+F;IAC/F,oBAAoB,EAAE;QAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAA;IACpD,kFAAkF;IAClF,iBAAiB,EAAE,IAAI,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,WAAW,CAAA;AAE5C,MAAM,WAAW,QAAQ;IACvB,uCAAuC;IACvC,MAAM,EAAE,MAAM,MAAM,CAAA;IACpB,kCAAkC;IAClC,QAAQ,EAAE,MAAM,MAAM,CAAA;IACtB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,MAAM,CAAA;IACzB,wCAAwC;IACxC,aAAa,EAAE,MAAM,MAAM,CAAA;IAC3B,oDAAoD;IACpD,sBAAsB,EAAE,MAAM,MAAM,CAAA;IACpC,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,uCAAuC;IACvC,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,OAAO,CAAA;IACvB,sCAAsC;IACtC,SAAS,EAAE,MAAM,OAAO,CAAA;CACzB;AAyID,2EAA2E;AAC3E,eAAO,MAAM,eAAe,SAAU,OAAO;;IAmJzC,yFAAyF;;;;;;;;;;SA8DpF,CAAC;UAQA,CAAC;;;;;;QA+EL;;;;;WAKG;;QAMH,gDAAgD;;QAEhD,0GAA0G;;;;QAS1G,oHAAoH;;QAIpH,mCAAmC;;;;sBA/WlB,CAAC;oBAiBH,CAAC;;CA8WrB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSimulator.d.ts","sourceRoot":"","sources":["../../src/simulator/createSimulator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAA4D,cAAc,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"createSimulator.d.ts","sourceRoot":"","sources":["../../src/simulator/createSimulator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAA4D,cAAc,EAAE,MAAM,SAAS,CAAA;AAqBxH,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,CAAA;AAG/C,UAAU,iBAAiB;IACzB,cAAc,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAA;IAClD,UAAU,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAA;IAC1C,UAAU,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAA;CAC/B;AAGD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,wBAAgB,eAAe,CAAC,MAAM,GAAE,eAAoB,GAAG,iBAAiB,CA0gB/E"}
|
package/dist/simulator/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../createSimulator-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`../createSimulator-EBKHk3L6.cjs`);exports.createSimulator=e.t;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { createSimulator } from "./createSimulator";
|
|
2
|
-
export type { SimulatorConfig, FixtureImports } from "./types";
|
|
2
|
+
export type { SimulatorConfig, FixtureImports, SimulatorView, SimulatorContext, HostContextPreset } from "./types";
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/simulator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/simulator/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA"}
|
package/dist/simulator/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as e } from "../createSimulator-
|
|
1
|
+
import { t as e } from "../createSimulator-Bo6J_KC_.js";
|
|
2
2
|
export { e as createSimulator };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require(`../createSimulator-
|
|
1
|
+
const e=require(`../createSimulator-EBKHk3L6.cjs`);var t=()=>{var t;e.t((t=window.SIMULATOR_CONFIG)==null?{}:t)};document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,t):t();
|
|
2
2
|
//# sourceMappingURL=standalone.cjs.map
|