@puzzmo/sdk 1.0.23 → 1.0.24

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/editor.d.ts CHANGED
@@ -33,11 +33,60 @@ export interface EditorBundleSettings<TComponent = unknown> {
33
33
  components: TComponent[];
34
34
  defaults: Record<string, unknown>;
35
35
  }
36
- /** Main interface for a Workshop bundle */
36
+ /** A preceding/following phrase for a word, returned by the getRelatedWords editor callback */
37
+ export interface RelatedWord {
38
+ word: string;
39
+ frequency: number;
40
+ position: "preceding" | "following";
41
+ }
42
+ /** Result of fetching a URL from within the editor */
43
+ export interface EditorFetchURLResult {
44
+ status: number;
45
+ body: string;
46
+ }
47
+ /** Config passed to an editor bundle's mount() */
48
+ export interface EditorMountConfig {
49
+ puzzleString: string;
50
+ onChange: (puzzleString: string) => void;
51
+ theme: "light" | "dark";
52
+ width: number;
53
+ height: number;
54
+ /**
55
+ * Optional function for making chat completions from the editor (e.g. for AI-assisted puzzle
56
+ * editing). This needs to be enabled for a team explicitly.
57
+ */
58
+ chatCompletion?: (prompt: string) => Promise<string>;
59
+ /**
60
+ * Optional function for fetching URLs from the editor (e.g. for fetching article content).
61
+ * This needs to be enabled for a team explicitly.
62
+ */
63
+ fetchURL?: (url: string) => Promise<EditorFetchURLResult>;
64
+ /** Optional function for looking up a word's preceding/following phrases from wordvault. */
65
+ getRelatedWords?: (word: string, limit?: number) => Promise<RelatedWord[]>;
66
+ /** Pre-configured editor settings values from the queue's editorSettings. */
67
+ settings?: Record<string, unknown>;
68
+ }
69
+ /** Handle returned by an editor bundle's mount() */
70
+ export interface EditorMountHandle {
71
+ unmount: () => void;
72
+ /**
73
+ * Called whenever the puzzle string is updated in the editor from the outside, or when the theme
74
+ * or dimensions update. Workshop will rely on the validator to reject / accept updates.
75
+ */
76
+ update: (config: {
77
+ puzzleString?: string;
78
+ theme?: "light" | "dark";
79
+ width?: number;
80
+ height?: number;
81
+ }) => void;
82
+ }
83
+ /** Main interface for a Workshop editor bundle */
37
84
  export interface EditorBundle<TSettingsComponent = unknown> {
85
+ /** Required validator for puzzle data validation */
38
86
  validator: {
39
87
  validate(data: string): Promise<ValidationReport> | ValidationReport;
40
88
  };
89
+ /** Optional importer for converting external puzzle file formats */
41
90
  importer?: {
42
91
  onImport(filename: string, contents: string | ArrayBuffer): Promise<ImportResult> | ImportResult;
43
92
  };
@@ -47,7 +96,8 @@ export interface EditorBundle<TSettingsComponent = unknown> {
47
96
  editorSettings?: EditorBundleSettings<TSettingsComponent>;
48
97
  /** Custom puzzle editor, if provided by the bundle */
49
98
  editor?: {
50
- mount(...args: unknown[]): unknown;
99
+ /** Called when first visiting a puzzle page. */
100
+ mount(element: HTMLElement, config: EditorMountConfig): Promise<EditorMountHandle>;
51
101
  };
52
102
  }
53
103
  //# sourceMappingURL=editor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAA;AAE1D,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,eAAe,CAAA;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,eAAe,EAAE,CAAA;CAC1B;AAED,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG,eAAe,GAAG,SAAS,CAAA;AAE5E,sDAAsD;AACtD,qBAAa,iBAAkB,SAAQ,KAAK;IAEjC,IAAI,EAAE,eAAe;IAErB,aAAa,CAAC;IAHvB,YACS,IAAI,EAAE,eAAe,EAC5B,OAAO,EAAE,MAAM,EACR,aAAa,CAAC,SAAS,EAI/B;CACF;AAED,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAA;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,0DAA0D;AAC1D,MAAM,WAAW,oBAAoB,CAAC,UAAU,GAAG,OAAO;IACxD,UAAU,EAAE,UAAU,EAAE,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC;AAED,2CAA2C;AAC3C,MAAM,WAAW,YAAY,CAAC,kBAAkB,GAAG,OAAO;IACxD,SAAS,EAAE;QACT,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAA;KACrE,CAAA;IACD,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAA;KACjG,CAAA;IACD,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,CAAA;IACnD,qFAAqF;IACrF,cAAc,CAAC,EAAE,oBAAoB,CAAC,kBAAkB,CAAC,CAAA;IACzD,sDAAsD;IACtD,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAA;KACnC,CAAA;CACF"}
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/sdk.ts","../src/keyboard.ts","../src/editor.ts"],"sourcesContent":["import type {\n MessagesSentFromEmbed,\n MessagesReceived,\n GamePlay,\n AugmentationConfig,\n CheckpointConfig,\n Theme,\n Deed,\n KeyboardConfig,\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>\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 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 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) => emit(\"settingsUpdate\", data))\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 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 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 },\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 },\n\n gameCompleted: (play: Partial<GamePlay>, config?: AugmentationConfig) => {\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 }\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 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","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","/** 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/** Main interface for a Workshop bundle */\nexport interface EditorBundle<TSettingsComponent = unknown> {\n validator: {\n validate(data: string): Promise<ValidationReport> | ValidationReport\n }\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 mount(...args: unknown[]): unknown\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/sdk.ts","../src/keyboard.ts","../src/editor.ts"],"sourcesContent":["import type {\n MessagesSentFromEmbed,\n MessagesReceived,\n GamePlay,\n AugmentationConfig,\n CheckpointConfig,\n Theme,\n Deed,\n KeyboardConfig,\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>\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 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 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) => emit(\"settingsUpdate\", data))\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 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 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 },\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 },\n\n gameCompleted: (play: Partial<GamePlay>, config?: AugmentationConfig) => {\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 }\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 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","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","/** 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: \"light\" | \"dark\"\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?: \"light\" | \"dark\"; 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":"gKAmFA,SAAS,EAAW,EAAwB,CAC1C,IAAM,EAAiB,GAAU,KAAU,IAC3C,OAAO,IAAI,KAAK,EAAO,CACpB,aAAa,CACb,MAAM,EAAiB,GAAK,GAAI,GAAG,CACnC,MAAM,IAAI,CAAC,GAGhB,SAAS,EACP,EAAgB,EAChB,EAAqB,EAOrB,CACA,IAAI,EAAW,EACX,EAAY,EACZ,EAAa,EACb,EAEA,EACA,EAEE,MAAwB,OAC5B,GAAI,IAAc,IAAA,GAAW,OAAO,EAAW,EAC/C,GAAI,IAAiB,IAAA,GAAW,OAAO,EAEvC,IAAM,IAAA,EADM,IAAA,KAAc,YAAY,KAAK,CAA/B,GACU,EAAY,EAClC,OAAO,EAAW,EAAY,GAGhC,MAAO,CACL,UAAa,CACP,IAAc,IAAA,KAChB,EAAY,YAAY,KAAK,CAC7B,EAAa,IAAA,KAGjB,WAAc,CACR,IAAe,IAAA,IAAa,IAAc,IAAA,KAC9C,EAAa,YAAY,KAAK,GAEhC,YAAe,CACT,IAAe,IAAA,KACnB,GAAc,YAAY,KAAK,CAAG,EAClC,EAAa,IAAA,KAEf,QAAS,EAAmB,EAAG,EAAwB,IAAM,CAC3D,EAAW,EACX,EAAY,EACZ,EAAa,EACb,EAAe,IAAA,GACf,EAAY,IAAA,GACZ,EAAa,IAAA,IAEf,cAAiB,CACf,GAAI,IAAe,IAAA,GAAW,CAC5B,EAAe,GAAS,CACxB,OAEF,GAAI,IAAc,IAAA,GAAW,CAC3B,EAAe,EAAW,EAC1B,OAEF,EAAe,GAAS,EAE1B,WAAc,GAAS,CACvB,aAAgB,GAAS,CAAG,IAC5B,gBAAmB,EACnB,kBAAqB,EAAY,IACjC,4BAA+B,GAAS,CAAG,GAAa,IACxD,WAAa,GAAe,CAC1B,GAAa,GAEf,aAAgB,IAAe,IAAA,IAAa,IAAc,IAAA,GAC1D,cAAiB,IAAc,IAAA,IAAa,IAAe,IAAA,GAC3D,YAAe,CACb,IAAM,EAAU,GAAS,CAAG,EAG5B,MAAO,CAFY,EAAW,KAAK,IAAI,EAAG,EAAQ,CAAC,CAClC,IAAc,EAAI,GAAK,EAAW,EAAU,CAChC,EAEhC,CAGH,SAAS,GAAgB,CACvB,IAAM,EAAkB,IAAI,IAyC5B,OAbI,OAAO,OAAW,KACpB,OAAO,iBAAiB,UAAY,GAAU,OAC5C,GAAI,EAAA,KAAA,OAAA,EAAC,EAAO,OAAA,OAAA,EAAM,MAAM,OACxB,IAAM,EAAU,EAAM,KAAK,KACrB,EAAW,EAAgB,IAAI,EAAQ,CAC7C,GAAI,EAAU,SACZ,IAAM,GAAA,GAAA,EAAU,EAAM,KAAK,OAAA,KAAQ,EAAM,KAAK,KAAnB,IAAmB,KAAQ,EAAE,CAAV,EAC1C,IAAY,cAAgB,IAAY,cAAc,QAAQ,IAAI,yBAA0B,EAAS,EAAQ,CACjH,EAAS,QAAS,GAAY,EAAQ,EAAQ,CAAC,GAEjD,CAGG,CAAE,aAvCuD,EAAS,IAAuC,SAC9G,IAAM,EAAU,CAAE,OAAM,OAAM,EAAG,IAAK,GAAI,KAAM,QAAS,GAAM,CAE3D,WAAY,QAAU,OAAO,SAAW,QAAQ,OAAO,OAAO,YAAY,EAAS,IAAI,CAE3F,OAAO,YAAY,EAAS,IAAI,CAE5B,WAAY,QAAA,GAAA,EAAW,OAAe,SAAA,OAAA,EAAA,EAAQ,kBAAA,OAAA,EAAiB,KAAM,OAAe,OAAO,gBAAgB,IAAI,YAAY,EAAQ,CAEnI,wBAAyB,QAAS,OAAe,oBAAoB,KAAK,UAAU,EAAQ,CAAC,CAE7F,uBAAwB,SAAA,EAAW,OAAe,qBAAA,MAAA,EAAoB,aACvE,OAAe,mBAAmB,YAAY,KAAK,UAAU,EAAQ,CAAC,CAErE,IAAS,cAAgB,IAAS,cAAc,QAAQ,IAAI,qBAAsB,EAAM,EAAK,EAyB7E,WAtBwC,EAAS,KAChE,EAAgB,IAAI,EAAK,EAAE,EAAgB,IAAI,EAAM,IAAI,IAAM,CACpE,EAAgB,IAAI,EAAK,CAAE,IAAI,EAAQ,KAE1B,QACX,EAAA,EAAgB,IAAI,EAAK,GAAA,MAAA,EAAE,OAAO,EAAQ,GAiBb,CAGnC,IAAM,EAAU,GAAe,CAG/B,MAAa,GAAmB,EAA4B,EAAE,GAAK,CACjE,IAAI,EAAmD,KACnD,EAA4E,KAE1E,MAAoB,4BAAW,sBAAA,KAAA,IAAA,GAAA,EAAqB,YACpD,MAAsB,wBAAa,GAAA,KAAA,IAAA,GAAA,EAAE,KAAA,KAAM,KAAN,GACrC,MAAwB,wBAAa,GAAA,KAAA,IAAA,GAAA,EAAE,OAAO,SAAA,KAAU,KAAV,GAC9C,MAAsB,wBAAa,GAAA,KAAA,IAAA,GAAA,EAAE,aAAA,KAAc,KAAd,GACrC,MAAiB,iCAAW,QAAA,KAAS,KAAT,GAC5B,MAAqB,wBAAa,GAAA,KAAA,IAAA,GAAA,EAAE,YAAA,KAAa,GAAb,GAEpC,EAAiB,IAAI,IAErB,EAAgB,GAAa,CAC/B,EAA2D,KAC3D,EAA2D,KAEzD,MAA4B,CAC5B,IAEJ,EAAoB,gBAAkB,CACpC,GAAI,EAAc,UAAU,CAAE,OAC9B,GAAM,CAAC,EAAS,GAAS,EAAc,SAAS,CAChD,EAAQ,YAAY,aAAc,CAAE,QAAS,CAAC,EAAS,EAAM,CAAE,CAAC,EAC/D,IAAI,CAEP,EAAoB,gBAAkB,CAChC,EAAc,UAAU,EAC5B,EAAQ,YAAY,aAAc,KAAK,MAAM,EAAc,wBAAwB,CAAC,CAAC,EACpF,IAAM,GAGL,MAA2B,CAC3B,IACF,cAAc,EAAkB,CAChC,EAAoB,MAElB,IACF,cAAc,EAAkB,CAChC,EAAoB,OAIlB,GAAgC,EAAU,IAA0B,CACxE,IAAM,EAAY,EAAe,IAAI,EAAM,CACvC,GAAW,EAAU,QAAS,GAAa,EAAS,EAAK,CAAC,EA4DhE,OAzDA,EAAQ,UAAU,iBAAoB,CACpC,EAAc,OAAO,CACrB,GAAqB,CACrB,EAAK,QAAQ,EACb,CAEF,EAAQ,UAAU,iBAAoB,CACpC,EAAc,QAAQ,CACtB,EAAK,QAAQ,EACb,CAEF,EAAQ,UAAU,kBAAqB,CACrC,EAAc,SAAS,CACvB,EAAK,SAAS,EACd,CAEF,EAAQ,UAAU,kBAAoB,GAAS,EAAK,iBAAkB,EAAK,CAAC,CAE5E,EAAQ,UAAU,qBAAuB,GAAS,EAAK,mBAAoB,EAAK,CAAC,CACjF,EAAQ,UAAU,yBAA2B,GAAS,EAAK,uBAAwB,EAAK,CAAC,CACzF,EAAQ,UAAU,0BAA6B,EAAK,oBAAoB,CAAC,CAEzE,EAAQ,UAAU,mBAAsB,CACtC,EAAc,QAAQ,CACtB,GAAoB,CACpB,EAAK,QAAQ,EACb,CAEF,EAAQ,UAAU,aAAe,GAAS,OACxC,IAAM,EAAgB,EACtB,EAAY,EAEZ,IAAM,GAAA,EAAa,EAAc,sBAAA,KAAA,IAAA,GAAA,EAAqB,WACtD,GAAI,EAAY,SACd,IAAM,IAAA,EAAgB,EAAW,kBAAA,KAAmB,EAAnB,GAAwB,IACnD,IAAA,EAAqB,EAAW,0BAAA,KAA2B,EAA3B,GAAgC,IACtE,EAAc,OAAO,EAAc,EAAkB,CAGnD,IACF,EAAiB,EAAc,CAC/B,EAAmB,OAErB,CAcK,CACL,MAbsB,CACtB,WAAc,EAAc,QAAQ,CACpC,aAAgB,EAAc,UAAU,CACxC,gBAAmB,EAAc,aAAa,CAC9C,kBAAqB,EAAc,eAAe,CAClD,2BAA8B,EAAc,wBAAwB,CACpE,YAAe,EAAc,SAAS,CACtC,WAAa,GAAe,EAAc,WAAW,EAAG,CACxD,aAAgB,EAAc,UAAU,CACxC,cAAiB,EAAc,WAAW,CAC3C,CAKC,UAAA,UAAA,sBAMM,OAGJ,GAFA,EAAQ,YAAY,QAAS,EAAE,CAAC,CAE5B,GAAiB,CAAE,CACrB,IAAM,EAAc,GAAe,CACnC,MAAO,CACL,aAAc,GAAiB,CAC/B,cAEA,WAAY,EACZ,MAAO,GAAU,CACjB,UAAW,GAAc,CACzB,YACD,CAGH,IAAM,GAAA,EAAU,EAAQ,UAAA,KAAW,IAAX,EAWxB,MAVyB,IAAI,SAAyC,EAAS,IAAW,CACxF,EAAmB,EACnB,eAAiB,CACX,IACF,EAAmB,KACnB,EAAW,MAAM,wCAAwC,EAAQ,IAAI,CAAC,GAEvE,EAAQ,EACX,CAIF,IAAM,EAAe,GAAiB,CACtC,GAAI,CAAC,EAAc,MAAU,MAAM,+CAA+C,CAElF,IAAM,EAAc,GAAe,CACnC,MAAO,CACL,eACA,cAEA,WAAY,EACZ,MAAO,GAAU,CACjB,UAAW,GAAc,CACzB,YACD,wDAGH,YAAa,EAAa,EAAE,GAAK,CAC/B,EAAQ,YAAY,oBAAqB,CACvC,QACA,oBAAqB,MACrB,qBAAsB,MACvB,CAAC,EAGJ,IAA6B,EAAU,KAChC,EAAe,IAAI,EAAM,EAAE,EAAe,IAAI,EAAO,IAAI,IAAM,CACpE,EAAe,IAAI,EAAM,CAAE,IAAI,EAAS,KAC3B,QACX,EAAA,EAAe,IAAI,EAAM,GAAA,MAAA,EAAE,OAAO,EAAS,GAI/C,KAA8B,EAAU,IAA8C,QACpF,EAAA,EAAe,IAAI,EAAM,GAAA,MAAA,EAAE,OAAO,EAAS,EAG7C,iBAAkB,EAAqB,IAA6B,SAClE,IAAM,EAAa,GAAe,CAC7B,GAEL,EAAQ,YAAY,wBAAyB,CAC3C,GAAI,EACJ,MAAO,CACL,WAAY,EACZ,iBAAA,EAAA,GAAA,KAAA,IAAA,GAAiB,EAAM,kBAAA,KAAmB,EAAc,wBAAwB,CAAzD,EACvB,yBAAA,EAAA,GAAA,KAAA,IAAA,GAAyB,EAAM,0BAAA,KAA2B,EAAc,eAAe,CAAxD,EAC/B,qBAAsB,EAAE,CACzB,CACF,CAAC,EAGJ,eAAgB,EAAyB,IAAgC,eACvE,EAAc,WAAW,CACzB,GAAoB,CAEpB,IAAM,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,CACD,EAAA,CAAA,EAAA,CAAA,CACH,iBAAA,EAAiB,EAAK,kBAAA,KAAmB,EAAc,wBAAwB,CAAzD,EACtB,yBAAA,EAAyB,EAAK,0BAAA,KAA2B,EAAc,eAAe,CAAxD,GAC/B,CAEK,GAAA,EAAA,GAAA,KAAA,IAAA,GAAiB,EAAQ,QAAA,KAAiB,EAAE,CAAnB,EAC/B,EAAM,KAAK,CAAE,GAAI,SAAU,MAAO,EAAK,cAAe,CAAC,CACvD,EAAM,KAAK,CACT,GAAI,OACJ,MAAO,KAAK,OAAA,EAAM,EAAU,kBAAA,KAAmB,EAAnB,EAAqB,CAAG,KAAK,OAAA,EAAM,EAAU,0BAAA,KAA2B,EAA3B,EAA6B,CACvG,CAAC,CAEF,IAAM,EAAa,GAAe,CAC9B,GACF,EAAQ,YAAY,iBAAkB,CACpC,GAAI,EACJ,MAAO,EACP,SACD,CAAC,EAIN,sBAAuB,EAAgB,EAAoB,EAAY,KAAS,CAC9E,EAAQ,YAAY,4BAA6B,CAC/C,UACA,YACA,WACD,CAAC,EAGJ,eAAgB,EAAwB,EAAoC,IAAgC,OAE1G,GAAI,CADe,GAAe,CACjB,OAEjB,IAAM,GAAA,EAAW,GAAe,GAAA,KAAI,GAAJ,EAC1B,EAA0B,CAC9B,gBAAiB,EAAc,wBAAwB,CACvD,wBAAyB,EAAc,eAAe,CACvD,CAED,EAAQ,YAAY,iBAAkB,CACpC,iBACA,SAAU,CAAE,WAAU,OAAM,CAC5B,mBACA,UAAW,GAAA,KAAU,EAAE,CAAZ,EACZ,CAAC,EAGJ,SAAU,CAER,KAAO,GAA2B,CAChC,EAAQ,YAAY,yBAA0B,EAAO,EAGvD,SAAY,CACV,EAAQ,YAAY,yBAA0B,CAC5C,OAAQ,EAAE,CACV,QAAS,EAAE,CACX,UAAW,EAAE,CACb,SAAU,EAAE,CACZ,GAAI,EAAE,CACN,EAAG,EAAE,CACL,mBAAoB,GACrB,CAAC,EAEL,CAED,SAAU,EACX,ECvdU,EAAwC,CACnD,OAAQ,CAAC,aAAc,YAAa,YAAa,IAAA,GAAU,CAC3D,QAAS,CAAE,IAAK,QAAS,IAAK,MAAO,CACrC,UAAW,CAAC,IAAK,IAAI,CACrB,SAAU,EAAE,CACZ,GAAI,EAAE,CACN,EAAG,CAAC,IAAK,IAAI,CACb,mBAAoB,GACrB,CCDD,IAAa,EAAb,cAAuC,KAAM,CAC3C,YACE,EACA,EACA,EACA,CACA,MAAM,EAAQ,CAJP,KAAA,KAAA,EAEA,KAAA,cAAA,EAGP,KAAK,KAAO"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,6 @@ export { createPuzzmoSDK } from "./sdk";
2
2
  export { defaultKeyboardConfig } from "./keyboard";
3
3
  export type { SDK as PuzzmoSDK, PuzzmoSDKOptions, SDKEventMap, SDKEventType, SDKTimer } from "./sdk";
4
4
  export type { Theme, GamePlay, AugmentationConfig, CheckpointConfig, Deed, GameOverMessageUIComponent, BootstrapGameData, MessagesReceived, MessagesSentFromEmbed, AppBundle, ThumbnailConfig, KeyboardConfig, } from "./types";
5
- export type { ValidationIssue, ValidationReport, ImportErrorType, ImportResult, EditorBundle } from "./editor";
5
+ export type { ValidationLevel, ValidationIssue, ValidationReport, ImportErrorType, ImportResult, EditorBundle, EditorBundleSettings, EditorMountConfig, EditorMountHandle, EditorFetchURLResult, RelatedWord, } from "./editor";
6
6
  export { EditorImportError } from "./editor";
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAA;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAElD,YAAY,EAAE,GAAG,IAAI,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEpG,YAAY,EACV,KAAK,EACL,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,EAChB,IAAI,EACJ,0BAA0B,EAC1B,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,SAAS,EACT,eAAe,EACf,cAAc,GACf,MAAM,SAAS,CAAA;AAEhB,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE9G,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,OAAO,CAAA;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAElD,YAAY,EAAE,GAAG,IAAI,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEpG,YAAY,EACV,KAAK,EACL,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,EAChB,IAAI,EACJ,0BAA0B,EAC1B,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,SAAS,EACT,eAAe,EACf,cAAc,GACf,MAAM,SAAS,CAAA;AAEhB,YAAY,EACV,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,WAAW,GACZ,MAAM,UAAU,CAAA;AAEjB,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA"}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/sdk.ts","../src/keyboard.ts","../src/editor.ts"],"sourcesContent":["import type {\n MessagesSentFromEmbed,\n MessagesReceived,\n GamePlay,\n AugmentationConfig,\n CheckpointConfig,\n Theme,\n Deed,\n KeyboardConfig,\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>\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 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 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) => emit(\"settingsUpdate\", data))\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 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 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 },\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 },\n\n gameCompleted: (play: Partial<GamePlay>, config?: AugmentationConfig) => {\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 }\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 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","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","/** 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/** Main interface for a Workshop bundle */\nexport interface EditorBundle<TSettingsComponent = unknown> {\n validator: {\n validate(data: string): Promise<ValidationReport> | ValidationReport\n }\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 mount(...args: unknown[]): unknown\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/sdk.ts","../src/keyboard.ts","../src/editor.ts"],"sourcesContent":["import type {\n MessagesSentFromEmbed,\n MessagesReceived,\n GamePlay,\n AugmentationConfig,\n CheckpointConfig,\n Theme,\n Deed,\n KeyboardConfig,\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>\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 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 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) => emit(\"settingsUpdate\", data))\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 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 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 },\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 },\n\n gameCompleted: (play: Partial<GamePlay>, config?: AugmentationConfig) => {\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 }\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 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","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","/** 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: \"light\" | \"dark\"\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?: \"light\" | \"dark\"; 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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@puzzmo/sdk",
3
- "version": "1.0.23",
3
+ "version": "1.0.24",
4
4
  "description": "Puzzmo runtime SDK for game developers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",