@memizy/protocol 1.0.0-rc.5 → 1.0.0-rc.7

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.
@@ -0,0 +1,8 @@
1
+ import { RateLimiter as e, assertJsonWithin as t, findNonJson as n, jsonByteSize as r } from "./limits.js";
2
+ import { toRuntime as i } from "./pluginRuntime.js";
3
+ import { HOST_API_ARGS as a, parseHostCall as o } from "./schemas.js";
4
+ import { RELAY_LIMITS as s, isPin as c, normalizePlayerName as l } from "./relay.js";
5
+ import { displayItem as u, publicItem as d, seededRandom as f } from "./items.js";
6
+ import { LIBRARY_ORIGINS as p, injectContentPolicy as m, pluginAllowAttribute as h, pluginContentPolicy as g } from "./security.js";
7
+ import { ensureConnectionPolyfills as _ } from "./polyfills.js";
8
+ export { a as HOST_API_ARGS, p as LIBRARY_ORIGINS, s as RELAY_LIMITS, e as RateLimiter, t as assertJsonWithin, u as displayItem, _ as ensureConnectionPolyfills, n as findNonJson, m as injectContentPolicy, c as isPin, r as jsonByteSize, l as normalizePlayerName, o as parseHostCall, h as pluginAllowAttribute, g as pluginContentPolicy, d as publicItem, f as seededRandom, i as toRuntime };
package/dist/items.cjs CHANGED
@@ -1 +1 @@
1
- let e=require("@memizy/oqse");function t(e){let t=2166136261;for(let n=0;n<e.length;n++)t=Math.imul(t^e.charCodeAt(n),16777619);let n=t>>>0;return()=>{n=n+1831565813>>>0;let e=n;return e=Math.imul(e^e>>>15,e|1),e^=e+Math.imul(e^e>>>7,e|61),((e^e>>>14)>>>0)/4294967296}}function n(e,t,n){for(let r=0;r<6;r++){let r=[...e];for(let e=r.length-1;e>0;e--){let n=Math.floor(t()*(e+1));[r[e],r[n]]=[r[n],r[e]]}if(!n||r.length<2||!n(r))return r}return[...e.slice(1),e[0]]}var r=e=>t=>t.every((t,n)=>t.id===e[n]);function i(t,i){let a=structuredClone(t);switch(a.type){case`mcq-single`:case`mcq-multi`:return a.shuffle!==!1&&(a.options=n(a.options,i)),a;case`fill-in-select`:for(let e of Object.values(a.blanks??{}))e.shuffle!==!1&&(e.options=n(e.options,i));return a;case`sort-items`:return a.items=n(a.items,i,r(a.correctOrder)),a;case`match-pairs`:return a.prompts=n(a.prompts,i),a.matches=n(a.matches,i),a;case`match-complex`:return a.leftItems=n(a.leftItems,i),a.rightItems=n(a.rightItems,i),a;case`timeline`:return a.shuffle!==!1&&(a.events=n(a.events,i,t=>(0,e.isTimelineOrderCorrect)(t))),a;case`categorize`:return a.items=n(a.items,i),a;case`diagram-label`:return a.requireTyping||(a.labels=n(a.labels,i)),a;default:return a}}var a=[`explanation`,`incorrectFeedback`],o={note:[`hiddenContent`],flashcard:[`back`],"true-false":[`correctAnswer`],"mcq-single":[`correctId`],"mcq-multi":[`correctIds`],"short-answer":[`correctAnswers`],"sort-items":[`correctOrder`],"match-pairs":[`pairs`],"match-complex":[`connections`],slider:[`correctAnswer`],"numeric-input":[`correctAnswer`],"math-input":[`correctAnswer`,`alternativeAnswers`],matrix:[`correctCells`],"open-ended":[`sampleAnswer`,`rubric`],"chess-puzzle":[`correctAnswers`]},s=[`options`,`items`,`prompts`,`matches`,`leftItems`,`rightItems`,`categories`,`rows`,`columns`,`labels`];function c(e){let t=e.type,n=structuredClone(e);for(let e of a)delete n[e];if(Array.isArray(n.sources))for(let e of n.sources)e&&typeof e==`object`&&delete e.quote;if(t.startsWith(`x-`))return delete n.solution,n.answerHidden=!0,n;for(let e of o[t]??[])delete n[e];for(let e of s)if(Array.isArray(n[e]))for(let t of n[e])t&&typeof t==`object`&&delete t.explanation;switch(t){case`fill-in-blanks`:n.blanks=Object.fromEntries(Object.keys(n.blanks??{}).map(e=>[e,[]]));break;case`fill-in-select`:for(let e of Object.values(n.blanks??{})){delete e.correctId;for(let t of e.options??[])delete t.explanation}break;case`categorize`:for(let e of n.items??[])delete e.correctCategoryId;break;case`timeline`:for(let e of n.events??[])delete e.date,delete e.precision;break;case`diagram-label`:for(let e of n.zones??[])delete e.correctLabelId;n.requireTyping&&(n.labels=[]);break;case`pin-on-image`:case`pin-on-model`:n.hotspots=[]}return n.answerHidden=!0,n}function l(e,n){let r=n===void 0?null:t(n),a=e.items.map(e=>r?i(e,r):structuredClone(e));return{set:{meta:e.meta,items:a},publicSet:{meta:e.meta,items:a.map(c)}}}exports.displayItem=i,exports.prepareDisplaySet=l,exports.publicItem=c,exports.seededRandom=t;
1
+ let e=require("@memizy/oqse");function t(e){let t=2166136261;for(let n=0;n<e.length;n++)t=Math.imul(t^e.charCodeAt(n),16777619);let n=t>>>0;return()=>{n=n+1831565813>>>0;let e=n;return e=Math.imul(e^e>>>15,e|1),e^=e+Math.imul(e^e>>>7,e|61),((e^e>>>14)>>>0)/4294967296}}function n(e,t,n){for(let r=0;r<6;r++){let r=[...e];for(let e=r.length-1;e>0;e--){let n=Math.floor(t()*(e+1));[r[e],r[n]]=[r[n],r[e]]}if(!n||r.length<2||!n(r))return r}return[...e.slice(1),e[0]]}var r=e=>t=>t.every((t,n)=>t.id===e[n]);function i(t,i){let a=structuredClone(t);switch(a.type){case`mcq-single`:case`mcq-multi`:return a.shuffle!==!1&&(a.options=n(a.options,i)),a;case`fill-in-select`:for(let e of Object.values(a.blanks??{}))e.shuffle!==!1&&(e.options=n(e.options,i));return a;case`sort-items`:return a.items=n(a.items,i,r(a.correctOrder)),a;case`match-pairs`:return a.prompts=n(a.prompts,i),a.matches=n(a.matches,i),a;case`match-complex`:return a.prompts=n(a.prompts,i),a.matches=n(a.matches,i),a;case`timeline`:return a.shuffle!==!1&&(a.events=n(a.events,i,t=>(0,e.isTimelineOrderCorrect)(t))),a;case`categorize`:return a.items=n(a.items,i),a;case`diagram-label`:return a.requireTyping||(a.labels=n(a.labels,i)),a;default:return a}}var a=e=>typeof e==`object`&&!!e&&!Array.isArray(e);function o(e,t){return e.find(e=>{let n=e._zod.def.shape?.type;return n?._zod.def.type===`literal`&&n._zod.def.values.includes(t.type)})}function s(e,t){let n=t._zod.def;switch(n.type){case`optional`:case`nullable`:case`default`:case`prefault`:case`nonoptional`:case`readonly`:case`catch`:return e==null?e:s(e,n.innerType);case`pipe`:return s(e,n.in);case`lazy`:return s(e,n.getter());case`object`:{if(!a(e))return e;let t={};for(let[r,i]of Object.entries(n.shape)){if(!(r in e))continue;let n=s(e[r],i);n!==void 0&&(t[r]=n)}return t}case`array`:return Array.isArray(e)?e.map(e=>s(e,n.element)):e;case`record`:return a(e)?[`unknown`,`any`].includes(n.valueType._zod.def.type)?void 0:Object.fromEntries(Object.entries(e).map(([e,t])=>[e,s(t,n.valueType)])):e;case`intersection`:return a(e)?{...s(e,n.left),...s(e,n.right)}:e;case`union`:{if(!a(e)&&!Array.isArray(e))return e;let t=n.options,r=a(e)&&o(t,e)||t.find(t=>t.safeParse(e).success);return r?s(e,r):Array.isArray(e)?[]:{}}case`unknown`:case`any`:return;default:return e}}var c=[`explanation`,`incorrectFeedback`],l={note:[`hiddenContent`],flashcard:[`back`],"true-false":[`correctAnswer`],"mcq-single":[`correctId`],"mcq-multi":[`correctIds`],"short-answer":[`correctAnswers`],"sort-items":[`correctOrder`],"match-pairs":[`pairs`],"match-complex":[`connections`],slider:[`correctAnswer`],"numeric-input":[`correctAnswer`],"math-input":[`correctAnswers`],matrix:[`correctCells`],"open-ended":[`sampleAnswer`,`rubric`],"chess-puzzle":[`correctAnswers`]},u=[`options`,`items`,`prompts`,`matches`,`categories`,`rows`,`columns`,`labels`];function d(t){let n=t.type,r=n.startsWith(`x-`)?structuredClone(t):s(structuredClone(t),e.OQSEItemSchema);for(let e of c)delete r[e];if(Array.isArray(r.sources))for(let e of r.sources)e&&typeof e==`object`&&delete e.quote;if(n.startsWith(`x-`))return delete r.solution,r.answerHidden=!0,r;for(let e of l[n]??[])delete r[e];for(let e of u)if(Array.isArray(r[e]))for(let t of r[e])t&&typeof t==`object`&&delete t.explanation;switch(n){case`fill-in-blanks`:r.blanks=Object.fromEntries(Object.keys(r.blanks??{}).map(e=>[e,[]]));break;case`fill-in-select`:for(let e of Object.values(r.blanks??{})){delete e.correctId;for(let t of e.options??[])delete t.explanation}break;case`categorize`:for(let e of r.items??[])delete e.correctCategoryId;break;case`timeline`:for(let e of r.events??[])delete e.date,delete e.precision;break;case`diagram-label`:for(let e of r.zones??[])delete e.correctLabelId;r.requireTyping&&(r.labels=[]);break;case`pin-on-image`:case`pin-on-model`:r.hotspots=[]}if(r.assets&&typeof r.assets==`object`){let e=f(r);r.assets=Object.fromEntries(Object.entries(r.assets).filter(([t])=>e.has(t.toLowerCase())))}return r.answerHidden=!0,r}function f(t){let n=new Set;for(let[r,i]of(0,e.itemRichText)(t))if(r[0]!==`assets`)for(let t of(0,e.findAssetKeys)(i))n.add(t);let r=t.targetAsset;return typeof r==`string`&&n.add(r.toLowerCase()),n}function p(t,n){if(!t.assets)return t;let r=new Set;for(let e of n)for(let t of f(e))r.add(t);for(let[n,i]of(0,e.metaRichText)(t))if(n[1]!==`assets`)for(let t of(0,e.findAssetKeys)(i))r.add(t);return t.thumbnail&&r.add(t.thumbnail.toLowerCase()),{...t,assets:Object.fromEntries(Object.entries(t.assets).filter(([e])=>r.has(e.toLowerCase())))}}function m(e,n){let r=e.items.map(e=>n===void 0?structuredClone(e):i(e,t(`${n}:${e.id}`))),a=r.map(d);return{set:{meta:e.meta,items:r},publicSet:{meta:p(e.meta,a),items:a}}}exports.displayItem=i,exports.prepareDisplaySet=m,exports.publicItem=d,exports.seededRandom=t;
package/dist/items.js CHANGED
@@ -1,6 +1,6 @@
1
- import { isTimelineOrderCorrect as e } from "@memizy/oqse";
1
+ import { OQSEItemSchema as e, findAssetKeys as t, isTimelineOrderCorrect as n, itemRichText as r, metaRichText as i } from "@memizy/oqse";
2
2
  //#region src/items.ts
