@memizy/protocol 0.0.0-stage → 1.0.0-rc.6

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +21 -0
  3. package/README.md +22 -2
  4. package/SPEC.md +497 -0
  5. package/dist/errors.cjs +1 -0
  6. package/dist/errors.js +37 -0
  7. package/dist/index.cjs +1 -0
  8. package/dist/index.js +13 -0
  9. package/dist/internal.cjs +1 -0
  10. package/dist/internal.js +8 -0
  11. package/dist/items.cjs +1 -0
  12. package/dist/items.js +114 -0
  13. package/dist/limits.cjs +1 -0
  14. package/dist/limits.js +73 -0
  15. package/dist/manifestSchema.cjs +2 -0
  16. package/dist/manifestSchema.js +107 -0
  17. package/dist/pluginRuntime.cjs +1 -0
  18. package/dist/pluginRuntime.js +48 -0
  19. package/dist/polyfills.cjs +1 -0
  20. package/dist/polyfills.js +23 -0
  21. package/dist/relay.cjs +1 -0
  22. package/dist/relay.js +22 -0
  23. package/dist/schemas.cjs +1 -0
  24. package/dist/schemas.js +74 -0
  25. package/dist/security.cjs +1 -0
  26. package/dist/security.js +40 -0
  27. package/dist/settings.cjs +1 -0
  28. package/dist/settings.js +35 -0
  29. package/dist/settingsSchema.cjs +1 -0
  30. package/dist/settingsSchema.js +58 -0
  31. package/dist/src/errors.d.ts +21 -0
  32. package/dist/src/index.d.ts +20 -0
  33. package/dist/src/internal.d.ts +12 -0
  34. package/dist/src/items.d.ts +27 -0
  35. package/dist/src/limits.d.ts +78 -0
  36. package/dist/src/manifestSchema.d.ts +159 -0
  37. package/dist/src/pluginRuntime.d.ts +47 -0
  38. package/dist/src/polyfills.d.ts +6 -0
  39. package/dist/src/relay.d.ts +186 -0
  40. package/dist/src/schemas.d.ts +111 -0
  41. package/dist/src/security.d.ts +21 -0
  42. package/dist/src/settings.d.ts +22 -0
  43. package/dist/src/settingsSchema.d.ts +54 -0
  44. package/dist/src/types.d.ts +182 -0
  45. package/dist/src/version.d.ts +30 -0
  46. package/dist/types.cjs +1 -0
  47. package/dist/types.js +24 -0
  48. package/dist/version.cjs +1 -0
  49. package/dist/version.js +31 -0
  50. package/package.json +63 -4
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./version.cjs"),t=require("./errors.cjs"),n=require("./limits.cjs"),r=require("./settingsSchema.cjs"),i=require("./settings.cjs"),a=require("./pluginRuntime.cjs"),o=require("./manifestSchema.cjs"),s=require("./types.cjs"),c=require("./schemas.cjs"),l=require("./relay.cjs"),u=require("./items.cjs"),d=require("./security.cjs");exports.AddressSchema=c.AddressSchema,exports.AnswerRecordSchema=c.AnswerRecordSchema,exports.BOARD_ADDRESS=s.BOARD_ADDRESS,exports.DataScopeSchema=c.DataScopeSchema,exports.ErrorReportSchema=c.ErrorReportSchema,exports.HOST_API_METHODS=s.HOST_API_METHODS,exports.HandshakeSchema=c.HandshakeSchema,exports.HostAsSchema=o.HostAsSchema,exports.LIMITS=n.LIMITS,exports.LocalizedTextSchema=r.LocalizedTextSchema,exports.MemizyRuntimeSchema=o.MemizyRuntimeSchema,exports.MultiplayerModeSchema=o.MultiplayerModeSchema,exports.OutgoingMessageSchema=c.OutgoingMessageSchema,exports.PLUGIN_API_METHODS=s.PLUGIN_API_METHODS,exports.PLUGIN_DEVICES=d.PLUGIN_DEVICES,exports.PROTOCOL_ERROR_CODES=t.PROTOCOL_ERROR_CODES,exports.PROTOCOL_VERSION=e.PROTOCOL_VERSION,exports.PluginManifestError=o.PluginManifestError,exports.PluginPermissionsSchema=o.PluginPermissionsSchema,exports.ProtocolError=t.ProtocolError,exports.RELAY_PROTOCOL=l.RELAY_PROTOCOL,exports.SERVER_ADDRESS=s.SERVER_ADDRESS,exports.SETTINGS_ADDRESS=s.SETTINGS_ADDRESS,exports.ServiceNameSchema=o.ServiceNameSchema,exports.SessionResultSchema=c.SessionResultSchema,exports.SettingDefinitionSchema=r.SettingDefinitionSchema,exports.SettingsUpdateSchema=c.SettingsUpdateSchema,exports.compareProtocolVersions=e.compareProtocolVersions,exports.extractManifestFromHtml=a.extractManifestFromHtml,exports.isProtocolErrorCode=t.isProtocolErrorCode,exports.isProtocolSupported=e.isProtocolSupported,exports.localize=i.localize,exports.negotiateProtocolVersion=e.negotiateProtocolVersion,exports.parsePluginManifest=o.parsePluginManifest,exports.parseProtocolVersion=e.parseProtocolVersion,exports.prepareDisplaySet=u.prepareDisplaySet,exports.readPluginManifestFromHtml=o.readPluginManifestFromHtml,exports.resolveSettings=i.resolveSettings,exports.safeParsePluginManifest=o.safeParsePluginManifest,exports.settingsForMode=i.settingsForMode,exports.toProtocolError=t.toProtocolError,exports.validateSettingValue=i.validateSettingValue;
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ import { PROTOCOL_VERSION as e, compareProtocolVersions as t, isProtocolSupported as n, negotiateProtocolVersion as r, parseProtocolVersion as i } from "./version.js";
2
+ import { PROTOCOL_ERROR_CODES as a, ProtocolError as o, isProtocolErrorCode as s, toProtocolError as c } from "./errors.js";
3
+ import { LIMITS as l } from "./limits.js";
4
+ import { LocalizedTextSchema as u, SettingDefinitionSchema as d } from "./settingsSchema.js";
5
+ import { localize as f, resolveSettings as p, settingsForMode as m, validateSettingValue as h } from "./settings.js";
6
+ import { extractManifestFromHtml as g } from "./pluginRuntime.js";
7
+ import { HostAsSchema as _, MemizyRuntimeSchema as v, MultiplayerModeSchema as y, PluginManifestError as b, PluginPermissionsSchema as x, ServiceNameSchema as S, parsePluginManifest as C, readPluginManifestFromHtml as w, safeParsePluginManifest as T } from "./manifestSchema.js";
8
+ import { BOARD_ADDRESS as E, HOST_API_METHODS as D, PLUGIN_API_METHODS as O, SERVER_ADDRESS as k, SETTINGS_ADDRESS as A } from "./types.js";
9
+ import { AddressSchema as j, AnswerRecordSchema as M, DataScopeSchema as N, ErrorReportSchema as P, HandshakeSchema as F, OutgoingMessageSchema as I, SessionResultSchema as L, SettingsUpdateSchema as R } from "./schemas.js";
10
+ import { RELAY_PROTOCOL as z } from "./relay.js";
11
+ import { prepareDisplaySet as B } from "./items.js";
12
+ import { PLUGIN_DEVICES as V } from "./security.js";
13
+ export { j as AddressSchema, M as AnswerRecordSchema, E as BOARD_ADDRESS, N as DataScopeSchema, P as ErrorReportSchema, D as HOST_API_METHODS, F as HandshakeSchema, _ as HostAsSchema, l as LIMITS, u as LocalizedTextSchema, v as MemizyRuntimeSchema, y as MultiplayerModeSchema, I as OutgoingMessageSchema, O as PLUGIN_API_METHODS, V as PLUGIN_DEVICES, a as PROTOCOL_ERROR_CODES, e as PROTOCOL_VERSION, b as PluginManifestError, x as PluginPermissionsSchema, o as ProtocolError, z as RELAY_PROTOCOL, k as SERVER_ADDRESS, A as SETTINGS_ADDRESS, S as ServiceNameSchema, L as SessionResultSchema, d as SettingDefinitionSchema, R as SettingsUpdateSchema, t as compareProtocolVersions, g as extractManifestFromHtml, s as isProtocolErrorCode, n as isProtocolSupported, f as localize, r as negotiateProtocolVersion, C as parsePluginManifest, i as parseProtocolVersion, B as prepareDisplaySet, w as readPluginManifestFromHtml, p as resolveSettings, T as safeParsePluginManifest, m as settingsForMode, c as toProtocolError, h as validateSettingValue };
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./limits.cjs"),t=require("./pluginRuntime.cjs"),n=require("./schemas.cjs"),r=require("./relay.cjs"),i=require("./items.cjs"),a=require("./security.cjs"),o=require("./polyfills.cjs");exports.HOST_API_ARGS=n.HOST_API_ARGS,exports.LIBRARY_ORIGINS=a.LIBRARY_ORIGINS,exports.RELAY_LIMITS=r.RELAY_LIMITS,exports.RateLimiter=e.RateLimiter,exports.assertJsonWithin=e.assertJsonWithin,exports.displayItem=i.displayItem,exports.ensureConnectionPolyfills=o.ensureConnectionPolyfills,exports.findNonJson=e.findNonJson,exports.injectContentPolicy=a.injectContentPolicy,exports.isPin=r.isPin,exports.jsonByteSize=e.jsonByteSize,exports.normalizePlayerName=r.normalizePlayerName,exports.parseHostCall=n.parseHostCall,exports.pluginAllowAttribute=a.pluginAllowAttribute,exports.pluginContentPolicy=a.pluginContentPolicy,exports.publicItem=i.publicItem,exports.seededRandom=i.seededRandom,exports.toRuntime=t.toRuntime;
@@ -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 ADDED
@@ -0,0 +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.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=[`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":[`correctAnswers`],matrix:[`correctCells`],"open-ended":[`sampleAnswer`,`rubric`],"chess-puzzle":[`correctAnswers`]},s=[`options`,`items`,`prompts`,`matches`,`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;
package/dist/items.js ADDED
@@ -0,0 +1,114 @@
1
+ import { isTimelineOrderCorrect as e } from "@memizy/oqse";
2
+ //#region src/items.ts
3
+ function t(e) {
4
+ let t = 2166136261;
5
+ for (let n = 0; n < e.length; n++) t = Math.imul(t ^ e.charCodeAt(n), 16777619);
6
+ let n = t >>> 0;
7
+ return () => {
8
+ n = n + 1831565813 >>> 0;
9
+ let e = n;
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
+ };
12
+ }
13
+ function n(e, t, n) {
14
+ for (let r = 0; r < 6; r++) {
15
+ let r = [...e];
16
+ for (let e = r.length - 1; e > 0; e--) {
17
+ let n = Math.floor(t() * (e + 1));
18
+ [r[e], r[n]] = [r[n], r[e]];
19
+ }
20
+ if (!n || r.length < 2 || !n(r)) return r;
21
+ }
22
+ return [...e.slice(1), e[0]];
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) {
28
+ case "mcq-single":
29
+ case "mcq-multi": return a.shuffle !== !1 && (a.options = n(a.options, i)), a;
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.prompts = n(a.prompts, i), a.matches = n(a.matches, 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;
40
+ }
41
+ }
42
+ var a = ["explanation", "incorrectFeedback"], o = {
43
+ note: ["hiddenContent"],
44
+ flashcard: ["back"],
45
+ "true-false": ["correctAnswer"],
46
+ "mcq-single": ["correctId"],
47
+ "mcq-multi": ["correctIds"],
48
+ "short-answer": ["correctAnswers"],
49
+ "sort-items": ["correctOrder"],
50
+ "match-pairs": ["pairs"],
51
+ "match-complex": ["connections"],
52
+ slider: ["correctAnswer"],
53
+ "numeric-input": ["correctAnswer"],
54
+ "math-input": ["correctAnswers"],
55
+ matrix: ["correctCells"],
56
+ "open-ended": ["sampleAnswer", "rubric"],
57
+ "chess-puzzle": ["correctAnswers"]
58
+ }, s = [
59
+ "options",
60
+ "items",
61
+ "prompts",
62
+ "matches",
63
+ "categories",
64
+ "rows",
65
+ "columns",
66
+ "labels"
67
+ ];
68
+ function c(e) {
69
+ let t = e.type, n = structuredClone(e);
70
+ for (let e of a) delete n[e];
71
+ if (Array.isArray(n.sources)) for (let e of n.sources) e && typeof e == "object" && delete e.quote;
72
+ if (t.startsWith("x-")) return delete n.solution, n.answerHidden = !0, n;
73
+ for (let e of o[t] ?? []) delete n[e];
74
+ for (let e of s) if (Array.isArray(n[e])) for (let t of n[e]) t && typeof t == "object" && delete t.explanation;
75
+ switch (t) {
76
+ case "fill-in-blanks":
77
+ n.blanks = Object.fromEntries(Object.keys(n.blanks ?? {}).map((e) => [e, []]));
78
+ break;
79
+ case "fill-in-select":
80
+ for (let e of Object.values(n.blanks ?? {})) {
81
+ delete e.correctId;
82
+ for (let t of e.options ?? []) delete t.explanation;
83
+ }
84
+ break;
85
+ case "categorize":
86
+ for (let e of n.items ?? []) delete e.correctCategoryId;
87
+ break;
88
+ case "timeline":
89
+ for (let e of n.events ?? []) delete e.date, delete e.precision;
90
+ break;
91
+ case "diagram-label":
92
+ for (let e of n.zones ?? []) delete e.correctLabelId;
93
+ n.requireTyping && (n.labels = []);
94
+ break;
95
+ case "pin-on-image":
96
+ case "pin-on-model": n.hotspots = [];
97
+ }
98
+ return n.answerHidden = !0, n;
99
+ }
100
+ function l(e, n) {
101
+ let r = n === void 0 ? null : t(n), a = e.items.map((e) => r ? i(e, r) : structuredClone(e));
102
+ return {
103
+ set: {
104
+ meta: e.meta,
105
+ items: a
106
+ },
107
+ publicSet: {
108
+ meta: e.meta,
109
+ items: a.map(c)
110
+ }
111
+ };
112
+ }
113
+ //#endregion
114
+ export { i as displayItem, l as prepareDisplaySet, c as publicItem, t as seededRandom };
@@ -0,0 +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,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 ADDED
@@ -0,0 +1,73 @@
1
+ import { ProtocolError as e } from "./errors.js";
2
+ //#region src/limits.ts
3
+ var t = {
4
+ messageBytes: 65536,
5
+ messagesPerSecond: 30,
6
+ messageBurst: 60,
7
+ snapshotBytes: 1048576,
8
+ snapshotsPerSecond: 2,
9
+ dataBytes: 262144,
10
+ dataWritesPerSecond: 1,
11
+ helloTimeoutMs: 1e4,
12
+ answerBytes: 4096,
13
+ generatedItemBytes: 32768,
14
+ answersPerSecond: 10,
15
+ answerBurst: 20,
16
+ serviceBytes: 65536,
17
+ serviceResultBytes: 262144,
18
+ servicesPerMinute: 30,
19
+ assetsPerSecond: 10,
20
+ assetBurst: 20,
21
+ assetBytes: 2097152,
22
+ errorReportsPerSecond: 5,
23
+ errorReportBurst: 10
24
+ }, n = 64;
25
+ function r(e, t = "$", i = 0, a = /* @__PURE__ */ new Set()) {
26
+ if (e === null || typeof e == "string" || typeof e == "boolean") return null;
27
+ if (typeof e == "number") return Number.isFinite(e) ? null : `${t} is not a finite number`;
28
+ if (typeof e != "object") return `${t} is a ${typeof e}`;
29
+ if (i >= n) return `${t} is nested deeper than ${n} levels`;
30
+ if (a.has(e)) return `${t} contains a circular reference`;
31
+ if (Array.isArray(e)) {
32
+ a.add(e);
33
+ for (let n = 0; n < e.length; n++) {
34
+ let o = r(e[n], `${t}[${n}]`, i + 1, a);
35
+ if (o) return o;
36
+ }
37
+ return a.delete(e), null;
38
+ }
39
+ let o = Object.getPrototypeOf(e);
40
+ if (o !== Object.prototype && o !== null) return `${t} is not a plain object`;
41
+ a.add(e);
42
+ for (let [n, o] of Object.entries(e)) {
43
+ let e = r(o, `${t}.${n}`, i + 1, a);
44
+ if (e) return e;
45
+ }
46
+ return a.delete(e), null;
47
+ }
48
+ function i(e) {
49
+ return new TextEncoder().encode(JSON.stringify(e)).length;
50
+ }
51
+ function a(t, n, a, o) {
52
+ let s = r(t);
53
+ if (s) throw new e("INVALID_ARGUMENT", `${o} must be plain JSON: ${s}.`);
54
+ let c = i(t);
55
+ if (c > n) throw new e(a, `${o} is ${c} bytes; the limit is ${n} bytes.`);
56
+ return c;
57
+ }
58
+ var o = class {
59
+ rate;
60
+ burst;
61
+ now;
62
+ tokens;
63
+ last;
64
+ constructor(e, t, n = () => Date.now()) {
65
+ this.rate = e, this.burst = t, this.now = n, this.tokens = t, this.last = n();
66
+ }
67
+ tryTake(e = 1) {
68
+ let t = this.now();
69
+ 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);
70
+ }
71
+ };
72
+ //#endregion
73
+ export { t as LIMITS, o as RateLimiter, a as assertJsonWithin, r as findNonJson, i as jsonByteSize };
@@ -0,0 +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(`
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;
@@ -0,0 +1,107 @@
1
+ import { SettingDefinitionSchema as e } from "./settingsSchema.js";
2
+ import { extractManifestFromHtml as t, toRuntime as n } from "./pluginRuntime.js";
3
+ import { z as r } from "zod";
4
+ import { OQSEManifestSchema as i, formatOQSEErrors as a, withManifestDefaults as o } from "@memizy/oqse";
5
+ //#region src/manifestSchema.ts
6
+ var s = r.string().regex(/^\d+\.\d+$/, "Protocol version must be in MAJOR.MINOR format (e.g. \"1.0\")"), c = r.enum(["presenter", "player"]), l = r.looseObject({
7
+ players: r.looseObject({
8
+ min: r.number().int().min(1, "players.min must be at least 1"),
9
+ max: r.number().int().max(200, "players.max must be at most 200"),
10
+ recommended: r.number().int().positive().optional()
11
+ }).refine((e) => e.min <= e.max, {
12
+ message: "players.min must not be greater than players.max",
13
+ path: ["min"]
14
+ }).refine((e) => e.recommended === void 0 || e.recommended >= e.min && e.recommended <= e.max, {
15
+ message: "players.recommended must be between min and max",
16
+ path: ["recommended"]
17
+ }),
18
+ hostAs: r.array(c).min(1, "hostAs must contain \"presenter\" and/or \"player\"").refine((e) => new Set(e).size === e.length, "hostAs must not contain duplicates"),
19
+ lateJoin: r.boolean().optional()
20
+ }), u = r.string().regex(/^(https|wss):\/\/[a-z0-9.-]+(:\d{1,5})?$/i, "A network origin is \"https://host\" or \"wss://host\" (no path)"), d = r.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\")"), f = r.looseObject({
21
+ network: r.array(u).max(20).optional(),
22
+ devices: r.array(r.enum([
23
+ "camera",
24
+ "microphone",
25
+ "geolocation",
26
+ "serial",
27
+ "bluetooth"
28
+ ])).max(5).optional()
29
+ }), p = r.looseObject({
30
+ protocol: s,
31
+ set: r.enum([
32
+ "required",
33
+ "optional",
34
+ "none"
35
+ ]).optional(),
36
+ modes: r.looseObject({
37
+ solo: r.looseObject({}).optional(),
38
+ multiplayer: l.optional()
39
+ }),
40
+ settings: r.array(e).optional(),
41
+ settingsScreen: r.looseObject({ size: r.enum(["compact", "large"]) }).optional(),
42
+ display: r.looseObject({ orientation: r.enum([
43
+ "any",
44
+ "portrait",
45
+ "landscape"
46
+ ]).optional() }).optional(),
47
+ permissions: f.optional(),
48
+ services: r.array(d).max(20).optional()
49
+ }).refine((e) => e.modes.solo !== void 0 || e.modes.multiplayer !== void 0, {
50
+ message: "modes must declare \"solo\" and/or \"multiplayer\"",
51
+ path: ["modes"]
52
+ }).refine((e) => e.settingsScreen === void 0 || e.modes.multiplayer !== void 0, {
53
+ message: "settingsScreen is only used in the multiplayer lobby; declare modes.multiplayer or remove it",
54
+ path: ["settingsScreen"]
55
+ }).refine((e) => {
56
+ let t = (e.settings ?? []).map((e) => e.id);
57
+ return new Set(t).size === t.length;
58
+ }, {
59
+ message: "Setting ids must be unique",
60
+ path: ["settings"]
61
+ });
62
+ function m(e) {
63
+ let t = [], r = i.safeParse(e);
64
+ r.success || t.push(...a(r.error));
65
+ let s = e?.appSpecific?.memizy;
66
+ if (s === void 0) t.push("appSpecific.memizy: Missing the Memizy runtime block (protocol, modes, …)");
67
+ else {
68
+ let e = p.safeParse(s);
69
+ e.success || t.push(...a(e.error).map((e) => `appSpecific.memizy.${e}`));
70
+ }
71
+ if (t.length > 0) return {
72
+ success: !1,
73
+ errors: t
74
+ };
75
+ let c = o(e), l = n(c.appSpecific.memizy), u = c.capabilities.types;
76
+ return l.set !== "none" && (!Array.isArray(u) || u.length === 0) ? {
77
+ success: !1,
78
+ errors: ["capabilities.types: Declare the item types the game shows, or \"set\": \"none\" in appSpecific.memizy for a game without questions."]
79
+ } : {
80
+ success: !0,
81
+ manifest: c,
82
+ runtime: l
83
+ };
84
+ }
85
+ var h = class extends Error {
86
+ errors;
87
+ constructor(e) {
88
+ super(`Invalid plugin manifest:\n${e.join("\n")}`), this.name = "PluginManifestError", this.errors = e;
89
+ }
90
+ };
91
+ function g(e) {
92
+ let t = m(e);
93
+ if (!t.success) throw new h(t.errors);
94
+ return {
95
+ manifest: t.manifest,
96
+ runtime: t.runtime
97
+ };
98
+ }
99
+ function _(e) {
100
+ let n = t(e);
101
+ return n.success ? m(n.data) : {
102
+ success: !1,
103
+ errors: [n.error]
104
+ };
105
+ }
106
+ //#endregion
107
+ export { c as HostAsSchema, p as MemizyRuntimeSchema, l as MultiplayerModeSchema, h as PluginManifestError, f as PluginPermissionsSchema, d as ServiceNameSchema, g as parsePluginManifest, _ as readPluginManifestFromHtml, m as safeParsePluginManifest };
@@ -0,0 +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;
@@ -0,0 +1,48 @@
1
+ //#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);
5
+ if (!n) return {
6
+ success: !1,
7
+ error: "No <script type=\"application/oqse-manifest+json\"> data island found."
8
+ };
9
+ try {
10
+ return {
11
+ success: !0,
12
+ data: JSON.parse(n[1])
13
+ };
14
+ } catch (e) {
15
+ return {
16
+ success: !1,
17
+ error: `The manifest data island is not valid JSON: ${e.message}`
18
+ };
19
+ }
20
+ }
21
+ function n(e) {
22
+ let t = e.modes.multiplayer, n = t ? {
23
+ players: {
24
+ min: t.players.min,
25
+ max: t.players.max,
26
+ ...t.players.recommended !== void 0 && { recommended: t.players.recommended }
27
+ },
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,
40
+ permissions: {
41
+ network: [...new Set(e.permissions?.network ?? [])],
42
+ devices: [...new Set(e.permissions?.devices ?? [])]
43
+ },
44
+ services: [...new Set(e.services ?? [])]
45
+ };
46
+ }
47
+ //#endregion
48
+ export { t as extractManifestFromHtml, n as toRuntime };
@@ -0,0 +1 @@
1
+ function e(){typeof Promise.withResolvers!=`function`&&Object.defineProperty(Promise,"withResolvers",{configurable:!0,writable:!0,value:function(){let e,t;return{promise:new this((n,r)=>{e=n,t=r}),resolve:e,reject:t}}}),typeof Object.hasOwn!=`function`&&Object.defineProperty(Object,"hasOwn",{configurable:!0,writable:!0,value:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)})}exports.ensureConnectionPolyfills=e;
@@ -0,0 +1,23 @@
1
+ //#region src/polyfills.ts
2
+ function e() {
3
+ typeof Promise.withResolvers != "function" && Object.defineProperty(Promise, "withResolvers", {
4
+ configurable: !0,
5
+ writable: !0,
6
+ value: function() {
7
+ let e, t;
8
+ return {
9
+ promise: new this((n, r) => {
10
+ e = n, t = r;
11
+ }),
12
+ resolve: e,
13
+ reject: t
14
+ };
15
+ }
16
+ }), typeof Object.hasOwn != "function" && Object.defineProperty(Object, "hasOwn", {
17
+ configurable: !0,
18
+ writable: !0,
19
+ value: (e, t) => Object.prototype.hasOwnProperty.call(e, t)
20
+ });
21
+ }
22
+ //#endregion
23
+ export { e as ensureConnectionPolyfills };
package/dist/relay.cjs ADDED
@@ -0,0 +1 @@
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 ADDED
@@ -0,0 +1,22 @@
1
+ //#region src/relay.ts
2
+ var e = 1, t = {
3
+ frameBytes: 3145728,
4
+ bundleBytes: 8388608,
5
+ playersPerRoom: 100,
6
+ playerFramesPerSecond: 60,
7
+ playerFrameBurst: 120,
8
+ hostFramesPerSecond: 3e3,
9
+ hostFrameBurst: 6e3,
10
+ nameLength: 32,
11
+ authLength: 4096,
12
+ hostAwayMs: 6e5,
13
+ roomTtlMs: 216e5
14
+ };
15
+ function n(e) {
16
+ return typeof e == "string" ? e.replace(/\s+/g, " ").trim().slice(0, t.nameLength) : "";
17
+ }
18
+ function r(e) {
19
+ return typeof e == "string" && /^\d{6}$/.test(e);
20
+ }
21
+ //#endregion
22
+ export { t as RELAY_LIMITS, e as RELAY_PROTOCOL, r as isPin, n as normalizePlayerName };
@@ -0,0 +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.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;
@@ -0,0 +1,74 @@
1
+ import { ProtocolError as e } from "./errors.js";
2
+ import { z as t } from "zod";
3
+ import { formatOQSEErrors as n } from "@memizy/oqse";
4
+ //#region src/schemas.ts
5
+ var r = (e) => t.string().min(1).max(e), i = t.string().min(1).max(128), a = t.looseObject({
6
+ protocol: t.string().regex(/^\d+\.\d+$/, "protocol must be in MAJOR.MINOR format"),
7
+ sdk: t.looseObject({
8
+ name: r(200),
9
+ version: r(100)
10
+ }),
11
+ plugin: t.looseObject({
12
+ id: r(500),
13
+ version: r(100)
14
+ }),
15
+ features: t.array(r(100)).max(100)
16
+ }), o = t.looseObject({
17
+ to: t.union([
18
+ t.literal("authority"),
19
+ t.literal("all"),
20
+ t.array(i).min(1).max(500)
21
+ ]),
22
+ data: t.unknown()
23
+ }), s = t.looseObject({
24
+ playerId: i.optional(),
25
+ itemId: r(128),
26
+ isCorrect: t.boolean(),
27
+ confidence: t.union([
28
+ t.literal(1),
29
+ t.literal(2),
30
+ t.literal(3),
31
+ t.literal(4)
32
+ ]).optional(),
33
+ timeSpentMs: t.number().int().nonnegative().optional(),
34
+ hintsUsed: t.number().int().nonnegative().optional(),
35
+ isSkipped: t.boolean().optional(),
36
+ answer: t.unknown().optional(),
37
+ item: t.looseObject({
38
+ id: r(128),
39
+ type: r(64)
40
+ }).optional()
41
+ }).refine((e) => !e.item || e.item.id === e.itemId, {
42
+ message: "item.id must equal itemId",
43
+ path: ["item", "id"]
44
+ }), c = t.enum(["plugin", "set"]), l = t.looseObject({
45
+ values: t.record(t.string(), t.unknown()),
46
+ valid: t.boolean(),
47
+ message: t.string().max(500).optional()
48
+ }), u = t.looseObject({
49
+ scores: t.record(i, t.number().finite()).optional(),
50
+ summary: t.string().max(1e3).optional()
51
+ }), d = t.looseObject({
52
+ code: r(100),
53
+ message: t.string().max(2e3),
54
+ context: t.record(t.string(), t.unknown()).optional()
55
+ }), f = {
56
+ hello: t.tuple([a]),
57
+ ready: t.tuple([]),
58
+ send: t.tuple([o]),
59
+ saveSnapshot: t.tuple([t.unknown()]),
60
+ recordAnswer: t.tuple([s]),
61
+ saveData: t.tuple([c, t.unknown()]),
62
+ updateSettings: t.tuple([l]),
63
+ getAsset: t.tuple([r(200), r(128).optional()]),
64
+ end: t.tuple([u]),
65
+ reportError: t.tuple([d]),
66
+ service: t.tuple([r(100), t.unknown()])
67
+ };
68
+ function p(t, r) {
69
+ let i = f[t].safeParse(r);
70
+ if (!i.success) throw new e("INVALID_ARGUMENT", `${t}: ${n(i.error).join("; ")}`);
71
+ return i.data;
72
+ }
73
+ //#endregion
74
+ 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 };
@@ -0,0 +1 @@
1
+ var e=[`https://cdn.jsdelivr.net`,`https://unpkg.com`,`https://cdnjs.cloudflare.com`,`https://esm.sh`],t=[`https://fonts.googleapis.com`,`https://fonts.gstatic.com`],n=[`camera`,`microphone`,`geolocation`,`serial`,`bluetooth`];function r(n,r){let i=r&&/^https?:\/\//.test(r)?[r]:[],a=n?.permissions.network??[],o=(...e)=>[...new Set(e.flat())].join(` `);return[`default-src 'none'`,`script-src 'unsafe-inline' 'wasm-unsafe-eval' blob: ${o(e,i)}`,`style-src 'unsafe-inline' ${o(e,t,i)}`,`font-src data: ${o(e,t,i)}`,`img-src data: blob: https:`,`media-src data: blob: https:`,`connect-src data: blob: ${o(e,i,a)}`,`worker-src blob: ${o(e,i)}`,`frame-src 'none'`,`form-action 'none'`,`base-uri 'none'`].join(`; `)}function i(e){return(e?.permissions.devices??[]).map(e=>`${e} *`).join(`; `)}function a(e,t){let n=`<meta http-equiv="Content-Security-Policy" content="${t.replace(/"/g,`&quot;`)}">`,r=/<head\b[^>]*>/i.exec(e);if(r)return e.slice(0,r.index+r[0].length)+n+e.slice(r.index+r[0].length);let i=/^\s*<!doctype[^>]*>/i.exec(e);return i?e.slice(0,i[0].length)+`<head>${n}</head>`+e.slice(i[0].length):`<head>${n}</head>${e}`}exports.LIBRARY_ORIGINS=e,exports.PLUGIN_DEVICES=n,exports.injectContentPolicy=a,exports.pluginAllowAttribute=i,exports.pluginContentPolicy=r;
@@ -0,0 +1,40 @@
1
+ //#region src/security.ts
2
+ var e = [
3
+ "https://cdn.jsdelivr.net",
4
+ "https://unpkg.com",
5
+ "https://cdnjs.cloudflare.com",
6
+ "https://esm.sh"
7
+ ], t = ["https://fonts.googleapis.com", "https://fonts.gstatic.com"], n = [
8
+ "camera",
9
+ "microphone",
10
+ "geolocation",
11
+ "serial",
12
+ "bluetooth"
13
+ ];
14
+ function r(n, r) {
15
+ let i = r && /^https?:\/\//.test(r) ? [r] : [], a = n?.permissions.network ?? [], o = (...e) => [...new Set(e.flat())].join(" ");
16
+ return [
17
+ "default-src 'none'",
18
+ `script-src 'unsafe-inline' 'wasm-unsafe-eval' blob: ${o(e, i)}`,
19
+ `style-src 'unsafe-inline' ${o(e, t, i)}`,
20
+ `font-src data: ${o(e, t, i)}`,
21
+ "img-src data: blob: https:",
22
+ "media-src data: blob: https:",
23
+ `connect-src data: blob: ${o(e, i, a)}`,
24
+ `worker-src blob: ${o(e, i)}`,
25
+ "frame-src 'none'",
26
+ "form-action 'none'",
27
+ "base-uri 'none'"
28
+ ].join("; ");
29
+ }
30
+ function i(e) {
31
+ return (e?.permissions.devices ?? []).map((e) => `${e} *`).join("; ");
32
+ }
33
+ function a(e, t) {
34
+ let n = `<meta http-equiv="Content-Security-Policy" content="${t.replace(/"/g, "&quot;")}">`, r = /<head\b[^>]*>/i.exec(e);
35
+ if (r) return e.slice(0, r.index + r[0].length) + n + e.slice(r.index + r[0].length);
36
+ let i = /^\s*<!doctype[^>]*>/i.exec(e);
37
+ return i ? e.slice(0, i[0].length) + `<head>${n}</head>` + e.slice(i[0].length) : `<head>${n}</head>${e}`;
38
+ }
39
+ //#endregion
40
+ export { e as LIBRARY_ORIGINS, n as PLUGIN_DEVICES, a as injectContentPolicy, i as pluginAllowAttribute, r as pluginContentPolicy };
@@ -0,0 +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;
@@ -0,0 +1,35 @@
1
+ //#region src/settings.ts
2
+ function e(e, t) {
3
+ switch (e.type) {
4
+ 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
+ case "boolean": return typeof t == "boolean" ? null : "must be true or false";
6
+ case "select": return e.options.some((e) => e.value === t) ? null : "must be one of the options";
7
+ 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
+ }
9
+ }
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;
15
+ continue;
16
+ }
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];
19
+ }
20
+ for (let e of Object.keys(n)) t.some((t) => t.id === e) || i.push(`${e}: unknown setting`);
21
+ return {
22
+ values: r,
23
+ errors: i
24
+ };
25
+ }
26
+ function n(e, t) {
27
+ return e.filter((e) => !e.modes || e.modes.includes(t));
28
+ }
29
+ function r(e, t) {
30
+ if (typeof e == "string") return e;
31
+ let n = t.split("-")[0];
32
+ return e[t] ?? e[n] ?? e.en ?? Object.values(e)[0];
33
+ }
34
+ //#endregion
35
+ export { r as localize, t as resolveSettings, n as settingsForMode, e as validateSettingValue };
@@ -0,0 +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;