@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/fonts.d.ts
CHANGED
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
* Uses subset fonts where available for smaller payloads.
|
|
4
4
|
*/
|
|
5
5
|
declare const fontURLMap: {
|
|
6
|
-
readonly "Poppins-Regular": "https://
|
|
7
|
-
readonly "Poppins-Bold": "https://
|
|
8
|
-
readonly "Poppins-BoldItalic": "https://
|
|
9
|
-
readonly "Poppins-Medium": "https://
|
|
10
|
-
readonly "Poppins-SemiBold": "https://
|
|
11
|
-
readonly "Poppins-ExtraLight": "https://
|
|
12
|
-
readonly "Poppins-Light": "https://
|
|
13
|
-
readonly "RedHatMono-Bold": "https://
|
|
14
|
-
readonly "RedHatMono-Regular": "https://
|
|
15
|
-
readonly "Dongle-Regular": "https://
|
|
16
|
-
readonly "Rubik-Light": "https://
|
|
17
|
-
readonly "Rubik-Regular": "https://
|
|
18
|
-
readonly "NotoSansSymbols2-Regular": "https://
|
|
6
|
+
readonly "Poppins-Regular": "https://cdn.puzzmo.com/assets/fonts-subset/Poppins-Regular-subset.ttf";
|
|
7
|
+
readonly "Poppins-Bold": "https://cdn.puzzmo.com/assets/fonts-subset/Poppins-Bold-subset.ttf";
|
|
8
|
+
readonly "Poppins-BoldItalic": "https://cdn.puzzmo.com/assets/fonts-subset/Poppins-BoldItalic-subset.ttf";
|
|
9
|
+
readonly "Poppins-Medium": "https://cdn.puzzmo.com/assets/fonts-subset/Poppins-Medium-subset.ttf";
|
|
10
|
+
readonly "Poppins-SemiBold": "https://cdn.puzzmo.com/assets/fonts-subset/Poppins-SemiBold-subset.ttf";
|
|
11
|
+
readonly "Poppins-ExtraLight": "https://cdn.puzzmo.com/assets/fonts-subset/Poppins-ExtraLight-subset.ttf";
|
|
12
|
+
readonly "Poppins-Light": "https://cdn.puzzmo.com/assets/fonts-subset/Poppins-Light-subset.ttf";
|
|
13
|
+
readonly "RedHatMono-Bold": "https://cdn.puzzmo.com/assets/fonts-subset/RedHatMono-Bold-subset.ttf";
|
|
14
|
+
readonly "RedHatMono-Regular": "https://cdn.puzzmo.com/assets/fonts-subset/RedHatMono-Regular-subset.ttf";
|
|
15
|
+
readonly "Dongle-Regular": "https://cdn.puzzmo.com/assets/fonts-subset/Dongle-Regular-subset.ttf";
|
|
16
|
+
readonly "Rubik-Light": "https://cdn.puzzmo.com/assets/fonts-subset/Rubik-Light-subset.ttf";
|
|
17
|
+
readonly "Rubik-Regular": "https://cdn.puzzmo.com/assets/fonts-subset/Rubik-Regular-subset.ttf";
|
|
18
|
+
readonly "NotoSansSymbols2-Regular": "https://cdn.puzzmo.com/assets/fonts/NotoSansSymbols2-Regular.ttf";
|
|
19
19
|
};
|
|
20
20
|
export type ThumbnailFontName = keyof typeof fontURLMap;
|
|
21
21
|
/** Generates an SVG `<style>` block with `@font-face` declarations for the given font names. */
|
package/dist/fonts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var e = "https://
|
|
1
|
+
var e = "https://cdn.puzzmo.com/assets/fonts-subset", t = "https://cdn.puzzmo.com/assets/fonts", n = {
|
|
2
2
|
"Poppins-Regular": `${e}/Poppins-Regular-subset.ttf`,
|
|
3
3
|
"Poppins-Bold": `${e}/Poppins-Bold-subset.ttf`,
|
|
4
4
|
"Poppins-BoldItalic": `${e}/Poppins-BoldItalic-subset.ttf`,
|
package/dist/fonts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fonts.js","names":[],"sources":["../src/fonts.ts"],"sourcesContent":["const fontSubsetBase = \"https://
|
|
1
|
+
{"version":3,"file":"fonts.js","names":[],"sources":["../src/fonts.ts"],"sourcesContent":["const fontSubsetBase = \"https://cdn.puzzmo.com/assets/fonts-subset\"\nconst fontFullBase = \"https://cdn.puzzmo.com/assets/fonts\"\n\n/**\n * Maps PostScript-style font names used in thumbnail SVGs to their font URLs.\n * Uses subset fonts where available for smaller payloads.\n */\nconst fontURLMap = {\n \"Poppins-Regular\": `${fontSubsetBase}/Poppins-Regular-subset.ttf`,\n \"Poppins-Bold\": `${fontSubsetBase}/Poppins-Bold-subset.ttf`,\n \"Poppins-BoldItalic\": `${fontSubsetBase}/Poppins-BoldItalic-subset.ttf`,\n \"Poppins-Medium\": `${fontSubsetBase}/Poppins-Medium-subset.ttf`,\n \"Poppins-SemiBold\": `${fontSubsetBase}/Poppins-SemiBold-subset.ttf`,\n \"Poppins-ExtraLight\": `${fontSubsetBase}/Poppins-ExtraLight-subset.ttf`,\n \"Poppins-Light\": `${fontSubsetBase}/Poppins-Light-subset.ttf`,\n \"RedHatMono-Bold\": `${fontSubsetBase}/RedHatMono-Bold-subset.ttf`,\n \"RedHatMono-Regular\": `${fontSubsetBase}/RedHatMono-Regular-subset.ttf`,\n \"Dongle-Regular\": `${fontSubsetBase}/Dongle-Regular-subset.ttf`,\n \"Rubik-Light\": `${fontSubsetBase}/Rubik-Light-subset.ttf`,\n \"Rubik-Regular\": `${fontSubsetBase}/Rubik-Regular-subset.ttf`,\n \"NotoSansSymbols2-Regular\": `${fontFullBase}/NotoSansSymbols2-Regular.ttf`,\n} as const satisfies Record<string, string>\n\nexport type ThumbnailFontName = keyof typeof fontURLMap\n\nfunction fontFaceRules(fontNames: ThumbnailFontName[]): string {\n return fontNames\n .map((name) => {\n const url = fontURLMap[name]\n return `@font-face { font-family: \"${name}\"; src: url(\"${url}\"); }`\n })\n .filter(Boolean)\n .join(\"\\n\")\n}\n\n/** Generates an SVG `<style>` block with `@font-face` declarations for the given font names. */\nexport function svgFontFaceCSS(fontNames: ThumbnailFontName[]): string {\n return `<style>${fontFaceRules(fontNames)}</style>`\n}\n\n/** Returns raw `@font-face` CSS for use inside a JSX `<style>` element. */\nexport function svgFontFaceCSSRaw(fontNames: ThumbnailFontName[]): string {\n return fontFaceRules(fontNames)\n}\n"],"mappings":"AAAA,IAAM,IAAiB,8CACjB,IAAe,uCAMf,IAAa;CACjB,mBAAmB,GAAG,EAAe;CACrC,gBAAgB,GAAG,EAAe;CAClC,sBAAsB,GAAG,EAAe;CACxC,kBAAkB,GAAG,EAAe;CACpC,oBAAoB,GAAG,EAAe;CACtC,sBAAsB,GAAG,EAAe;CACxC,iBAAiB,GAAG,EAAe;CACnC,mBAAmB,GAAG,EAAe;CACrC,sBAAsB,GAAG,EAAe;CACxC,kBAAkB,GAAG,EAAe;CACpC,eAAe,GAAG,EAAe;CACjC,iBAAiB,GAAG,EAAe;CACnC,4BAA4B,GAAG,EAAa;CAC7C;AAID,SAAS,EAAc,GAAwC;AAC7D,QAAO,EACJ,KAAK,MAEG,8BAA8B,EAAK,eAD9B,EAAW,GACsC,OAC7D,CACD,OAAO,QAAQ,CACf,KAAK,KAAK;;;AAIf,SAAgB,EAAe,GAAwC;AACrE,QAAO,UAAU,EAAc,EAAU,CAAC;;;AAI5C,SAAgB,EAAkB,GAAwC;AACxE,QAAO,EAAc,EAAU"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./objectSpread2-CuluYLUL.cjs`),t=require(`./asyncToGenerator-DANjdFmB.cjs`);var n=function(){var n=t.t(function*(t,n,r,i){try{let a=e.t(e.t({},t),{},{eventType:n,elapsedTimeOnComplete:i,metadata:r}),o=yield fetch(`${t.apiRoot}events`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(a),keepalive:!0});o.ok||console.warn(`Analytics event failed: ${n} - ${o.status} ${o.statusText}`)}catch(e){console.warn(`Analytics event error: ${n}`,e)}});return function(e,t,r,i){return n.apply(this,arguments)}}();const r=e=>{let t={context:e,updateCount:0,startTime:new Date,tracked:{pageView:!1,gameplayActive:!1,active30s:!1,completed:!1}};return{trackEvent:(e,r)=>{let i=(new Date().getTime()-t.startTime.getTime())/1e3;switch(e){case`READY_GAME_LOADED`:t.tracked.pageView||(n(t.context,`page_view`),t.tracked.pageView=!0);break;case`UPLOAD_NEW_GAME_STATE`:t.updateCount++,t.updateCount>2&&!t.tracked.gameplayActive&&(n(t.context,`gameplay_active`),t.tracked.gameplayActive=!0),i>30&&!t.tracked.active30s&&(n(t.context,`active_30s`),t.tracked.active30s=!0);break;case`GAME_COMPLETED`:!t.tracked.completed&&r!=null&&r.input&&(n(t.context,`completed`,{},r.input.elapsedTimeSecs),t.tracked.completed=!0);break}},trackLinkClick:e=>{n(t.context,`link_click`,e)}}},i=e=>{if(!u(e))return null;let t=o();if(!t.enabled)return null;let n=s(e,t.apiRoot);return n?r(n):null},a=e=>{if(typeof document>`u`)return()=>{};let t=t=>{var n,r;let i=t.target;if(!(i instanceof Element))return;let a=i.closest(`a[href]`);a instanceof HTMLAnchorElement&&e({href:a.href,text:(n=(r=a.textContent)==null?void 0:r.trim())==null?``:n,target:a.target})};return document.addEventListener(`click`,t),()=>document.removeEventListener(`click`,t)};var o=()=>{if(typeof window>`u`||!window.location)return{apiRoot:``,enabled:!1};let e=window.location.hostname;return e===`localhost`||e===`127.0.0.1`||e===`0.0.0.0`||e===`[::1]`||e.endsWith(`.localhost`)?{apiRoot:``,enabled:!1}:{apiRoot:e.includes(`staging`)?`https://api-staging.puzzmo.com/`:`https://api.puzzmo.com/`,enabled:!0}},s=(e,t)=>{var n,r,i,a,o,s,l,u,f,p,m;let h=e==null||(n=e.startOrFindGameplay)==null?void 0:n.gamePlayed;if(!h)return null;let g=h.puzzle,_=(r=e==null?void 0:e.hostContext)==null?[]:r;return{gameSlug:(i=(a=g.game)==null?void 0:a.slug)==null?`unknown`:i,puzzleID:g.id,gameplayOwnerID:h.ownerID||(e==null||(o=e.userState)==null?void 0:o.id)||`unknown`,apiRoot:t,userType:c(e==null||(s=e.currentUser)==null?void 0:s.type),runtimeType:d(_),userID:e==null||(l=e.currentUser)==null?void 0:l.id,teamSlug:((u=g.team)==null?void 0:u.slug)||void 0,partnerSlug:((f=_.find(e=>(e==null?void 0:e.type)===`embed`))==null||(f=f.partner)==null?void 0:f.slug)||void 0,dailyDateKey:(p=g.mostRecentDaily)==null||(p=p.daily)==null?void 0:p.dateKey,embedID:((m=_.find(e=>(e==null?void 0:e.type)===`embed`))==null?void 0:m.embedID)||void 0}},c=e=>!e||e===`Unverified`?`anon`:e===`Paid`?`paid`:`user`,l=512,u=e=>{var t,n;let r=e==null||(t=e.startOrFindGameplay)==null||(t=t.gamePlayed)==null||(t=t.puzzle)==null?void 0:t.game,i=r==null?void 0:r.flagsArr;return((Array.isArray(i)?(n=i[0])==null?0:n:typeof i==`number`?i:0)&l)===l},d=e=>{if(e.find(e=>(e==null?void 0:e.type)===`embed`))return`iframe`;let t=e.find(e=>(e==null?void 0:e.type)===`app`);return(t==null?void 0:t.host)===`__embed`?`simple_embed`:(t==null?void 0:t.host)===`app-embed`?`app_embed`:(t==null?void 0:t.host)===`ios-app`?`ios_native`:`puzzmo_com`};function f(e){let t=e>=3600*1e3;return new Date(e).toISOString().slice(t?11:14,-1).split(`.`)[0]}function p(e=0,t=0){let n=e,r=t,i=0,a,o,s,c=()=>{var e;if(o===void 0)return n+r;if(a!==void 0)return a;let t=((e=s)==null?performance.now():e)-o-i;return n+r+t};return{_init:()=>{o===void 0&&(o=performance.now(),s=void 0)},_pause:()=>{s!==void 0||o===void 0||(s=performance.now())},_resume:()=>{s!==void 0&&(i+=performance.now()-s,s=void 0)},_reset:(e=0,t=0)=>{n=e,r=t,i=0,a=void 0,o=void 0,s=void 0},_conclude:()=>{if(s!==void 0){a=c();return}if(o===void 0){a=n+r;return}a=c()},timeMs:()=>c(),timeSecs:()=>c()/1e3,addedTimeMs:()=>r,addedTimeSecs:()=>r/1e3,timeWithoutPenaltySecs:()=>(c()-r)/1e3,addPenalty:e=>{r+=e},isPaused:()=>s!==void 0||o===void 0,isRunning:()=>o!==void 0&&s===void 0,display:()=>{let e=c()-r;return[f(Math.max(0,e)),r===0?``:f(r)]}}}function m(){let e=new Map;return typeof window<`u`&&window.addEventListener(`message`,t=>{var n;if(!(!(t==null||(n=t.data)==null)&&n.type))return;let r=t.data.type,i=e.get(r);if(i){var a,o;let e=(a=(o=t.data.data)==null?t.data.json:o)==null?{}:a;r!==`TIMER_TICK`&&r!==`TIMER_SYNC`&&console.log(`[Puzzmo SDK] received:`,r,e),i.forEach(t=>t(e))}}),{sendMessage:(e,t)=>{var n,r;let i={type:e,json:t,_:`p`,__:`mp`,private:!0};`parent`in window&&window.parent!==window&&window.parent.postMessage(i,`*`),window.postMessage(i,`*`),`webkit`in window&&!((n=window.webkit)==null||(n=n.messageHandlers)==null)&&n.app&&window.webkit.messageHandlers.app.postMessage(i),`puzzmoMessageString`in window&&window.puzzmoMessageString(JSON.stringify(i)),`ReactNativeWebView`in window&&(r=window.ReactNativeWebView)!=null&&r.postMessage&&window.ReactNativeWebView.postMessage(JSON.stringify(i)),e!==`TIMER_TICK`&&e!==`TIMER_SYNC`&&console.log(`[Puzzmo SDK] sent:`,e,t)},onMessage:(t,n)=>(e.has(t)||e.set(t,new Set),e.get(t).add(n),()=>{var r;(r=e.get(t))==null||r.delete(n)})}}var h=m();const g=(n={})=>{let r=null,o=null,s=()=>{var e;return r==null||(e=r.startOrFindGameplay)==null?void 0:e.gamePlayed},c=()=>{var e,t;return(e=(t=s())==null?void 0:t.id)==null?null:e},l=()=>{var e,t;return(e=(t=s())==null?void 0:t.puzzle.puzzle)==null?null:e},u=()=>{var e,t;return(e=(t=s())==null?void 0:t.boardState)==null?null:e},d=()=>{var e;return(e=r==null?void 0:r.theme)==null?null:e},f=()=>{var e,t;return(e=(t=s())==null?void 0:t.completed)==null?!1:e},m=new Map,g=null,v=null,y=!1,b=(e,t)=>{y||v==null||v.trackEvent(e,t)},x=p(),S=null,C=null,w=()=>{S||(S=setInterval(()=>{if(x.isPaused())return;let[e,t]=x.display();h.sendMessage(`TIMER_TICK`,{display:[e,t]})},500),C=setInterval(()=>{x.isPaused()||h.sendMessage(`TIMER_SYNC`,Math.floor(x.timeWithoutPenaltySecs()))},1e4))},T=()=>{S&&(clearInterval(S),S=null),C&&(clearInterval(C),C=null)},E=(e,t)=>{let n=m.get(e);n&&n.forEach(e=>e(t))};return h.onMessage(`START_GAME`,()=>{x._init(),w(),E(`start`)}),h.onMessage(`PAUSE_GAME`,()=>{x._pause(),E(`pause`)}),h.onMessage(`RESUME_GAME`,()=>{x._resume(),E(`resume`)}),h.onMessage(`SETTINGS_UPDATE`,t=>{g=e.t(e.t({},g),t),E(`settingsUpdate`,g)}),h.onMessage(`KEYBOARD_KEY_PRESS`,e=>E(`keyboardKeyPress`,e)),h.onMessage(`KEYBOARD_CURSOR_CHANGE`,e=>E(`keyboardCursorChange`,e)),h.onMessage(`KEYBOARD_CURSOR_END`,()=>E(`keyboardCursorEnd`)),h.onMessage(`RETRY_PUZZLE`,()=>{x._reset(),T(),E(`retry`)}),h.onMessage(`READY_DATA`,e=>{var t,n,s;let c=e;r=c,g===null&&(g=(t=(n=c.userState)==null?void 0:n.gameSettings)==null?{}:t);let l=(s=c.startOrFindGameplay)==null?void 0:s.gamePlayed;if(l){var u,d;let e=((u=l.elapsedTimeSecs)==null?0:u)*1e3,t=((d=l.additionalTimeAddedSecs)==null?0:d)*1e3;x._reset(e,t)}if(!v){var f;v=i(c),y=(f=l==null?void 0:l.completed)==null?!1:f,v&&a(e=>v.trackLinkClick(e))}o&&(o(c),o=null)}),{timer:{timeMs:()=>x.timeMs(),timeSecs:()=>x.timeSecs(),addedTimeMs:()=>x.addedTimeMs(),addedTimeSecs:()=>x.addedTimeSecs(),timeWithoutPenaltySecs:()=>x.timeWithoutPenaltySecs(),display:()=>x.display(),addPenalty:e=>x.addPenalty(e),isPaused:()=>x.isPaused(),isRunning:()=>x.isRunning()},gameReady:function(){var e=t.t(function*(){var e;if(h.sendMessage(`READY`,{}),l()){let e=u();return{puzzleString:l(),inputString:e,boardState:e,theme:d(),completed:f(),readyData:r}}let t=(e=n.timeout)==null?5e3:e;yield new Promise((e,n)=>{o=e,setTimeout(()=>{o&&(o=null,n(Error(`Timeout waiting for READY_DATA after ${t}ms`)))},t)});let i=l();if(!i)throw Error(`READY_DATA received but no puzzle data found`);let a=u();return{puzzleString:i,inputString:a,boardState:a,theme:d(),completed:f(),readyData:r}});return function(){return e.apply(this,arguments)}}(),gameLoaded:(e={})=>{h.sendMessage(`READY_GAME_LOADED`,{state:e,gameRuntimeContract:`1.0`,embedRuntimeContract:`1.0`}),b(`READY_GAME_LOADED`)},on:(e,t)=>(m.has(e)||m.set(e,new Set),m.get(e).add(t),()=>{var n;(n=m.get(e))==null||n.delete(t)}),off:(e,t)=>{var n;(n=m.get(e))==null||n.delete(t)},updateGameState:(e,t)=>{var n,r;let i=c();i&&(h.sendMessage(`UPLOAD_NEW_GAME_STATE`,{id:i,input:{boardState:e,elapsedTimeSecs:(n=t==null?void 0:t.elapsedTimeSecs)==null?x.timeWithoutPenaltySecs():n,additionalTimeAddedSecs:(r=t==null?void 0:t.additionalTimeAddedSecs)==null?x.addedTimeSecs():r,collabUserReferences:[]}}),b(`UPLOAD_NEW_GAME_STATE`))},gameCompleted:(t,n)=>{var r,i,a,o,s;x._conclude(),T();let l=e.t(e.t({},t),{},{elapsedTimeSecs:(r=t.elapsedTimeSecs)==null?x.timeWithoutPenaltySecs():r,additionalTimeAddedSecs:(i=t.additionalTimeAddedSecs)==null?x.addedTimeSecs():i}),u=(a=n==null?void 0:n.deeds)==null?[]:a;u.push({id:`points`,value:t.pointsAwarded}),u.push({id:`time`,value:Math.round((o=l.elapsedTimeSecs)==null?0:o)+Math.round((s=l.additionalTimeAddedSecs)==null?0:s)});let d=c();d&&(h.sendMessage(`GAME_COMPLETED`,{id:d,input:l,config:n}),b(`GAME_COMPLETED`,{input:l}))},showCompletionScreen:(e,t,n=!0)=>{h.sendMessage(`SHOW_GAME_COMPLETE_SCREEN`,{results:e,showRetry:n,gameplay:t})},hitCheckpoint:(e,t,n)=>{var r;if(!c())return;let i=(r=u())==null?``:r,a={elapsedTimeSecs:x.timeWithoutPenaltySecs(),additionalTimeAddedSecs:x.addedTimeSecs()};h.sendMessage(`HIT_CHECKPOINT`,{checkpointName:e,gameplay:{inputStr:i,play:a},checkpointConfig:t,augConfig:n==null?{}:n})},settings:{initialize:t=>(g=e.t(e.t({},_(t)),g),h.sendMessage(`INITIALIZE_SETTINGS`,{components:t,settings:g}),g),get:()=>{var e;return(e=g)==null?{}:e},update:t=>(g=e.t(e.t({},g),t),h.sendMessage(`UPDATE_SETTINGS_FROM_EMBED`,{settings:g}),g)},keyboard:{show:e=>{h.sendMessage(`KEYBOARD_UPDATE_CONFIG`,e)},hide:()=>{h.sendMessage(`KEYBOARD_UPDATE_CONFIG`,{layout:[],symbols:{},highlight:[],disabled:[],xl:[],l:[],supportsDragCursor:!1})}},_hostAPI:h}};function _(e){let t={};for(let n of e)n.type===`split`?Object.assign(t,_(n.content)):`name`in n&&(t[n.name]=n.defaultValue);return t}const v={layout:[`qwertyuiop`,`asdfghjkl`,`↵zxcvbnm⌫`,void 0],symbols:{"↵":`Enter`,"⌫":`bsp`},highlight:[`↵`,`⌫`],disabled:[],xl:[],l:[`↵`,`⌫`],supportsDragCursor:!1};var y=class extends Error{constructor(e,t,n){super(t),this.type=e,this.originalError=n,this.name=`EditorImportError`}};exports.EditorImportError=y,exports.createPuzzmoSDK=g,exports.defaultKeyboardConfig=v;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./objectSpread2-CuluYLUL.cjs`),t=require(`./asyncToGenerator-DANjdFmB.cjs`);var n=function(){var n=t.t(function*(t,n,r,i){try{let a=e.t(e.t({},t),{},{eventType:n,elapsedTimeOnComplete:i,metadata:r}),o=yield fetch(`${t.apiRoot}events`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(a),keepalive:!0});o.ok||console.warn(`Analytics event failed: ${n} - ${o.status} ${o.statusText}`)}catch(e){console.warn(`Analytics event error: ${n}`,e)}});return function(e,t,r,i){return n.apply(this,arguments)}}();const r=e=>{let t={context:e,updateCount:0,startTime:new Date,tracked:{pageView:!1,gameplayActive:!1,active30s:!1,completed:!1}};return{trackEvent:(e,r)=>{let i=(new Date().getTime()-t.startTime.getTime())/1e3;switch(e){case`READY_GAME_LOADED`:t.tracked.pageView||(n(t.context,`page_view`),t.tracked.pageView=!0);break;case`UPLOAD_NEW_GAME_STATE`:t.updateCount++,t.updateCount>2&&!t.tracked.gameplayActive&&(n(t.context,`gameplay_active`),t.tracked.gameplayActive=!0),i>30&&!t.tracked.active30s&&(n(t.context,`active_30s`),t.tracked.active30s=!0);break;case`GAME_COMPLETED`:!t.tracked.completed&&r!=null&&r.input&&(n(t.context,`completed`,{},r.input.elapsedTimeSecs),t.tracked.completed=!0);break}},trackLinkClick:e=>{n(t.context,`link_click`,e)}}},i=e=>{if(!u(e))return null;let t=o();if(!t.enabled)return null;let n=s(e,t.apiRoot);return n?r(n):null},a=e=>{if(typeof document>`u`)return()=>{};let t=t=>{var n,r;let i=t.target;if(!(i instanceof Element))return;let a=i.closest(`a[href]`);a instanceof HTMLAnchorElement&&e({href:a.href,text:(n=(r=a.textContent)==null?void 0:r.trim())==null?``:n,target:a.target})};return document.addEventListener(`click`,t),()=>document.removeEventListener(`click`,t)};var o=()=>{if(typeof window>`u`||!window.location)return{apiRoot:``,enabled:!1};let e=window.location.hostname;return e===`localhost`||e===`127.0.0.1`||e===`0.0.0.0`||e===`[::1]`||e.endsWith(`.localhost`)?{apiRoot:``,enabled:!1}:{apiRoot:e.includes(`staging`)?`https://api-staging.puzzmo.com/`:`https://api.puzzmo.com/`,enabled:!0}},s=(e,t)=>{var n,r,i,a,o,s,l,u,f,p,m;let h=e==null||(n=e.startOrFindGameplay)==null?void 0:n.gamePlayed;if(!h)return null;let g=h.puzzle,_=(r=e==null?void 0:e.hostContext)==null?[]:r;return{gameSlug:(i=(a=g.game)==null?void 0:a.slug)==null?`unknown`:i,puzzleID:g.id,gameplayOwnerID:h.ownerID||(e==null||(o=e.userState)==null?void 0:o.id)||`unknown`,apiRoot:t,userType:c(e==null||(s=e.currentUser)==null?void 0:s.type),runtimeType:d(_),userID:e==null||(l=e.currentUser)==null?void 0:l.id,teamSlug:((u=g.team)==null?void 0:u.slug)||void 0,partnerSlug:((f=_.find(e=>(e==null?void 0:e.type)===`embed`))==null||(f=f.partner)==null?void 0:f.slug)||void 0,dailyDateKey:(p=g.mostRecentDaily)==null||(p=p.daily)==null?void 0:p.dateKey,embedID:((m=_.find(e=>(e==null?void 0:e.type)===`embed`))==null?void 0:m.embedID)||void 0}},c=e=>!e||e===`Unverified`?`anon`:e===`Paid`?`paid`:`user`,l=512,u=e=>{var t,n;let r=e==null||(t=e.startOrFindGameplay)==null||(t=t.gamePlayed)==null||(t=t.puzzle)==null?void 0:t.game,i=r==null?void 0:r.flagsArr;return((Array.isArray(i)?(n=i[0])==null?0:n:typeof i==`number`?i:0)&l)===l},d=e=>{if(e.find(e=>(e==null?void 0:e.type)===`embed`))return`iframe`;let t=e.find(e=>(e==null?void 0:e.type)===`app`);return(t==null?void 0:t.host)===`__embed`?`simple_embed`:(t==null?void 0:t.host)===`app-embed`?`app_embed`:(t==null?void 0:t.host)===`ios-app`?`ios_native`:`puzzmo_com`};function f(e){let t=e>=3600*1e3;return new Date(e).toISOString().slice(t?11:14,-1).split(`.`)[0]}function p(e=0,t=0){let n=e,r=t,i=0,a,o,s,c=()=>{var e;if(o===void 0)return n+r;if(a!==void 0)return a;let t=((e=s)==null?performance.now():e)-o-i;return n+r+t};return{_init:()=>{o===void 0&&(o=performance.now(),s=void 0)},_pause:()=>{s!==void 0||o===void 0||(s=performance.now())},_resume:()=>{s!==void 0&&(i+=performance.now()-s,s=void 0)},_reset:(e=0,t=0)=>{n=e,r=t,i=0,a=void 0,o=void 0,s=void 0},_conclude:()=>{if(s!==void 0){a=c();return}if(o===void 0){a=n+r;return}a=c()},timeMs:()=>c(),timeSecs:()=>c()/1e3,addedTimeMs:()=>r,addedTimeSecs:()=>r/1e3,timeWithoutPenaltySecs:()=>(c()-r)/1e3,addPenalty:e=>{r+=e},isPaused:()=>s!==void 0||o===void 0,isRunning:()=>o!==void 0&&s===void 0,display:()=>{let e=c()-r;return[f(Math.max(0,e)),r===0?``:f(r)]}}}function m(){let e=new Map;return typeof window<`u`&&window.addEventListener(`message`,t=>{var n;if(!(!(t==null||(n=t.data)==null)&&n.type))return;let r=t.data.type,i=e.get(r);if(i){var a,o;let e=(a=(o=t.data.data)==null?t.data.json:o)==null?{}:a;r!==`TIMER_TICK`&&r!==`TIMER_SYNC`&&console.log(`[Puzzmo SDK] received:`,r,e),i.forEach(t=>t(e))}}),{sendMessage:(e,t)=>{var n,r;let i={type:e,json:t,_:`p`,__:`mp`,private:!0};`parent`in window&&window.parent!==window&&window.parent.postMessage(i,`*`),window.postMessage(i,`*`),`webkit`in window&&!((n=window.webkit)==null||(n=n.messageHandlers)==null)&&n.app&&window.webkit.messageHandlers.app.postMessage(i),`puzzmoMessageString`in window&&window.puzzmoMessageString(JSON.stringify(i)),`ReactNativeWebView`in window&&(r=window.ReactNativeWebView)!=null&&r.postMessage&&window.ReactNativeWebView.postMessage(JSON.stringify(i)),e!==`TIMER_TICK`&&e!==`TIMER_SYNC`&&console.log(`[Puzzmo SDK] sent:`,e,t)},onMessage:(t,n)=>(e.has(t)||e.set(t,new Set),e.get(t).add(n),()=>{var r;(r=e.get(t))==null||r.delete(n)})}}var h=m();const g=(n={})=>{var r;let o=null,s=null,c=()=>{var e;return o==null||(e=o.startOrFindGameplay)==null?void 0:e.gamePlayed},l=()=>{var e,t;return(e=(t=c())==null?void 0:t.id)==null?null:e},u=()=>{var e,t;return(e=(t=c())==null?void 0:t.puzzle.puzzle)==null?null:e},d=()=>{var e,t;return(e=(t=c())==null?void 0:t.boardState)==null?null:e},f=()=>{var e;return(e=o==null?void 0:o.theme)==null?null:e},m=()=>{var e,t;return(e=(t=c())==null?void 0:t.completed)==null?!1:e},g=new Map,v=null,y=null,b=!1,x=(e,t)=>{b||y==null||y.trackEvent(e,t)},S=p(),C=null,w=null,T=()=>{C||(C=setInterval(()=>{if(S.isPaused())return;let[e,t]=S.display();h.sendMessage(`TIMER_TICK`,{display:[e,t]})},500),w=setInterval(()=>{S.isPaused()||h.sendMessage(`TIMER_SYNC`,Math.floor(S.timeWithoutPenaltySecs()))},1e4))},E=()=>{C&&(clearInterval(C),C=null),w&&(clearInterval(w),w=null)},D=(e,t)=>{let n=g.get(e);n&&n.forEach(e=>e(t))};h.onMessage(`START_GAME`,()=>{S._init(),T(),D(`start`)}),h.onMessage(`PAUSE_GAME`,()=>{S._pause(),D(`pause`)}),h.onMessage(`RESUME_GAME`,()=>{S._resume(),D(`resume`)}),h.onMessage(`SETTINGS_UPDATE`,t=>{v=e.t(e.t({},v),t),D(`settingsUpdate`,v)}),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`,()=>{S._reset(),E(),D(`retry`)}),h.onMessage(`READY_DATA`,e=>{var t,n,r;let c=e;o=c,v===null&&(v=(t=(n=c.userState)==null?void 0:n.gameSettings)==null?{}:t);let l=(r=c.startOrFindGameplay)==null?void 0:r.gamePlayed;if(l){var u,d;let e=((u=l.elapsedTimeSecs)==null?0:u)*1e3,t=((d=l.additionalTimeAddedSecs)==null?0:d)*1e3;S._reset(e,t)}if(!y){var f;y=i(c),b=(f=l==null?void 0:l.completed)==null?!1:f,y&&a(e=>y.trackLinkClick(e))}s&&(s(c),s=null)});let O={timeMs:()=>S.timeMs(),timeSecs:()=>S.timeSecs(),addedTimeMs:()=>S.addedTimeMs(),addedTimeSecs:()=>S.addedTimeSecs(),timeWithoutPenaltySecs:()=>S.timeWithoutPenaltySecs(),display:()=>S.display(),addPenalty:e=>S.addPenalty(e),isPaused:()=>S.isPaused(),isRunning:()=>S.isRunning()},k={send:(e,t)=>h.sendMessage(e,t),on:(e,t)=>h.onMessage(e,t),timer:O,bootstrap:()=>o},A={};for(let e of(r=n.plugins)==null?[]:r)A[e.name]=e.setup(k);return{timer:O,plugins:A,gameReady:function(){var e=t.t(function*(){var e;if(h.sendMessage(`READY`,{}),u()){let e=d();return{puzzleString:u(),inputString:e,boardState:e,theme:f(),completed:m(),readyData:o}}let t=(e=n.timeout)==null?5e3:e;yield new Promise((e,n)=>{s=e,setTimeout(()=>{s&&(s=null,n(Error(`Timeout waiting for READY_DATA after ${t}ms`)))},t)});let r=u();if(!r)throw Error(`READY_DATA received but no puzzle data found`);let i=d();return{puzzleString:r,inputString:i,boardState:i,theme:f(),completed:m(),readyData:o}});return function(){return e.apply(this,arguments)}}(),gameLoaded:(e={})=>{h.sendMessage(`READY_GAME_LOADED`,{state:e,gameRuntimeContract:`1.0`,embedRuntimeContract:`1.0`}),x(`READY_GAME_LOADED`)},on:(e,t)=>(g.has(e)||g.set(e,new Set),g.get(e).add(t),()=>{var n;(n=g.get(e))==null||n.delete(t)}),off:(e,t)=>{var n;(n=g.get(e))==null||n.delete(t)},updateGameState:(e,t)=>{var n,r;let i=l();i&&(h.sendMessage(`UPLOAD_NEW_GAME_STATE`,{id:i,input:{boardState:e,elapsedTimeSecs:(n=t==null?void 0:t.elapsedTimeSecs)==null?S.timeWithoutPenaltySecs():n,additionalTimeAddedSecs:(r=t==null?void 0:t.additionalTimeAddedSecs)==null?S.addedTimeSecs():r,collabUserReferences:[]}}),x(`UPLOAD_NEW_GAME_STATE`))},gameCompleted:(t,n)=>{var r,i,a,o,s;S._conclude(),E();let c=e.t(e.t({},t),{},{elapsedTimeSecs:(r=t.elapsedTimeSecs)==null?S.timeWithoutPenaltySecs():r,additionalTimeAddedSecs:(i=t.additionalTimeAddedSecs)==null?S.addedTimeSecs():i}),u=(a=n==null?void 0:n.deeds)==null?[]:a;u.push({id:`points`,value:t.pointsAwarded}),u.push({id:`time`,value:Math.round((o=c.elapsedTimeSecs)==null?0:o)+Math.round((s=c.additionalTimeAddedSecs)==null?0:s)});let d=l();d&&(h.sendMessage(`GAME_COMPLETED`,{id:d,input:c,config:n}),x(`GAME_COMPLETED`,{input:c}))},showCompletionScreen:(e,t,n=!0)=>{h.sendMessage(`SHOW_GAME_COMPLETE_SCREEN`,{results:e,showRetry:n,gameplay:t})},hitCheckpoint:(e,t,n)=>{var r;if(!l())return;let i=(r=d())==null?``:r,a={elapsedTimeSecs:S.timeWithoutPenaltySecs(),additionalTimeAddedSecs:S.addedTimeSecs()};h.sendMessage(`HIT_CHECKPOINT`,{checkpointName:e,gameplay:{inputStr:i,play:a},checkpointConfig:t,augConfig:n==null?{}:n})},settings:{initialize:t=>(v=e.t(e.t({},_(t)),v),h.sendMessage(`INITIALIZE_SETTINGS`,{components:t,settings:v}),v),get:()=>{var e;return(e=v)==null?{}:e},update:t=>(v=e.t(e.t({},v),t),h.sendMessage(`UPDATE_SETTINGS_FROM_EMBED`,{settings:v}),v)},keyboard:{show:e=>{h.sendMessage(`KEYBOARD_UPDATE_CONFIG`,e)},hide:()=>{h.sendMessage(`KEYBOARD_UPDATE_CONFIG`,{layout:[],symbols:{},highlight:[],disabled:[],xl:[],l:[],supportsDragCursor:!1})}},_hostAPI:h}};function _(e){let t={};for(let n of e)n.type===`split`?Object.assign(t,_(n.content)):`name`in n&&(t[n.name]=n.defaultValue);return t}const v={layout:[`qwertyuiop`,`asdfghjkl`,`↵zxcvbnm⌫`,void 0],symbols:{"↵":`Enter`,"⌫":`bsp`},highlight:[`↵`,`⌫`],disabled:[],xl:[],l:[`↵`,`⌫`],supportsDragCursor:!1};var y=class extends Error{constructor(e,t,n){super(t),this.type=e,this.originalError=n,this.name=`EditorImportError`}};exports.EditorImportError=y,exports.createPuzzmoSDK=g,exports.defaultKeyboardConfig=v;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","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.cjs","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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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { createPuzzmoSDK } from "./sdk";
|
|
2
2
|
export { defaultKeyboardConfig } from "./keyboard";
|
|
3
3
|
export type { SDK as PuzzmoSDK, PuzzmoSDKOptions, SDKEventMap, SDKEventType, SDKTimer } from "./sdk";
|
|
4
|
-
export type {
|
|
4
|
+
export type { SDKPlugin, SDKPluginContext, PluginAPIs } from "./plugins";
|
|
5
|
+
export type { Theme, GamePlay, AugmentationConfig, CheckpointConfig, Deed, GameOverMessageUIComponent, BootstrapGameData, MessagesReceived, MessagesSentFromEmbed, AppBundle, ThumbnailConfig, KeyboardConfig, GameSettingsUIComponents, HostContext, AppHostContext, EmbedHostContext, SandboxHostContext, ServerConfigHostContext, } from "./types";
|
|
5
6
|
export type { ValidationLevel, ValidationIssue, ValidationReport, ImportErrorType, ImportResult, EditorBundle, EditorBundleSettings, EditorMountConfig, EditorMountHandle, EditorTheme, EditorFetchURLResult, RelatedWord, } from "./editor";
|
|
6
7
|
export { EditorImportError } from "./editor";
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,EACd,wBAAwB,
|
|
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,EAAE,SAAS,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAExE,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,EACd,wBAAwB,EACxB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,SAAS,CAAA;AAEhB,YAAY,EACV,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,oBAAoB,EACpB,WAAW,GACZ,MAAM,UAAU,CAAA;AAEjB,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA"}
|