3
- function t(e) {
3
+ function a(e) {
4
4
  let t = 2166136261;
5
5
  for (let n = 0; n < e.length; n++) t = Math.imul(t ^ e.charCodeAt(n), 16777619);
6
6
  let n = t >>> 0;
@@ -10,7 +10,7 @@ function t(e) {
10
10
  return e = Math.imul(e ^ e >>> 15, e | 1), e ^= e + Math.imul(e ^ e >>> 7, e | 61), ((e ^ e >>> 14) >>> 0) / 4294967296;
11
11
  };
12
12
  }
13
- function n(e, t, n) {
13
+ function o(e, t, n) {
14
14
  for (let r = 0; r < 6; r++) {
15
15
  let r = [...e];
16
16
  for (let e = r.length - 1; e > 0; e--) {
@@ -21,25 +21,70 @@ function n(e, t, n) {
21
21
  }
22
22
  return [...e.slice(1), e[0]];
23
23
  }
24
- var r = (e) => (t) => t.every((t, n) => t.id === e[n]);
25
- function i(t, i) {
26
- let a = structuredClone(t);
27
- switch (a.type) {
24
+ var s = (e) => (t) => t.every((t, n) => t.id === e[n]);
25
+ function c(e, t) {
26
+ let r = structuredClone(e);
27
+ switch (r.type) {
28
28
  case "mcq-single":
29
- case "mcq-multi": return a.shuffle !== !1 && (a.options = n(a.options, i)), a;
29
+ case "mcq-multi": return r.shuffle !== !1 && (r.options = o(r.options, t)), r;
30
30
  case "fill-in-select":
31
- for (let e of Object.values(a.blanks ?? {})) e.shuffle !== !1 && (e.options = n(e.options, i));
32
- return a;
33
- case "sort-items": return a.items = n(a.items, i, r(a.correctOrder)), a;
34
- case "match-pairs": return a.prompts = n(a.prompts, i), a.matches = n(a.matches, i), a;
35
- case "match-complex": return a.leftItems = n(a.leftItems, i), a.rightItems = n(a.rightItems, i), a;
36
- case "timeline": return a.shuffle !== !1 && (a.events = n(a.events, i, (t) => e(t))), a;
37
- case "categorize": return a.items = n(a.items, i), a;
38
- case "diagram-label": return a.requireTyping || (a.labels = n(a.labels, i)), a;
39
- default: return a;
31
+ for (let e of Object.values(r.blanks ?? {})) e.shuffle !== !1 && (e.options = o(e.options, t));
32
+ return r;
33
+ case "sort-items": return r.items = o(r.items, t, s(r.correctOrder)), r;
34
+ case "match-pairs": return r.prompts = o(r.prompts, t), r.matches = o(r.matches, t), r;
35
+ case "match-complex": return r.prompts = o(r.prompts, t), r.matches = o(r.matches, t), r;
36
+ case "timeline": return r.shuffle !== !1 && (r.events = o(r.events, t, (e) => n(e))), r;
37
+ case "categorize": return r.items = o(r.items, t), r;
38
+ case "diagram-label": return r.requireTyping || (r.labels = o(r.labels, t)), r;
39
+ default: return r;
40
40
  }
41
41
  }
42
- var a = ["explanation", "incorrectFeedback"], o = {
42
+ var l = (e) => typeof e == "object" && !!e && !Array.isArray(e);
43
+ function u(e, t) {
44
+ return e.find((e) => {
45
+ let n = e._zod.def.shape?.type;
46
+ return n?._zod.def.type === "literal" && n._zod.def.values.includes(t.type);
47
+ });
48
+ }
49
+ function d(e, t) {
50
+ let n = t._zod.def;
51
+ switch (n.type) {
52
+ case "optional":
53
+ case "nullable":
54
+ case "default":
55
+ case "prefault":
56
+ case "nonoptional":
57
+ case "readonly":
58
+ case "catch": return e == null ? e : d(e, n.innerType);
59
+ case "pipe": return d(e, n.in);
60
+ case "lazy": return d(e, n.getter());
61
+ case "object": {
62
+ if (!l(e)) return e;
63
+ let t = {};
64
+ for (let [r, i] of Object.entries(n.shape)) {
65
+ if (!(r in e)) continue;
66
+ let n = d(e[r], i);
67
+ n !== void 0 && (t[r] = n);
68
+ }
69
+ return t;
70
+ }
71
+ case "array": return Array.isArray(e) ? e.map((e) => d(e, n.element)) : e;
72
+ case "record": return l(e) ? ["unknown", "any"].includes(n.valueType._zod.def.type) ? void 0 : Object.fromEntries(Object.entries(e).map(([e, t]) => [e, d(t, n.valueType)])) : e;
73
+ case "intersection": return l(e) ? {
74
+ ...d(e, n.left),
75
+ ...d(e, n.right)
76
+ } : e;
77
+ case "union": {
78
+ if (!l(e) && !Array.isArray(e)) return e;
79
+ let t = n.options, r = l(e) && u(t, e) || t.find((t) => t.safeParse(e).success);
80
+ return r ? d(e, r) : Array.isArray(e) ? [] : {};
81
+ }
82
+ case "unknown":
83
+ case "any": return;
84
+ default: return e;
85
+ }
86
+ }
87
+ var f = ["explanation", "incorrectFeedback"], p = {
43
88
  note: ["hiddenContent"],
44
89
  flashcard: ["back"],
45
90
  "true-false": ["correctAnswer"],
@@ -51,66 +96,84 @@ var a = ["explanation", "incorrectFeedback"], o = {
51
96
  "match-complex": ["connections"],
52
97
  slider: ["correctAnswer"],
53
98
  "numeric-input": ["correctAnswer"],
54
- "math-input": ["correctAnswer", "alternativeAnswers"],
99
+ "math-input": ["correctAnswers"],
55
100
  matrix: ["correctCells"],
56
101
  "open-ended": ["sampleAnswer", "rubric"],
57
102
  "chess-puzzle": ["correctAnswers"]
58
- }, s = [
103
+ }, m = [
59
104
  "options",
60
105
  "items",
61
106
  "prompts",
62
107
  "matches",
63
- "leftItems",
64
- "rightItems",
65
108
  "categories",
66
109
  "rows",
67
110
  "columns",
68
111
  "labels"
69
112
  ];
70
- function c(e) {
71
- let t = e.type, n = structuredClone(e);
72
- for (let e of a) delete n[e];
73
- if (Array.isArray(n.sources)) for (let e of n.sources) e && typeof e == "object" && delete e.quote;
74
- if (t.startsWith("x-")) return delete n.solution, n.answerHidden = !0, n;
75
- for (let e of o[t] ?? []) delete n[e];
76
- for (let e of s) if (Array.isArray(n[e])) for (let t of n[e]) t && typeof t == "object" && delete t.explanation;
77
- switch (t) {
113
+ function h(t) {
114
+ let n = t.type, r = n.startsWith("x-") ? structuredClone(t) : d(structuredClone(t), e);
115
+ for (let e of f) delete r[e];
116
+ if (Array.isArray(r.sources)) for (let e of r.sources) e && typeof e == "object" && delete e.quote;
117
+ if (n.startsWith("x-")) return delete r.solution, r.answerHidden = !0, r;
118
+ for (let e of p[n] ?? []) delete r[e];
119
+ for (let e of m) if (Array.isArray(r[e])) for (let t of r[e]) t && typeof t == "object" && delete t.explanation;
120
+ switch (n) {
78
121
  case "fill-in-blanks":
79
- n.blanks = Object.fromEntries(Object.keys(n.blanks ?? {}).map((e) => [e, []]));
122
+ r.blanks = Object.fromEntries(Object.keys(r.blanks ?? {}).map((e) => [e, []]));
80
123
  break;
81
124
  case "fill-in-select":
82
- for (let e of Object.values(n.blanks ?? {})) {
125
+ for (let e of Object.values(r.blanks ?? {})) {
83
126
  delete e.correctId;
84
127
  for (let t of e.options ?? []) delete t.explanation;
85
128
  }
86
129
  break;
87
130
  case "categorize":
88
- for (let e of n.items ?? []) delete e.correctCategoryId;
131
+ for (let e of r.items ?? []) delete e.correctCategoryId;
89
132
  break;
90
133
  case "timeline":
91
- for (let e of n.events ?? []) delete e.date, delete e.precision;
134
+ for (let e of r.events ?? []) delete e.date, delete e.precision;
92
135
  break;
93
136
  case "diagram-label":
94
- for (let e of n.zones ?? []) delete e.correctLabelId;
95
- n.requireTyping && (n.labels = []);
137
+ for (let e of r.zones ?? []) delete e.correctLabelId;
138
+ r.requireTyping && (r.labels = []);
96
139
  break;
97
140
  case "pin-on-image":
98
- case "pin-on-model": n.hotspots = [];
141
+ case "pin-on-model": r.hotspots = [];
99
142
  }
100
- return n.answerHidden = !0, n;
143
+ if (r.assets && typeof r.assets == "object") {
144
+ let e = g(r);
145
+ r.assets = Object.fromEntries(Object.entries(r.assets).filter(([t]) => e.has(t.toLowerCase())));
146
+ }
147
+ return r.answerHidden = !0, r;
148
+ }
149
+ function g(e) {
150
+ let n = /* @__PURE__ */ new Set();
151
+ for (let [i, a] of r(e)) if (i[0] !== "assets") for (let e of t(a)) n.add(e);
152
+ let i = e.targetAsset;
153
+ return typeof i == "string" && n.add(i.toLowerCase()), n;
101
154
  }
102
- function l(e, n) {
103
- let r = n === void 0 ? null : t(n), a = e.items.map((e) => r ? i(e, r) : structuredClone(e));
155
+ function _(e, n) {
156
+ if (!e.assets) return e;
157
+ let r = /* @__PURE__ */ new Set();
158
+ for (let e of n) for (let t of g(e)) r.add(t);
159
+ for (let [n, a] of i(e)) if (n[1] !== "assets") for (let e of t(a)) r.add(e);
160
+ return e.thumbnail && r.add(e.thumbnail.toLowerCase()), {
161
+ ...e,
162
+ assets: Object.fromEntries(Object.entries(e.assets).filter(([e]) => r.has(e.toLowerCase())))
163
+ };
164
+ }
165
+ function v(e, t) {
166
+ let n = e.items.map((e) => t === void 0 ? structuredClone(e) : c(e, a(`${t}:${e.id}`))), r = n.map(h);
104
167
  return {
105
168
  set: {
106
169
  meta: e.meta,
107
- items: a
170
+ items: n
108
171
  },
109
172
  publicSet: {
110
- meta: e.meta,
111
- items: a.map(c)
173
+ meta: _(e.meta, r),
174
+ items: r
112
175
  }
113
176
  };
114
177
  }
115
178
  //#endregion
116
- export { i as displayItem, l as prepareDisplaySet, c as publicItem, t as seededRandom };
179
+ export { c as displayItem, v as prepareDisplaySet, h as publicItem, a as seededRandom };
package/dist/limits.cjs CHANGED
@@ -1 +1 @@
1
- const e=require("./errors.cjs");var t={messageBytes:65536,messagesPerSecond:30,messageBurst:60,snapshotBytes:1048576,snapshotsPerSecond:2,dataBytes:262144,dataWritesPerSecond:1,helloTimeoutMs:1e4,answerBytes:4096,generatedItemBytes:32768,serviceBytes:65536,servicesPerMinute:30},n=64;function r(e,t=`$`,i=0,a=new Set){if(e===null||typeof e==`string`||typeof e==`boolean`)return null;if(typeof e==`number`)return Number.isFinite(e)?null:`${t} is not a finite number`;if(typeof e!=`object`)return`${t} is a ${typeof e}`;if(i>=n)return`${t} is nested deeper than ${n} levels`;if(a.has(e))return`${t} contains a circular reference`;if(Array.isArray(e)){a.add(e);for(let n=0;n<e.length;n++){let o=r(e[n],`${t}[${n}]`,i+1,a);if(o)return o}return a.delete(e),null}let o=Object.getPrototypeOf(e);if(o!==Object.prototype&&o!==null)return`${t} is not a plain object`;a.add(e);for(let[n,o]of Object.entries(e)){let e=r(o,`${t}.${n}`,i+1,a);if(e)return e}return a.delete(e),null}function i(e){return new TextEncoder().encode(JSON.stringify(e)).length}function a(t,n,a,o){let s=r(t);if(s)throw new e.ProtocolError(`INVALID_ARGUMENT`,`${o} must be plain JSON: ${s}.`);let c=i(t);if(c>n)throw new e.ProtocolError(a,`${o} is ${c} bytes; the limit is ${n} bytes.`);return c}var o=class{rate;burst;now;tokens;last;constructor(e,t,n=()=>Date.now()){this.rate=e,this.burst=t,this.now=n,this.tokens=t,this.last=n()}tryTake(e=1){let t=this.now();return this.tokens=Math.min(this.burst,this.tokens+(t-this.last)/1e3*this.rate),this.last=t,this.tokens<e?!1:(this.tokens-=e,!0)}};exports.LIMITS=t,exports.RateLimiter=o,exports.assertJsonWithin=a,exports.findNonJson=r,exports.jsonByteSize=i;
1
+ const e=require("./errors.cjs");var t={messageBytes:65536,messagesPerSecond:30,messageBurst:60,snapshotBytes:1048576,snapshotsPerSecond:2,dataBytes:262144,dataWritesPerSecond:1,helloTimeoutMs:1e4,answerBytes:4096,generatedItemBytes:32768,answersPerSecond:10,answerBurst:20,serviceBytes:65536,serviceResultBytes:262144,servicesPerMinute:30,assetsPerSecond:10,assetBurst:20,assetBytes:2097152,errorReportsPerSecond:5,errorReportBurst:10},n=64;function r(e,t=`$`,i=0,a=new Set){if(e===null||typeof e==`string`||typeof e==`boolean`)return null;if(typeof e==`number`)return Number.isFinite(e)?null:`${t} is not a finite number`;if(typeof e!=`object`)return`${t} is a ${typeof e}`;if(i>=n)return`${t} is nested deeper than ${n} levels`;if(a.has(e))return`${t} contains a circular reference`;if(Array.isArray(e)){a.add(e);for(let n=0;n<e.length;n++){let o=r(e[n],`${t}[${n}]`,i+1,a);if(o)return o}return a.delete(e),null}let o=Object.getPrototypeOf(e);if(o!==Object.prototype&&o!==null)return`${t} is not a plain object`;a.add(e);for(let[n,o]of Object.entries(e)){let e=r(o,`${t}.${n}`,i+1,a);if(e)return e}return a.delete(e),null}function i(e){return new TextEncoder().encode(JSON.stringify(e)).length}function a(t,n,a,o){let s=r(t);if(s)throw new e.ProtocolError(`INVALID_ARGUMENT`,`${o} must be plain JSON: ${s}.`);let c=i(t);if(c>n)throw new e.ProtocolError(a,`${o} is ${c} bytes; the limit is ${n} bytes.`);return c}var o=class{rate;burst;now;tokens;last;constructor(e,t,n=()=>Date.now()){this.rate=e,this.burst=t,this.now=n,this.tokens=t,this.last=n()}tryTake(e=1){let t=this.now();return this.tokens=Math.min(this.burst,this.tokens+(t-this.last)/1e3*this.rate),this.last=t,this.tokens<e?!1:(this.tokens-=e,!0)}};exports.LIMITS=t,exports.RateLimiter=o,exports.assertJsonWithin=a,exports.findNonJson=r,exports.jsonByteSize=i;
package/dist/limits.js CHANGED
@@ -11,8 +11,16 @@ var t = {
11
11
  helloTimeoutMs: 1e4,
12
12
  answerBytes: 4096,
13
13
  generatedItemBytes: 32768,
14
+ answersPerSecond: 10,
15
+ answerBurst: 20,
14
16
  serviceBytes: 65536,
15
- servicesPerMinute: 30
17
+ serviceResultBytes: 262144,
18
+ servicesPerMinute: 30,
19
+ assetsPerSecond: 10,
20
+ assetBurst: 20,
21
+ assetBytes: 2097152,
22
+ errorReportsPerSecond: 5,
23
+ errorReportBurst: 10
16
24
  }, n = 64;
17
25
  function r(e, t = "$", i = 0, a = /* @__PURE__ */ new Set()) {
18
26
  if (e === null || typeof e == "string" || typeof e == "boolean") return null;
@@ -1,2 +1,2 @@
1
- const e=require("./settingsSchema.cjs"),t=require("./pluginRuntime.cjs");let n=require("zod"),r=require("@memizy/oqse");var i=n.z.string().regex(/^\d+\.\d+$/,`Protocol version must be in MAJOR.MINOR format (e.g. "1.0")`),a=n.z.enum([`presenter`,`player`]),o=n.z.looseObject({players:n.z.looseObject({min:n.z.number().int().min(1,`players.min must be at least 1`),max:n.z.number().int().max(200,`players.max must be at most 200`),recommended:n.z.number().int().positive().optional()}).refine(e=>e.min<=e.max,{message:`players.min must not be greater than players.max`,path:[`min`]}).refine(e=>e.recommended===void 0||e.recommended>=e.min&&e.recommended<=e.max,{message:`players.recommended must be between min and max`,path:[`recommended`]}),hostAs:n.z.array(a).min(1,`hostAs must contain "presenter" and/or "player"`).refine(e=>new Set(e).size===e.length,`hostAs must not contain duplicates`),lateJoin:n.z.boolean().optional()}),s=n.z.string().regex(/^(https|wss):\/\/[a-z0-9.-]+(:\d{1,5})?$/i,`A network origin is "https://host" or "wss://host" (no path)`),c=n.z.string().max(100).regex(/^[a-z][a-z0-9-]*(\.[a-z0-9][a-z0-9-]*)*$/,`A service name is lowercase segments joined by dots (e.g. "chess.puzzles")`),l=n.z.looseObject({network:n.z.array(s).max(20).optional(),devices:n.z.array(n.z.enum([`camera`,`microphone`,`geolocation`,`serial`,`bluetooth`])).max(5).optional()}),u=n.z.looseObject({protocol:i,set:n.z.enum([`required`,`optional`,`none`]).optional(),modes:n.z.looseObject({solo:n.z.looseObject({}).optional(),multiplayer:o.optional()}),settings:n.z.array(e.SettingDefinitionSchema).optional(),settingsScreen:n.z.looseObject({size:n.z.enum([`compact`,`large`])}).optional(),display:n.z.looseObject({orientation:n.z.enum([`any`,`portrait`,`landscape`]).optional()}).optional(),permissions:l.optional(),services:n.z.array(c).max(20).optional()}).refine(e=>e.modes.solo!==void 0||e.modes.multiplayer!==void 0,{message:`modes must declare "solo" and/or "multiplayer"`,path:[`modes`]}).refine(e=>e.settingsScreen===void 0||e.modes.multiplayer!==void 0,{message:`settingsScreen is only used in the multiplayer lobby; declare modes.multiplayer or remove it`,path:[`settingsScreen`]}).refine(e=>{let t=(e.settings??[]).map(e=>e.id);return new Set(t).size===t.length},{message:`Setting ids must be unique`,path:[`settings`]});function d(e){let n=[],i=r.OQSEManifestSchema.safeParse(e);i.success||n.push(...(0,r.formatOQSEErrors)(i.error));let a=e?.appSpecific?.memizy;if(a===void 0)n.push(`appSpecific.memizy: Missing the Memizy runtime block (protocol, modes, …)`);else{let e=u.safeParse(a);e.success||n.push(...(0,r.formatOQSEErrors)(e.error).map(e=>`appSpecific.memizy.${e}`))}if(n.length>0)return{success:!1,errors:n};let o=(0,r.withManifestDefaults)(e),s=t.toRuntime(o.appSpecific.memizy),c=o.capabilities.types;return s.set!==`none`&&(!Array.isArray(c)||c.length===0)?{success:!1,errors:[`capabilities.types: Declare the item types the game shows, or "set": "none" in appSpecific.memizy for a game without questions.`]}:{success:!0,manifest:o,runtime:s}}var f=class extends Error{errors;constructor(e){super(`Invalid plugin manifest:\n${e.join(`
1
+ const e=require("./settingsSchema.cjs"),t=require("./pluginRuntime.cjs");let n=require("zod"),r=require("@memizy/oqse");var i=n.z.string().regex(/^\d+\.\d+$/,`Protocol version must be in MAJOR.MINOR format (e.g. "1.0")`),a=n.z.enum([`presenter`,`player`]),o=n.z.looseObject({players:n.z.looseObject({min:n.z.number().int().min(1,`players.min must be at least 1`),max:n.z.number().int().max(200,`players.max must be at most 200`),recommended:n.z.number().int().positive().optional()}).refine(e=>e.min<=e.max,{message:`players.min must not be greater than players.max`,path:[`min`]}).refine(e=>e.recommended===void 0||e.recommended>=e.min&&e.recommended<=e.max,{message:`players.recommended must be between min and max`,path:[`recommended`]}),hostAs:n.z.array(a).min(1,`hostAs must contain "presenter" and/or "player"`).refine(e=>new Set(e).size===e.length,`hostAs must not contain duplicates`),lateJoin:n.z.boolean().optional()}),s=n.z.string().regex(/^(https|wss):\/\/[a-z0-9.-]+(:\d{1,5})?$/i,`A network origin is "https://host" or "wss://host" (no path)`),c=n.z.string().max(100).regex(/^[a-z][a-z0-9-]*(\.[a-z0-9][a-z0-9-]*)*$/,`A service name is lowercase segments joined by dots (e.g. "chess.puzzles")`),l=n.z.looseObject({network:n.z.array(s).max(20).optional(),devices:n.z.array(n.z.enum([`camera`,`microphone`,`geolocation`,`serial`,`bluetooth`])).max(5).optional()}),u=n.z.looseObject({protocol:i,set:n.z.enum([`required`,`optional`,`none`]).optional(),modes:n.z.looseObject({solo:n.z.looseObject({}).optional(),multiplayer:o.optional()}),settings:n.z.array(e.SettingEntrySchema).optional(),settingsScreen:n.z.looseObject({size:n.z.enum([`compact`,`large`])}).optional(),display:n.z.looseObject({orientation:n.z.enum([`any`,`portrait`,`landscape`]).optional()}).optional(),permissions:l.optional(),services:n.z.array(c).max(20).optional()}).refine(e=>e.modes.solo!==void 0||e.modes.multiplayer!==void 0,{message:`modes must declare "solo" and/or "multiplayer"`,path:[`modes`]}).refine(e=>e.settingsScreen===void 0||e.modes.multiplayer!==void 0,{message:`settingsScreen is only used in the multiplayer lobby; declare modes.multiplayer or remove it`,path:[`settingsScreen`]}).refine(e=>{let t=(e.settings??[]).map(e=>e.id);return new Set(t).size===t.length},{message:`Setting ids must be unique`,path:[`settings`]});function d(e){let n=[],i=r.OQSEManifestSchema.safeParse(e);i.success||n.push(...(0,r.formatOQSEErrors)(i.error));let a=e?.appSpecific?.memizy;if(a===void 0)n.push(`appSpecific.memizy: Missing the Memizy runtime block (protocol, modes, …)`);else{let e=u.safeParse(a);e.success||n.push(...(0,r.formatOQSEErrors)(e.error).map(e=>`appSpecific.memizy.${e}`))}if(n.length>0)return{success:!1,errors:n};let o=(0,r.withManifestDefaults)(e),s=t.toRuntime(o.appSpecific.memizy),c=o.capabilities.types;return s.set!==`none`&&(!Array.isArray(c)||c.length===0)?{success:!1,errors:[`capabilities.types: Declare the item types the game shows, or "set": "none" in appSpecific.memizy for a game without questions.`]}:{success:!0,manifest:o,runtime:s}}var f=class extends Error{errors;constructor(e){super(`Invalid plugin manifest:\n${e.join(`
2
2
  `)}`),this.name=`PluginManifestError`,this.errors=e}};function p(e){let t=d(e);if(!t.success)throw new f(t.errors);return{manifest:t.manifest,runtime:t.runtime}}function m(e){let n=t.extractManifestFromHtml(e);return n.success?d(n.data):{success:!1,errors:[n.error]}}exports.HostAsSchema=a,exports.MemizyRuntimeSchema=u,exports.MultiplayerModeSchema=o,exports.PluginManifestError=f,exports.PluginPermissionsSchema=l,exports.ServiceNameSchema=c,exports.parsePluginManifest=p,exports.readPluginManifestFromHtml=m,exports.safeParsePluginManifest=d;
@@ -1,4 +1,4 @@
1
- import { SettingDefinitionSchema as e } from "./settingsSchema.js";
1
+ import { SettingEntrySchema as e } from "./settingsSchema.js";
2
2
  import { extractManifestFromHtml as t, toRuntime as n } from "./pluginRuntime.js";
3
3
  import { z as r } from "zod";
4
4
  import { OQSEManifestSchema as i, formatOQSEErrors as a, withManifestDefaults as o } from "@memizy/oqse";
@@ -1 +1 @@
1
- var e=/<script\b[^>]*\btype\s*=\s*["']application\/oqse-manifest\+json["'][^>]*>([\s\S]*?)<\/script\s*>/i;function t(t){let n=e.exec(t);if(!n)return{success:!1,error:`No <script type="application/oqse-manifest+json"> data island found.`};try{return{success:!0,data:JSON.parse(n[1])}}catch(e){return{success:!1,error:`The manifest data island is not valid JSON: ${e.message}`}}}function n(e){let t=e.modes.multiplayer,n=t?{players:{min:t.players.min,max:t.players.max,...t.players.recommended!==void 0&&{recommended:t.players.recommended}},hostAs:[...t.hostAs],lateJoin:t.lateJoin??!0}:null,r=[];return e.modes.solo&&r.push(`solo`),n?.hostAs.includes(`presenter`)&&r.push(`board`),n&&r.push(`controller`),n&&e.settingsScreen&&r.push(`settings`),{protocol:e.protocol,set:e.set??`required`,solo:e.modes.solo!==void 0,multiplayer:n,settings:e.settings??[],settingsScreen:e.settingsScreen?{size:e.settingsScreen.size}:null,orientation:e.display?.orientation??`any`,views:r,permissions:{network:[...new Set(e.permissions?.network??[])],devices:[...new Set(e.permissions?.devices??[])]},services:[...new Set(e.services??[])]}}exports.extractManifestFromHtml=t,exports.toRuntime=n;
1
+ const e=require("./settings.cjs");var t=/<script\b[^>]*\btype\s*=\s*["']application\/oqse-manifest\+json["'][^>]*>([\s\S]*?)<\/script\s*>/i;function n(e){let n=t.exec(e);if(!n)return{success:!1,error:`No <script type="application/oqse-manifest+json"> data island found.`};try{return{success:!0,data:JSON.parse(n[1])}}catch(e){return{success:!1,error:`The manifest data island is not valid JSON: ${e.message}`}}}function r(t){let n=t.modes.multiplayer,r=n?{players:{min:n.players.min,max:n.players.max,...n.players.recommended!==void 0&&{recommended:n.players.recommended}},hostAs:[...n.hostAs],lateJoin:n.lateJoin??!0}:null,i=[];return t.modes.solo&&i.push(`solo`),r?.hostAs.includes(`presenter`)&&i.push(`board`),r&&i.push(`controller`),r&&t.settingsScreen&&i.push(`settings`),{protocol:t.protocol,set:t.set??`required`,solo:t.modes.solo!==void 0,multiplayer:r,settings:(t.settings??[]).filter(e.isKnownSetting),unknownSettings:(t.settings??[]).filter(t=>!e.isKnownSetting(t)),settingsScreen:t.settingsScreen?{size:t.settingsScreen.size}:null,orientation:t.display?.orientation??`any`,views:i,permissions:{network:[...new Set(t.permissions?.network??[])],devices:[...new Set(t.permissions?.devices??[])]},services:[...new Set(t.services??[])]}}exports.extractManifestFromHtml=n,exports.toRuntime=r;
@@ -1,7 +1,8 @@
1
+ import { isKnownSetting as e } from "./settings.js";
1
2
  //#region src/pluginRuntime.ts
2
- var e = /<script\b[^>]*\btype\s*=\s*["']application\/oqse-manifest\+json["'][^>]*>([\s\S]*?)<\/script\s*>/i;
3
- function t(t) {
4
- let n = e.exec(t);
3
+ var t = /<script\b[^>]*\btype\s*=\s*["']application\/oqse-manifest\+json["'][^>]*>([\s\S]*?)<\/script\s*>/i;
4
+ function n(e) {
5
+ let n = t.exec(e);
5
6
  if (!n) return {
6
7
  success: !1,
7
8
  error: "No <script type=\"application/oqse-manifest+json\"> data island found."
@@ -18,31 +19,32 @@ function t(t) {
18
19
  };
19
20
  }
20
21
  }
21
- function n(e) {
22
- let t = e.modes.multiplayer, n = t ? {
22
+ function r(t) {
23
+ let n = t.modes.multiplayer, r = n ? {
23
24
  players: {
24
- min: t.players.min,
25
- max: t.players.max,
26
- ...t.players.recommended !== void 0 && { recommended: t.players.recommended }
25
+ min: n.players.min,
26
+ max: n.players.max,
27
+ ...n.players.recommended !== void 0 && { recommended: n.players.recommended }
27
28
  },
28
- hostAs: [...t.hostAs],
29
- lateJoin: t.lateJoin ?? !0
30
- } : null, r = [];
31
- return e.modes.solo && r.push("solo"), n?.hostAs.includes("presenter") && r.push("board"), n && r.push("controller"), n && e.settingsScreen && r.push("settings"), {
32
- protocol: e.protocol,
33
- set: e.set ?? "required",
34
- solo: e.modes.solo !== void 0,
35
- multiplayer: n,
36
- settings: e.settings ?? [],
37
- settingsScreen: e.settingsScreen ? { size: e.settingsScreen.size } : null,
38
- orientation: e.display?.orientation ?? "any",
39
- views: r,
29
+ hostAs: [...n.hostAs],
30
+ lateJoin: n.lateJoin ?? !0
31
+ } : null, i = [];
32
+ return t.modes.solo && i.push("solo"), r?.hostAs.includes("presenter") && i.push("board"), r && i.push("controller"), r && t.settingsScreen && i.push("settings"), {
33
+ protocol: t.protocol,
34
+ set: t.set ?? "required",
35
+ solo: t.modes.solo !== void 0,
36
+ multiplayer: r,
37
+ settings: (t.settings ?? []).filter(e),
38
+ unknownSettings: (t.settings ?? []).filter((t) => !e(t)),
39
+ settingsScreen: t.settingsScreen ? { size: t.settingsScreen.size } : null,
40
+ orientation: t.display?.orientation ?? "any",
41
+ views: i,
40
42
  permissions: {
41
- network: [...new Set(e.permissions?.network ?? [])],
42
- devices: [...new Set(e.permissions?.devices ?? [])]
43
+ network: [...new Set(t.permissions?.network ?? [])],
44
+ devices: [...new Set(t.permissions?.devices ?? [])]
43
45
  },
44
- services: [...new Set(e.services ?? [])]
46
+ services: [...new Set(t.services ?? [])]
45
47
  };
46
48
  }
47
49
  //#endregion
48
- export { t as extractManifestFromHtml, n as toRuntime };
50
+ export { n as extractManifestFromHtml, r as toRuntime };
package/dist/relay.cjs CHANGED
@@ -1 +1 @@
1
- var e=1,t={frameBytes:2097152,bundleBytes:8388608,playersPerRoom:100,playerFramesPerSecond:60,playerFrameBurst:120,hostFramesPerSecond:3e3,hostFrameBurst:6e3,nameLength:32,hostAwayMs:6e5,roomTtlMs:216e5};function n(e){return typeof e==`string`?e.replace(/\s+/g,` `).trim().slice(0,t.nameLength):``}function r(e){return typeof e==`string`&&/^\d{6}$/.test(e)}exports.RELAY_LIMITS=t,exports.RELAY_PROTOCOL=e,exports.isPin=r,exports.normalizePlayerName=n;
1
+ var e=1,t={frameBytes:3145728,bundleBytes:8388608,playersPerRoom:100,playerFramesPerSecond:60,playerFrameBurst:120,hostFramesPerSecond:3e3,hostFrameBurst:6e3,nameLength:32,authLength:4096,hostAwayMs:6e5,roomTtlMs:216e5};function n(e){return typeof e==`string`?e.replace(/\s+/g,` `).trim().slice(0,t.nameLength):``}function r(e){return typeof e==`string`&&/^\d{6}$/.test(e)}exports.RELAY_LIMITS=t,exports.RELAY_PROTOCOL=e,exports.isPin=r,exports.normalizePlayerName=n;
package/dist/relay.js CHANGED
@@ -1,6 +1,6 @@
1
1
  //#region src/relay.ts
2
2
  var e = 1, t = {
3
- frameBytes: 2097152,
3
+ frameBytes: 3145728,
4
4
  bundleBytes: 8388608,
5
5
  playersPerRoom: 100,
6
6
  playerFramesPerSecond: 60,
@@ -8,6 +8,7 @@ var e = 1, t = {
8
8
  hostFramesPerSecond: 3e3,
9
9
  hostFrameBurst: 6e3,
10
10
  nameLength: 32,
11
+ authLength: 4096,
11
12
  hostAwayMs: 6e5,
12
13
  roomTtlMs: 216e5
13
14
  };
package/dist/schemas.cjs CHANGED
@@ -1 +1 @@
1
- const e=require("./errors.cjs");let t=require("zod"),n=require("@memizy/oqse");var r=e=>t.z.string().min(1).max(e),i=t.z.string().min(1).max(128),a=t.z.looseObject({protocol:t.z.string().regex(/^\d+\.\d+$/,`protocol must be in MAJOR.MINOR format`),sdk:t.z.looseObject({name:r(200),version:r(100)}),plugin:t.z.looseObject({id:r(500),version:r(100)}),features:t.z.array(r(100)).max(100)}),o=t.z.looseObject({to:t.z.union([t.z.literal(`authority`),t.z.literal(`all`),t.z.array(i).min(1).max(500)]),data:t.z.unknown()}),s=t.z.looseObject({playerId:i.optional(),itemId:r(128),isCorrect:t.z.boolean(),confidence:t.z.union([t.z.literal(1),t.z.literal(2),t.z.literal(3),t.z.literal(4)]).optional(),timeSpentMs:t.z.number().int().nonnegative().optional(),hintsUsed:t.z.number().int().nonnegative().optional(),isSkipped:t.z.boolean().optional(),answer:t.z.unknown().optional(),item:t.z.looseObject({id:r(128),type:r(64)}).optional()}).refine(e=>!e.item||e.item.id===e.itemId,{message:`item.id must equal itemId`,path:[`item`,`id`]}),c=t.z.record(r(128),n.ProgressRecordSchema),l=t.z.enum([`plugin`,`set`]),u=t.z.looseObject({values:t.z.record(t.z.string(),t.z.unknown()),valid:t.z.boolean(),message:t.z.string().max(500).optional()}),d=t.z.looseObject({scores:t.z.record(i,t.z.number().finite()).optional(),summary:t.z.string().max(1e3).optional()}),f=t.z.looseObject({height:t.z.union([t.z.number().positive().max(1e5),t.z.literal(`auto`)])}),p=t.z.looseObject({code:r(100),message:t.z.string().max(2e3),context:t.z.record(t.z.string(),t.z.unknown()).optional()}),m={hello:t.z.tuple([a]),ready:t.z.tuple([]),send:t.z.tuple([o]),saveSnapshot:t.z.tuple([t.z.unknown()]),recordAnswer:t.z.tuple([s]),saveProgress:t.z.tuple([c]),saveData:t.z.tuple([l,t.z.unknown()]),updateSettings:t.z.tuple([u]),getAsset:t.z.tuple([r(200),r(128).optional()]),end:t.z.tuple([d]),resize:t.z.tuple([f]),reportError:t.z.tuple([p]),exit:t.z.tuple([]),service:t.z.tuple([r(100),t.z.unknown()])};function h(t,r){let i=m[t].safeParse(r);if(!i.success)throw new e.ProtocolError(`INVALID_ARGUMENT`,`${t}: ${(0,n.formatOQSEErrors)(i.error).join(`; `)}`);return i.data}exports.AddressSchema=i,exports.AnswerRecordSchema=s,exports.DataScopeSchema=l,exports.ErrorReportSchema=p,exports.HOST_API_ARGS=m,exports.HandshakeSchema=a,exports.OutgoingMessageSchema=o,exports.ProgressRecordsSchema=c,exports.ResizeRequestSchema=f,exports.SessionResultSchema=d,exports.SettingsUpdateSchema=u,exports.parseHostCall=h;
1
+ const e=require("./errors.cjs");let t=require("zod"),n=require("@memizy/oqse");var r=e=>t.z.string().min(1).max(e),i=t.z.string().min(1).max(128),a=t.z.looseObject({protocol:t.z.string().regex(/^\d+\.\d+$/,`protocol must be in MAJOR.MINOR format`),sdk:t.z.looseObject({name:r(200),version:r(100)}),plugin:t.z.looseObject({id:r(500),version:r(100)}),features:t.z.array(r(100)).max(100)}),o=t.z.looseObject({to:t.z.union([t.z.literal(`authority`),t.z.literal(`all`),t.z.array(i).min(1).max(500)]),data:t.z.unknown()}),s=t.z.looseObject({playerId:i.optional(),itemId:r(128),isCorrect:t.z.boolean(),confidence:t.z.union([t.z.literal(1),t.z.literal(2),t.z.literal(3),t.z.literal(4)]).optional(),timeSpentMs:t.z.number().int().nonnegative().optional(),hintsUsed:t.z.number().int().nonnegative().optional(),isSkipped:t.z.boolean().optional(),score:t.z.number().min(0).max(1).optional(),answer:t.z.unknown().optional(),item:t.z.looseObject({id:r(128),type:r(64)}).optional()}).refine(e=>!e.item||e.item.id===e.itemId,{message:`item.id must equal itemId`,path:[`item`,`id`]}),c=t.z.enum([`plugin`,`set`]),l=t.z.looseObject({values:t.z.record(t.z.string(),t.z.unknown()),valid:t.z.boolean(),message:t.z.string().max(500).optional()}),u=t.z.looseObject({scores:t.z.record(i,t.z.number().finite()).optional(),summary:t.z.string().max(1e3).optional()}),d=t.z.looseObject({code:r(100),message:t.z.string().max(2e3),context:t.z.record(t.z.string(),t.z.unknown()).optional()}),f={hello:t.z.tuple([a]),ready:t.z.tuple([]),send:t.z.tuple([o]),saveSnapshot:t.z.tuple([t.z.unknown()]),recordAnswer:t.z.tuple([s]),saveData:t.z.tuple([c,t.z.unknown()]),updateSettings:t.z.tuple([l]),getAsset:t.z.tuple([r(200),r(128).optional()]),end:t.z.tuple([u]),reportError:t.z.tuple([d]),service:t.z.tuple([r(100),t.z.unknown()])};function p(t,r){let i=f[t].safeParse(r);if(!i.success)throw new e.ProtocolError(`INVALID_ARGUMENT`,`${t}: ${(0,n.formatOQSEErrors)(i.error).join(`; `)}`);return i.data}exports.AddressSchema=i,exports.AnswerRecordSchema=s,exports.DataScopeSchema=c,exports.ErrorReportSchema=d,exports.HOST_API_ARGS=f,exports.HandshakeSchema=a,exports.OutgoingMessageSchema=o,exports.SessionResultSchema=u,exports.SettingsUpdateSchema=l,exports.parseHostCall=p;
package/dist/schemas.js CHANGED
@@ -1,28 +1,28 @@
1
1
  import { ProtocolError as e } from "./errors.js";
2
2
  import { z as t } from "zod";
3
- import { ProgressRecordSchema as n, formatOQSEErrors as r } from "@memizy/oqse";
3
+ import { formatOQSEErrors as n } from "@memizy/oqse";
4
4
  //#region src/schemas.ts
5
- var i = (e) => t.string().min(1).max(e), a = t.string().min(1).max(128), o = t.looseObject({
5
+ var r = (e) => t.string().min(1).max(e), i = t.string().min(1).max(128), a = t.looseObject({
6
6
  protocol: t.string().regex(/^\d+\.\d+$/, "protocol must be in MAJOR.MINOR format"),
7
7
  sdk: t.looseObject({
8
- name: i(200),
9
- version: i(100)
8
+ name: r(200),
9
+ version: r(100)
10
10
  }),
11
11
  plugin: t.looseObject({
12
- id: i(500),
13
- version: i(100)
12
+ id: r(500),
13
+ version: r(100)
14
14
  }),
15
- features: t.array(i(100)).max(100)
16
- }), s = t.looseObject({
15
+ features: t.array(r(100)).max(100)
16
+ }), o = t.looseObject({
17
17
  to: t.union([
18
18
  t.literal("authority"),
19
19
  t.literal("all"),
20
- t.array(a).min(1).max(500)
20
+ t.array(i).min(1).max(500)
21
21
  ]),
22
22
  data: t.unknown()
23
- }), c = t.looseObject({
24
- playerId: a.optional(),
25
- itemId: i(128),
23
+ }), s = t.looseObject({
24
+ playerId: i.optional(),
25
+ itemId: r(128),
26
26
  isCorrect: t.boolean(),
27
27
  confidence: t.union([
28
28
  t.literal(1),
@@ -33,45 +33,43 @@ var i = (e) => t.string().min(1).max(e), a = t.string().min(1).max(128), o = t.l
33
33
  timeSpentMs: t.number().int().nonnegative().optional(),
34
34
  hintsUsed: t.number().int().nonnegative().optional(),
35
35
  isSkipped: t.boolean().optional(),
36
+ score: t.number().min(0).max(1).optional(),
36
37
  answer: t.unknown().optional(),
37
38
  item: t.looseObject({
38
- id: i(128),
39
- type: i(64)
39
+ id: r(128),
40
+ type: r(64)
40
41
  }).optional()
41
42
  }).refine((e) => !e.item || e.item.id === e.itemId, {
42
43
  message: "item.id must equal itemId",
43
44
  path: ["item", "id"]
44
- }), l = t.record(i(128), n), u = t.enum(["plugin", "set"]), d = t.looseObject({
45
+ }), c = t.enum(["plugin", "set"]), l = t.looseObject({
45
46
  values: t.record(t.string(), t.unknown()),
46
47
  valid: t.boolean(),
47
48
  message: t.string().max(500).optional()
48
- }), f = t.looseObject({
49
- scores: t.record(a, t.number().finite()).optional(),
49
+ }), u = t.looseObject({
50
+ scores: t.record(i, t.number().finite()).optional(),
50
51
  summary: t.string().max(1e3).optional()
51
- }), p = t.looseObject({ height: t.union([t.number().positive().max(1e5), t.literal("auto")]) }), m = t.looseObject({
52
- code: i(100),
52
+ }), d = t.looseObject({
53
+ code: r(100),
53
54
  message: t.string().max(2e3),
54
55
  context: t.record(t.string(), t.unknown()).optional()
55
- }), h = {
56
- hello: t.tuple([o]),
56
+ }), f = {
57
+ hello: t.tuple([a]),
57
58
  ready: t.tuple([]),
58
- send: t.tuple([s]),
59
+ send: t.tuple([o]),
59
60
  saveSnapshot: t.tuple([t.unknown()]),
60
- recordAnswer: t.tuple([c]),
61
- saveProgress: t.tuple([l]),
62
- saveData: t.tuple([u, t.unknown()]),
63
- updateSettings: t.tuple([d]),
64
- getAsset: t.tuple([i(200), i(128).optional()]),
65
- end: t.tuple([f]),
66
- resize: t.tuple([p]),
67
- reportError: t.tuple([m]),
68
- exit: t.tuple([]),
69
- service: t.tuple([i(100), t.unknown()])
61
+ recordAnswer: t.tuple([s]),
62
+ saveData: t.tuple([c, t.unknown()]),
63
+ updateSettings: t.tuple([l]),
64
+ getAsset: t.tuple([r(200), r(128).optional()]),
65
+ end: t.tuple([u]),
66
+ reportError: t.tuple([d]),
67
+ service: t.tuple([r(100), t.unknown()])
70
68
  };
71
- function g(t, n) {
72
- let i = h[t].safeParse(n);
73
- if (!i.success) throw new e("INVALID_ARGUMENT", `${t}: ${r(i.error).join("; ")}`);
69
+ function p(t, r) {
70
+ let i = f[t].safeParse(r);
71
+ if (!i.success) throw new e("INVALID_ARGUMENT", `${t}: ${n(i.error).join("; ")}`);
74
72
  return i.data;
75
73
  }
76
74
  //#endregion
77
- export { a as AddressSchema, c as AnswerRecordSchema, u as DataScopeSchema, m as ErrorReportSchema, h as HOST_API_ARGS, o as HandshakeSchema, s as OutgoingMessageSchema, l as ProgressRecordsSchema, p as ResizeRequestSchema, f as SessionResultSchema, d as SettingsUpdateSchema, g as parseHostCall };
75
+ export { i as AddressSchema, s as AnswerRecordSchema, c as DataScopeSchema, d as ErrorReportSchema, f as HOST_API_ARGS, a as HandshakeSchema, o as OutgoingMessageSchema, u as SessionResultSchema, l as SettingsUpdateSchema, p as parseHostCall };
package/dist/settings.cjs CHANGED
@@ -1 +1 @@
1
- function e(e,t){switch(e.type){case`number`:return typeof t!=`number`||!Number.isFinite(t)?`must be a number`:e.min!==void 0&&t<e.min?`must be at least ${e.min}`:e.max!==void 0&&t>e.max?`must be at most ${e.max}`:null;case`boolean`:return typeof t==`boolean`?null:`must be true or false`;case`select`:return e.options.some(e=>e.value===t)?null:`must be one of the options`;case`text`:return typeof t==`string`?e.maxLength!==void 0&&t.length>e.maxLength?`must be at most ${e.maxLength} characters`:null:`must be text`}}function t(t,n={}){let r={},i=[];for(let a of t){if(!(a.id in n)){r[a.id]=a.default;continue}let t=e(a,n[a.id]);t?(i.push(`${a.id}: ${t}`),r[a.id]=a.default):r[a.id]=n[a.id]}for(let e of Object.keys(n))t.some(t=>t.id===e)||i.push(`${e}: unknown setting`);return{values:r,errors:i}}function n(e,t){return e.filter(e=>!e.modes||e.modes.includes(t))}function r(e,t){if(typeof e==`string`)return e;let n=t.split(`-`)[0];return e[t]??e[n]??e.en??Object.values(e)[0]}exports.localize=r,exports.resolveSettings=t,exports.settingsForMode=n,exports.validateSettingValue=e;
1
+ var e=[`number`,`boolean`,`select`,`text`];function t(t){return e.includes(t.type)}function n(e,t){switch(e.type){case`number`:return typeof t!=`number`||!Number.isFinite(t)?`must be a number`:e.min!==void 0&&t<e.min?`must be at least ${e.min}`:e.max!==void 0&&t>e.max?`must be at most ${e.max}`:null;case`boolean`:return typeof t==`boolean`?null:`must be true or false`;case`select`:return e.options.some(e=>e.value===t)?null:`must be one of the options`;case`text`:return typeof t==`string`?e.maxLength!==void 0&&t.length>e.maxLength?`must be at most ${e.maxLength} characters`:null:`must be text`}}function r(e,t={},r=[]){let i={},a=[];for(let r of e){if(!(r.id in t)){i[r.id]=r.default;continue}let e=n(r,t[r.id]);e?(a.push(`${r.id}: ${e}`),i[r.id]=r.default):i[r.id]=t[r.id]}for(let e of r)i[e.id]=structuredClone(e.default);for(let n of Object.keys(t))!e.some(e=>e.id===n)&&!r.some(e=>e.id===n)&&a.push(`${n}: unknown setting`);return{values:i,errors:a}}function i(e,t){return e.filter(e=>!e.modes||e.modes.includes(t))}function a(e,t){if(typeof e==`string`)return e;let n=t.split(`-`)[0];return e[t]??e[n]??e.en??Object.values(e)[0]}exports.SETTING_TYPES=e,exports.isKnownSetting=t,exports.localize=a,exports.resolveSettings=r,exports.settingsForMode=i,exports.validateSettingValue=n;
package/dist/settings.js CHANGED
@@ -1,5 +1,14 @@
1
1
  //#region src/settings.ts
2
- function e(e, t) {
2
+ var e = [
3
+ "number",
4
+ "boolean",
5
+ "select",
6
+ "text"
7
+ ];
8
+ function t(t) {
9
+ return e.includes(t.type);
10
+ }
11
+ function n(e, t) {
3
12
  switch (e.type) {
4
13
  case "number": return typeof t != "number" || !Number.isFinite(t) ? "must be a number" : e.min !== void 0 && t < e.min ? `must be at least ${e.min}` : e.max !== void 0 && t > e.max ? `must be at most ${e.max}` : null;
5
14
  case "boolean": return typeof t == "boolean" ? null : "must be true or false";
@@ -7,29 +16,30 @@ function e(e, t) {
7
16
  case "text": return typeof t == "string" ? e.maxLength !== void 0 && t.length > e.maxLength ? `must be at most ${e.maxLength} characters` : null : "must be text";
8
17
  }
9
18
  }
10
- function t(t, n = {}) {
11
- let r = {}, i = [];
12
- for (let a of t) {
13
- if (!(a.id in n)) {
14
- r[a.id] = a.default;
19
+ function r(e, t = {}, r = []) {
20
+ let i = {}, a = [];
21
+ for (let r of e) {
22
+ if (!(r.id in t)) {
23
+ i[r.id] = r.default;
15
24
  continue;
16
25
  }
17
- let t = e(a, n[a.id]);
18
- t ? (i.push(`${a.id}: ${t}`), r[a.id] = a.default) : r[a.id] = n[a.id];
26
+ let e = n(r, t[r.id]);
27
+ e ? (a.push(`${r.id}: ${e}`), i[r.id] = r.default) : i[r.id] = t[r.id];
19
28
  }
20
- for (let e of Object.keys(n)) t.some((t) => t.id === e) || i.push(`${e}: unknown setting`);
29
+ for (let e of r) i[e.id] = structuredClone(e.default);
30
+ for (let n of Object.keys(t)) !e.some((e) => e.id === n) && !r.some((e) => e.id === n) && a.push(`${n}: unknown setting`);
21
31
  return {
22
- values: r,
23
- errors: i
32
+ values: i,
33
+ errors: a
24
34
  };
25
35
  }
26
- function n(e, t) {
36
+ function i(e, t) {
27
37
  return e.filter((e) => !e.modes || e.modes.includes(t));
28
38
  }
29
- function r(e, t) {
39
+ function a(e, t) {
30
40
  if (typeof e == "string") return e;
31
41
  let n = t.split("-")[0];
32
42
  return e[t] ?? e[n] ?? e.en ?? Object.values(e)[0];
33
43
  }
34
44
  //#endregion
35
- export { r as localize, t as resolveSettings, n as settingsForMode, e as validateSettingValue };
45
+ export { e as SETTING_TYPES, t as isKnownSetting, a as localize, r as resolveSettings, i as settingsForMode, n as validateSettingValue };
@@ -1 +1 @@
1
- let e=require("zod");var t=e.z.union([e.z.string().min(1,`Text must not be empty`),e.z.record(e.z.string().min(2),e.z.string().min(1)).refine(e=>Object.keys(e).length>0,`Localized text needs at least one language`)]),n={id:e.z.string().regex(/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/,`Setting id must start with a letter and contain only letters, digits, "_" and "-"`),label:t,description:t.optional(),modes:e.z.array(e.z.enum([`solo`,`multiplayer`])).min(1,`modes needs at least one mode`).optional()},r=e.z.looseObject({...n,type:e.z.literal(`number`),default:e.z.number().finite(),min:e.z.number().finite().optional(),max:e.z.number().finite().optional(),step:e.z.number().positive().optional()}).refine(e=>e.min===void 0||e.max===void 0||e.min<=e.max,{message:`min must not be greater than max`,path:[`min`]}).refine(e=>(e.min===void 0||e.default>=e.min)&&(e.max===void 0||e.default<=e.max),{message:`default is outside min/max`,path:[`default`]}),i=e.z.looseObject({...n,type:e.z.literal(`boolean`),default:e.z.boolean()}),a=e.z.union([e.z.string(),e.z.number().finite(),e.z.boolean()]),o=e.z.looseObject({...n,type:e.z.literal(`select`),options:e.z.array(e.z.looseObject({value:a,label:t})).min(1,`A select setting needs at least one option`),default:a}).refine(e=>new Set(e.options.map(e=>e.value)).size===e.options.length,{message:`Option values must be unique`,path:[`options`]}).refine(e=>e.options.some(t=>t.value===e.default),{message:`default must be one of the option values`,path:[`default`]}),s=e.z.looseObject({...n,type:e.z.literal(`text`),default:e.z.string(),maxLength:e.z.number().int().positive().optional()}).refine(e=>e.maxLength===void 0||e.default.length<=e.maxLength,{message:`default is longer than maxLength`,path:[`default`]}),c=e.z.discriminatedUnion(`type`,[r,i,o,s]);exports.LocalizedTextSchema=t,exports.SettingDefinitionSchema=c;
1
+ const e=require("./settings.cjs");let t=require("zod");var n=t.z.union([t.z.string().min(1,`Text must not be empty`),t.z.record(t.z.string().min(2),t.z.string().min(1)).refine(e=>Object.keys(e).length>0,`Localized text needs at least one language`)]),r={id:t.z.string().regex(/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/,`Setting id must start with a letter and contain only letters, digits, "_" and "-"`),label:n,description:n.optional(),modes:t.z.array(t.z.enum([`solo`,`multiplayer`])).min(1,`modes needs at least one mode`).optional()},i=t.z.looseObject({...r,type:t.z.literal(`number`),default:t.z.number().finite(),min:t.z.number().finite().optional(),max:t.z.number().finite().optional(),step:t.z.number().positive().optional()}).refine(e=>e.min===void 0||e.max===void 0||e.min<=e.max,{message:`min must not be greater than max`,path:[`min`]}).refine(e=>(e.min===void 0||e.default>=e.min)&&(e.max===void 0||e.default<=e.max),{message:`default is outside min/max`,path:[`default`]}),a=t.z.looseObject({...r,type:t.z.literal(`boolean`),default:t.z.boolean()}),o=t.z.union([t.z.string(),t.z.number().finite(),t.z.boolean()]),s=t.z.looseObject({...r,type:t.z.literal(`select`),options:t.z.array(t.z.looseObject({value:o,label:n})).min(1,`A select setting needs at least one option`),default:o}).refine(e=>new Set(e.options.map(e=>e.value)).size===e.options.length,{message:`Option values must be unique`,path:[`options`]}).refine(e=>e.options.some(t=>t.value===e.default),{message:`default must be one of the option values`,path:[`default`]}),c=t.z.looseObject({...r,type:t.z.literal(`text`),default:t.z.string(),maxLength:t.z.number().int().positive().optional()}).refine(e=>e.maxLength===void 0||e.default.length<=e.maxLength,{message:`default is longer than maxLength`,path:[`default`]}),l=t.z.discriminatedUnion(`type`,[i,a,s,c]),u=t.z.looseObject({...r,type:t.z.string().regex(/^[a-z][a-z0-9-]*$/,`A setting type is a lowercase name`).refine(t=>!e.SETTING_TYPES.includes(t),`A known setting type`),default:t.z.unknown().refine(e=>e!==void 0,`A setting needs a default`)}),d=t.z.unknown().transform((n,r)=>{let i=typeof n==`object`&&n?n.type:void 0,a=(typeof i==`string`&&e.SETTING_TYPES.includes(i)?l:u).safeParse(n);if(!a.success){for(let e of a.error.issues)r.addIssue({code:`custom`,message:e.message,path:e.path});return t.z.NEVER}return a.data});exports.LocalizedTextSchema=n,exports.SettingDefinitionSchema=l,exports.SettingEntrySchema=d,exports.UnknownSettingSchema=u;