@primitivedotdev/sdk 1.22.1 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -4
- package/dist/api/index.d.ts +4 -4
- package/dist/api/index.js +3 -3
- package/dist/{api-D18uroMD.js → api-C_OS8hb4.js} +315 -9
- package/dist/contract/index.d.ts +2 -2
- package/dist/contract/index.js +1 -1
- package/dist/{errors-BSsdMlef.d.ts → errors-BgI9FKHK.d.ts} +1 -1
- package/dist/{index-PTY8VraQ.d.ts → index-Dv1zvPCc.d.ts} +1220 -59
- package/dist/{index-x795QzI4.d.ts → index-fS1gVNuX.d.ts} +3 -3
- package/dist/index.d.ts +7 -6
- package/dist/index.js +2 -2
- package/dist/interactions/index.d.ts +132 -0
- package/dist/interactions/index.js +494 -0
- package/dist/openapi/index.js +1 -1
- package/dist/{operations.generated-CqlK_7LO.js → operations.generated-CG4ftbsn.js} +6281 -2960
- package/dist/parser/address.d.ts +1 -1
- package/dist/parser/index.d.ts +2 -2
- package/dist/payloads/index.js +1 -1
- package/dist/{payloads-CQtSsUT6.js → payloads-BfJAtqW9.js} +8 -6
- package/dist/sign-m3ZStnNw.d.ts +292 -0
- package/dist/{types-DJsEJwJ6.d.ts → types-DcXtFARL.d.ts} +1 -1
- package/dist/webhook/index.d.ts +3 -3
- package/dist/webhook/index.js +1 -1
- package/dist/{webhook-CTaEuJz2.js → webhook-BtyuJtUF.js} +1 -1
- package/dist/x402/index.d.ts +1 -290
- package/package.json +14 -9
- /package/dist/{address-parser-CJqzx713.d.ts → address-parser-B7L4E0zP.d.ts} +0 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
//#region src/interactions/signals.ts
|
|
2
|
+
const UUID$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3
|
+
const ATOM = "[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+";
|
|
4
|
+
const MAILBOX = new RegExp(`^${ATOM}(?:\\.${ATOM})*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$`);
|
|
5
|
+
const ID = /^[!-~]+@[!-~]+$/;
|
|
6
|
+
const encoder = new TextEncoder();
|
|
7
|
+
function check(condition, message) {
|
|
8
|
+
if (!condition) throw new TypeError(message);
|
|
9
|
+
}
|
|
10
|
+
function header(value, limit) {
|
|
11
|
+
check(typeof value === "string" && ![...value].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127) && !/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(value) && encoder.encode(value).length <= limit, "invalid header");
|
|
12
|
+
}
|
|
13
|
+
function wireId(value) {
|
|
14
|
+
check(typeof value === "string", "invalid Message-ID");
|
|
15
|
+
let start = 0;
|
|
16
|
+
let end = value.length;
|
|
17
|
+
while (start < end && value.charCodeAt(start) === 32) start++;
|
|
18
|
+
while (end > start && value.charCodeAt(end - 1) === 32) end--;
|
|
19
|
+
let id = value.slice(start, end);
|
|
20
|
+
if (id.startsWith("<") && id.endsWith(">")) id = id.slice(1, -1);
|
|
21
|
+
check(id.length <= 996 && ID.test(id) && !/[<>]/.test(id) && id.split("@").length === 2, "invalid Message-ID");
|
|
22
|
+
return `<${id}>`;
|
|
23
|
+
}
|
|
24
|
+
function milliseconds(value) {
|
|
25
|
+
check(Number.isSafeInteger(value) && value >= 0 && value <= 0xe677d21fdbff, "invalid clock or expiry");
|
|
26
|
+
}
|
|
27
|
+
function signalJson(value) {
|
|
28
|
+
return JSON.stringify(value).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
29
|
+
}
|
|
30
|
+
function base64(bytes) {
|
|
31
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
32
|
+
let out = "";
|
|
33
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
34
|
+
const a = bytes[i] ?? 0, b = bytes[i + 1] ?? 0, c = bytes[i + 2] ?? 0;
|
|
35
|
+
out += alphabet[a >> 2];
|
|
36
|
+
out += alphabet[(a & 3) << 4 | b >> 4];
|
|
37
|
+
out += i + 1 < bytes.length ? alphabet[(b & 15) << 2 | c >> 6] : "=";
|
|
38
|
+
out += i + 2 < bytes.length ? alphabet[c & 63] : "=";
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function prepareSignalEmail(input, dependencies) {
|
|
43
|
+
const p = input.parent;
|
|
44
|
+
if (p.messageId === null || p.messageId === "") return { status: "waiting_on_parent" };
|
|
45
|
+
const target = wireId(p.messageId);
|
|
46
|
+
for (const address of [p.from, p.to]) {
|
|
47
|
+
header(address, 320);
|
|
48
|
+
check(MAILBOX.test(address), "use one bare mailbox per address");
|
|
49
|
+
}
|
|
50
|
+
const subject = p.subject ?? "";
|
|
51
|
+
header(subject, 998);
|
|
52
|
+
header(p.accountScope, 256);
|
|
53
|
+
check(p.accountScope.length > 0, "accountScope is required");
|
|
54
|
+
check(p.references.length <= 1e3, "too many references");
|
|
55
|
+
let references = p.references.map(wireId).filter((id) => id !== target);
|
|
56
|
+
references.push(target);
|
|
57
|
+
let length = references.reduce((total, id) => total + id.length + 1, -1);
|
|
58
|
+
let start = 0;
|
|
59
|
+
while (references.length - start > 100 || length > 8192) {
|
|
60
|
+
length -= (references[start]?.length ?? 0) + 1;
|
|
61
|
+
start++;
|
|
62
|
+
}
|
|
63
|
+
references = references.slice(start);
|
|
64
|
+
const now = dependencies.now();
|
|
65
|
+
milliseconds(now);
|
|
66
|
+
let expires = null;
|
|
67
|
+
const payload = { subject_message_id: target };
|
|
68
|
+
if (input.kind === "ack") {
|
|
69
|
+
check(signalText("ack", input.status) !== "", "invalid ACK status");
|
|
70
|
+
payload.status = input.status;
|
|
71
|
+
if (input.note !== void 0) {
|
|
72
|
+
check(typeof input.note === "string" && input.note.length <= 2e3 && !input.note.includes("\0") && !/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(input.note), "invalid note");
|
|
73
|
+
payload.note = input.note;
|
|
74
|
+
}
|
|
75
|
+
} else if (input.kind !== "read") {
|
|
76
|
+
check(input.kind === "working", "invalid signal kind");
|
|
77
|
+
milliseconds(input.expiresAtMs);
|
|
78
|
+
check(input.expiresAtMs > now && input.expiresAtMs - now <= 6e4, "working expiry must be within 60 seconds");
|
|
79
|
+
expires = input.expiresAtMs;
|
|
80
|
+
}
|
|
81
|
+
const interaction = dependencies.uuid().toLowerCase(), step = dependencies.uuid().toLowerCase();
|
|
82
|
+
check(UUID$1.test(interaction) && UUID$1.test(step) && interaction !== step, "two distinct UUIDs are required");
|
|
83
|
+
const envelope = signalJson({
|
|
84
|
+
interaction_version: 1,
|
|
85
|
+
interaction_id: `${interaction}@${p.to.split("@")[1]}`,
|
|
86
|
+
protocol: input.kind,
|
|
87
|
+
protocol_version: 1,
|
|
88
|
+
step: input.kind,
|
|
89
|
+
step_id: step,
|
|
90
|
+
prev_step_id: null,
|
|
91
|
+
expires_at: expires === null ? null : new Date(expires).toISOString(),
|
|
92
|
+
payload
|
|
93
|
+
});
|
|
94
|
+
const bytes = encoder.encode(envelope);
|
|
95
|
+
check(bytes.length <= 65536, "signal exceeds 64 KiB");
|
|
96
|
+
const body = {
|
|
97
|
+
from: p.to,
|
|
98
|
+
to: p.from,
|
|
99
|
+
subject: subject || "Re: Your message",
|
|
100
|
+
body_text: signalText(input.kind, payload.status, payload.note),
|
|
101
|
+
in_reply_to: target,
|
|
102
|
+
references,
|
|
103
|
+
attachments: [{
|
|
104
|
+
filename: "interaction.json",
|
|
105
|
+
content_type: "application/json",
|
|
106
|
+
content_base64: base64(bytes)
|
|
107
|
+
}]
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
status: "prepared",
|
|
111
|
+
prepared: Object.freeze({
|
|
112
|
+
accountScope: p.accountScope,
|
|
113
|
+
preparedAtMs: now,
|
|
114
|
+
expiresAtMs: expires,
|
|
115
|
+
idempotencyKey: `signal-${step}`,
|
|
116
|
+
requestJson: signalJson(body)
|
|
117
|
+
})
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** One ordinary send attempt; the caller owns persistence, reconciliation and retries. */
|
|
121
|
+
async function sendPreparedSignal(sendMail, prepared, options) {
|
|
122
|
+
check(options.accountScope === prepared.accountScope && options.accountScope.length > 0, "account scope mismatch");
|
|
123
|
+
const now = options.now();
|
|
124
|
+
milliseconds(now);
|
|
125
|
+
if (prepared.expiresAtMs !== null && now >= prepared.expiresAtMs) return {
|
|
126
|
+
status: "expired",
|
|
127
|
+
idempotencyKey: prepared.idempotencyKey
|
|
128
|
+
};
|
|
129
|
+
return {
|
|
130
|
+
status: "response",
|
|
131
|
+
result: await sendMail(JSON.parse(prepared.requestJson), prepared.idempotencyKey)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Internal shared fallback builder; callers validate the supported signal first. */
|
|
135
|
+
function signalText(kind, status, note) {
|
|
136
|
+
if (kind === "read") return "I read your message.";
|
|
137
|
+
if (kind === "working") return "I am working on your message.";
|
|
138
|
+
const bodies = {
|
|
139
|
+
received: "Received your message.",
|
|
140
|
+
will_process: "I intend to process your message.",
|
|
141
|
+
will_not_process: "I will not process your message."
|
|
142
|
+
};
|
|
143
|
+
const text = status !== void 0 && Object.hasOwn(bodies, status) ? bodies[status] ?? "" : "";
|
|
144
|
+
return note === void 0 ? text : `${text}\n\n${note}`;
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/interactions/classify.ts
|
|
148
|
+
const ENVELOPE_KEYS = [
|
|
149
|
+
"interaction_version",
|
|
150
|
+
"interaction_id",
|
|
151
|
+
"protocol",
|
|
152
|
+
"protocol_version",
|
|
153
|
+
"step",
|
|
154
|
+
"step_id",
|
|
155
|
+
"prev_step_id",
|
|
156
|
+
"expires_at",
|
|
157
|
+
"payload"
|
|
158
|
+
];
|
|
159
|
+
function asciiLower(value) {
|
|
160
|
+
return value.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
|
|
161
|
+
}
|
|
162
|
+
function mediaType(value) {
|
|
163
|
+
const separator = value.indexOf(";");
|
|
164
|
+
let end = separator === -1 ? value.length : separator;
|
|
165
|
+
let start = 0;
|
|
166
|
+
const padding = (index) => value.charCodeAt(index) === 32 || value.charCodeAt(index) === 9;
|
|
167
|
+
while (start < end && padding(start)) start++;
|
|
168
|
+
while (end > start && padding(end - 1)) end--;
|
|
169
|
+
return asciiLower(value.slice(start, end));
|
|
170
|
+
}
|
|
171
|
+
function exactKeys(value, required, optional = []) {
|
|
172
|
+
return required.every((key) => Object.hasOwn(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
|
|
173
|
+
}
|
|
174
|
+
function utcExpiry(value) {
|
|
175
|
+
if (typeof value !== "string") return false;
|
|
176
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?Z$/.exec(value);
|
|
177
|
+
if (!match) return false;
|
|
178
|
+
const year = Number(match[1]), month = Number(match[2]), day = Number(match[3]);
|
|
179
|
+
const days = [
|
|
180
|
+
31,
|
|
181
|
+
year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28,
|
|
182
|
+
31,
|
|
183
|
+
30,
|
|
184
|
+
31,
|
|
185
|
+
30,
|
|
186
|
+
31,
|
|
187
|
+
31,
|
|
188
|
+
30,
|
|
189
|
+
31,
|
|
190
|
+
30,
|
|
191
|
+
31
|
|
192
|
+
];
|
|
193
|
+
return year >= 1970 && month >= 1 && month <= 12 && day >= 1 && day <= (days[month - 1] ?? 0) && Number(match[4]) < 24 && Number(match[5]) < 60 && Number(match[6]) < 60;
|
|
194
|
+
}
|
|
195
|
+
function fallback(result) {
|
|
196
|
+
if (result.status !== "valid") return null;
|
|
197
|
+
const e = result.envelope;
|
|
198
|
+
if (!exactKeys(e, ENVELOPE_KEYS) || e.protocol_version !== 1 || ![
|
|
199
|
+
"ack",
|
|
200
|
+
"read",
|
|
201
|
+
"working"
|
|
202
|
+
].includes(e.protocol) || e.step !== e.protocol || e.prev_step_id !== null || e.interaction_id.split("@")[0]?.toLowerCase() === e.step_id.toLowerCase()) return null;
|
|
203
|
+
if (e.protocol === "working" ? !utcExpiry(e.expires_at) : e.expires_at !== null) return null;
|
|
204
|
+
if (!e.payload || typeof e.payload !== "object" || Array.isArray(e.payload)) return null;
|
|
205
|
+
const p = e.payload;
|
|
206
|
+
if (!exactKeys(p, e.protocol === "ack" ? ["subject_message_id", "status"] : ["subject_message_id"], e.protocol === "ack" ? ["note"] : [])) return null;
|
|
207
|
+
const target = p.subject_message_id;
|
|
208
|
+
if (typeof target !== "string" || target.length > 998 || !/^<[!-~]+@[!-~]+>$/.test(target) || target.slice(1, -1).includes("<") || target.slice(1, -1).includes(">") || target.split("@").length !== 2) return null;
|
|
209
|
+
if (e.protocol !== "ack") return signalText(e.protocol);
|
|
210
|
+
if (typeof p.status !== "string" || ![
|
|
211
|
+
"received",
|
|
212
|
+
"will_process",
|
|
213
|
+
"will_not_process"
|
|
214
|
+
].includes(p.status)) return null;
|
|
215
|
+
if (Object.hasOwn(p, "note") && (typeof p.note !== "string" || p.note.length > 2e3 || p.note.includes("\0"))) return null;
|
|
216
|
+
return signalText("ack", p.status, p.note);
|
|
217
|
+
}
|
|
218
|
+
/** Pure content classification. It establishes no sender, receipt or task authority. */
|
|
219
|
+
function classifySignalContent(input) {
|
|
220
|
+
if (input.inventory.status !== "complete" || !Array.isArray(input.inventory.parts)) return {
|
|
221
|
+
classification: "unavailable",
|
|
222
|
+
reason: "inventory_unavailable"
|
|
223
|
+
};
|
|
224
|
+
const parts = input.inventory.parts;
|
|
225
|
+
const canonical = parts.filter((part) => part.filename !== null && asciiLower(part.filename) === "interaction.json");
|
|
226
|
+
if (!canonical.length) return {
|
|
227
|
+
classification: "plain",
|
|
228
|
+
reason: "no_canonical_part"
|
|
229
|
+
};
|
|
230
|
+
if (canonical.length !== 1) return {
|
|
231
|
+
classification: "mixed_or_unsupported",
|
|
232
|
+
reason: "duplicate_canonical_parts"
|
|
233
|
+
};
|
|
234
|
+
const interaction = input.canonicalPartBytes === null ? void 0 : parseInteractionEnvelope(input.canonicalPartBytes);
|
|
235
|
+
const evidence = interaction === void 0 ? {} : { interaction };
|
|
236
|
+
if (parts.length !== 1) return {
|
|
237
|
+
classification: "mixed_or_unsupported",
|
|
238
|
+
reason: "additional_parts",
|
|
239
|
+
...evidence
|
|
240
|
+
};
|
|
241
|
+
if (!interaction) return {
|
|
242
|
+
classification: "unavailable",
|
|
243
|
+
reason: "part_unavailable"
|
|
244
|
+
};
|
|
245
|
+
if (input.bodies.status !== "complete") return {
|
|
246
|
+
classification: "unavailable",
|
|
247
|
+
reason: "bodies_unavailable",
|
|
248
|
+
interaction
|
|
249
|
+
};
|
|
250
|
+
if (interaction.status === "invalid") return {
|
|
251
|
+
classification: "mixed_or_unsupported",
|
|
252
|
+
reason: "invalid_interaction",
|
|
253
|
+
interaction
|
|
254
|
+
};
|
|
255
|
+
if (mediaType(canonical[0]?.contentType ?? "") !== "application/json") return {
|
|
256
|
+
classification: "mixed_or_unsupported",
|
|
257
|
+
reason: "unsupported_content_type",
|
|
258
|
+
interaction
|
|
259
|
+
};
|
|
260
|
+
const text = fallback(interaction);
|
|
261
|
+
if (text === null) return {
|
|
262
|
+
classification: "mixed_or_unsupported",
|
|
263
|
+
reason: "unsupported_signal",
|
|
264
|
+
interaction
|
|
265
|
+
};
|
|
266
|
+
if (input.bodies.html !== null && input.bodies.html !== "") return {
|
|
267
|
+
classification: "mixed_or_unsupported",
|
|
268
|
+
reason: "html_present",
|
|
269
|
+
interaction
|
|
270
|
+
};
|
|
271
|
+
const actual = (input.bodies.text ?? "").replace(/\r\n/g, "\n");
|
|
272
|
+
const expected = text.replace(/\r\n/g, "\n");
|
|
273
|
+
const matches = actual === "" || actual === "\n" || actual === expected || actual === `${expected}\n`;
|
|
274
|
+
return {
|
|
275
|
+
classification: matches ? "informational_only" : "mixed_or_unsupported",
|
|
276
|
+
reason: matches ? "informational_signal" : "text_mismatch",
|
|
277
|
+
interaction
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
//#endregion
|
|
281
|
+
//#region src/interactions/index.ts
|
|
282
|
+
const MAX_INTERACTION_BYTES = 65536;
|
|
283
|
+
const MAX_INTERACTION_DEPTH = 64;
|
|
284
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
285
|
+
const WIRE_ID = new RegExp(`^${UUID.source.slice(1, -1)}@[^\\s@]+$`, "i");
|
|
286
|
+
function scalarString(value) {
|
|
287
|
+
for (let i = 0; i < value.length; i++) {
|
|
288
|
+
const code = value.charCodeAt(i);
|
|
289
|
+
if (code >= 55296 && code <= 56319) {
|
|
290
|
+
const next = value.charCodeAt(++i);
|
|
291
|
+
if (!(next >= 56320 && next <= 57343)) return false;
|
|
292
|
+
} else if (code >= 56320 && code <= 57343) return false;
|
|
293
|
+
}
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
function safeNumber(value) {
|
|
297
|
+
return Number.isFinite(value) && (!Number.isInteger(value) || Number.isSafeInteger(value));
|
|
298
|
+
}
|
|
299
|
+
function scan(text) {
|
|
300
|
+
let at = 0;
|
|
301
|
+
const fail = () => {
|
|
302
|
+
throw new Error("invalid JSON");
|
|
303
|
+
};
|
|
304
|
+
const space = () => {
|
|
305
|
+
while (/[\x20\t\r\n]/.test(text[at] ?? "")) at++;
|
|
306
|
+
};
|
|
307
|
+
const string = () => {
|
|
308
|
+
const start = at++;
|
|
309
|
+
while (at < text.length) {
|
|
310
|
+
const char = text[at++];
|
|
311
|
+
if (char === "\\") {
|
|
312
|
+
at++;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (char === "\"") {
|
|
316
|
+
const value = JSON.parse(text.slice(start, at));
|
|
317
|
+
if (typeof value !== "string" || !scalarString(value)) fail();
|
|
318
|
+
return value;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return fail();
|
|
322
|
+
};
|
|
323
|
+
const value = (depth) => {
|
|
324
|
+
space();
|
|
325
|
+
const char = text[at];
|
|
326
|
+
if (char === "{" || char === "[") {
|
|
327
|
+
if (depth >= 64) fail();
|
|
328
|
+
at++;
|
|
329
|
+
space();
|
|
330
|
+
const close = char === "{" ? "}" : "]";
|
|
331
|
+
const keys = /* @__PURE__ */ new Set();
|
|
332
|
+
if (text[at] === close) {
|
|
333
|
+
at++;
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
while (at < text.length) {
|
|
337
|
+
if (char === "{") {
|
|
338
|
+
if (text[at] !== "\"") fail();
|
|
339
|
+
const key = string();
|
|
340
|
+
if (keys.has(key)) fail();
|
|
341
|
+
keys.add(key);
|
|
342
|
+
space();
|
|
343
|
+
if (text[at++] !== ":") fail();
|
|
344
|
+
}
|
|
345
|
+
value(depth + 1);
|
|
346
|
+
space();
|
|
347
|
+
if (text[at] === close) {
|
|
348
|
+
at++;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (text[at++] !== ",") fail();
|
|
352
|
+
space();
|
|
353
|
+
}
|
|
354
|
+
fail();
|
|
355
|
+
} else if (char === "\"") string();
|
|
356
|
+
else {
|
|
357
|
+
const start = at;
|
|
358
|
+
while (at < text.length && !/[\x20\t\r\n,\]}]/.test(text[at] ?? "")) at++;
|
|
359
|
+
const token = text.slice(start, at);
|
|
360
|
+
if ([
|
|
361
|
+
"true",
|
|
362
|
+
"false",
|
|
363
|
+
"null"
|
|
364
|
+
].includes(token)) return;
|
|
365
|
+
if (token.length > 128 || !/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(token) || !safeNumber(Number(token))) fail();
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
value(0);
|
|
369
|
+
space();
|
|
370
|
+
if (at !== text.length) fail();
|
|
371
|
+
}
|
|
372
|
+
function envelope(value) {
|
|
373
|
+
const invalid = {
|
|
374
|
+
status: "invalid",
|
|
375
|
+
reason: "invalid_envelope"
|
|
376
|
+
};
|
|
377
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return invalid;
|
|
378
|
+
const obj = value;
|
|
379
|
+
if (!Object.hasOwn(obj, "interaction_version")) return invalid;
|
|
380
|
+
const version = obj.interaction_version;
|
|
381
|
+
if (typeof version !== "number" || !Number.isSafeInteger(version) || version < 1) return invalid;
|
|
382
|
+
if (version !== 1) return {
|
|
383
|
+
status: "unsupported",
|
|
384
|
+
version
|
|
385
|
+
};
|
|
386
|
+
if (![
|
|
387
|
+
"interaction_id",
|
|
388
|
+
"protocol",
|
|
389
|
+
"protocol_version",
|
|
390
|
+
"step",
|
|
391
|
+
"step_id",
|
|
392
|
+
"prev_step_id",
|
|
393
|
+
"expires_at",
|
|
394
|
+
"payload"
|
|
395
|
+
].every((key) => Object.hasOwn(obj, key))) return invalid;
|
|
396
|
+
if (typeof obj.interaction_id !== "string" || !WIRE_ID.test(obj.interaction_id) || typeof obj.protocol !== "string" || !obj.protocol.trim() || typeof obj.protocol_version !== "number" || !Number.isSafeInteger(obj.protocol_version) || obj.protocol_version < 1 || typeof obj.step !== "string" || !obj.step.trim() || typeof obj.step_id !== "string" || !UUID.test(obj.step_id) || !(obj.prev_step_id === null || typeof obj.prev_step_id === "string" && UUID.test(obj.prev_step_id)) || !(obj.expires_at === null || typeof obj.expires_at === "string") || !Object.hasOwn(obj, "payload")) return invalid;
|
|
397
|
+
return {
|
|
398
|
+
status: "valid",
|
|
399
|
+
envelope: obj
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
/** Parse at most 64 KiB of strict UTF-8 JSON. Unknown protocols remain valid. */
|
|
403
|
+
function parseInteractionEnvelope(input) {
|
|
404
|
+
try {
|
|
405
|
+
if (typeof input !== "string" && !(input instanceof Uint8Array)) return {
|
|
406
|
+
status: "invalid",
|
|
407
|
+
reason: "invalid_input"
|
|
408
|
+
};
|
|
409
|
+
if (input.length > 65536) return {
|
|
410
|
+
status: "invalid",
|
|
411
|
+
reason: "too_large"
|
|
412
|
+
};
|
|
413
|
+
const bytes = typeof input === "string" ? void 0 : new Uint8Array(input);
|
|
414
|
+
const text = typeof input === "string" ? input : new TextDecoder("utf-8", {
|
|
415
|
+
fatal: true,
|
|
416
|
+
ignoreBOM: true
|
|
417
|
+
}).decode(bytes);
|
|
418
|
+
if (!scalarString(text)) return {
|
|
419
|
+
status: "invalid",
|
|
420
|
+
reason: "invalid_json"
|
|
421
|
+
};
|
|
422
|
+
if (new TextEncoder().encode(text).length > 65536) return {
|
|
423
|
+
status: "invalid",
|
|
424
|
+
reason: "too_large"
|
|
425
|
+
};
|
|
426
|
+
scan(text);
|
|
427
|
+
const result = envelope(JSON.parse(text));
|
|
428
|
+
return result.status === "invalid" ? result : {
|
|
429
|
+
...result,
|
|
430
|
+
source: bytes ? {
|
|
431
|
+
text,
|
|
432
|
+
bytes
|
|
433
|
+
} : { text }
|
|
434
|
+
};
|
|
435
|
+
} catch {
|
|
436
|
+
return {
|
|
437
|
+
status: "invalid",
|
|
438
|
+
reason: "invalid_json"
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/** Validate a decoded JSON value. Cannot detect lost duplicate keys or original bytes. */
|
|
443
|
+
function validateInteractionEnvelope(input) {
|
|
444
|
+
try {
|
|
445
|
+
let budget = MAX_INTERACTION_BYTES;
|
|
446
|
+
const seen = /* @__PURE__ */ new Set();
|
|
447
|
+
const clone = (value, depth) => {
|
|
448
|
+
if (--budget < 0) throw new Error("too large");
|
|
449
|
+
if (value === null || typeof value === "boolean") return value;
|
|
450
|
+
if (typeof value === "string") {
|
|
451
|
+
if (value.length > budget) throw new Error("too large");
|
|
452
|
+
budget -= new TextEncoder().encode(value).length;
|
|
453
|
+
if (budget < 0 || !scalarString(value)) throw new Error("invalid string");
|
|
454
|
+
return value;
|
|
455
|
+
}
|
|
456
|
+
if (typeof value === "number" && safeNumber(value)) return value;
|
|
457
|
+
if (typeof value !== "object" || !value || depth >= 64 || seen.has(value)) throw new Error("invalid value");
|
|
458
|
+
const array = Array.isArray(value);
|
|
459
|
+
if (array ? Object.getPrototypeOf(value) !== Array.prototype : ![Object.prototype, null].includes(Object.getPrototypeOf(value))) throw new Error("not JSON");
|
|
460
|
+
seen.add(value);
|
|
461
|
+
const out = array ? [] : Object.create(null);
|
|
462
|
+
const keys = Reflect.ownKeys(value);
|
|
463
|
+
if (keys.length - (array ? 1 : 0) > budget) throw new Error("too large");
|
|
464
|
+
if (array && Object.getOwnPropertyDescriptor(value, "length")?.value !== keys.length - 1) throw new Error("sparse array");
|
|
465
|
+
for (const key of keys) {
|
|
466
|
+
if (array && key === "length") continue;
|
|
467
|
+
if (typeof key !== "string") throw new Error("invalid key");
|
|
468
|
+
if (!array && key.length >= budget || array && key.length > 10) throw new Error("too large");
|
|
469
|
+
if (!scalarString(key)) throw new Error("invalid key");
|
|
470
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
471
|
+
if (!descriptor?.enumerable || !("value" in descriptor)) throw new Error("not JSON data");
|
|
472
|
+
if (array && !/^(0|[1-9]\d*)$/.test(key)) throw new Error("invalid array key");
|
|
473
|
+
if (!array) budget -= 1 + new TextEncoder().encode(key).length;
|
|
474
|
+
Object.defineProperty(out, key, {
|
|
475
|
+
value: clone(descriptor.value, depth + 1),
|
|
476
|
+
enumerable: true,
|
|
477
|
+
writable: true,
|
|
478
|
+
configurable: true
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
if (array && out.length !== keys.length - 1) throw new Error("sparse array");
|
|
482
|
+
seen.delete(value);
|
|
483
|
+
return out;
|
|
484
|
+
};
|
|
485
|
+
return envelope(clone(input, 0));
|
|
486
|
+
} catch {
|
|
487
|
+
return {
|
|
488
|
+
status: "invalid",
|
|
489
|
+
reason: "invalid_input"
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
//#endregion
|
|
494
|
+
export { MAX_INTERACTION_BYTES, MAX_INTERACTION_DEPTH, classifySignalContent, parseInteractionEnvelope, prepareSignalEmail, sendPreparedSignal, validateInteractionEnvelope };
|
package/dist/openapi/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as openapiDocument, t as operationManifest } from "../operations.generated-
|
|
1
|
+
import { n as openapiDocument, t as operationManifest } from "../operations.generated-CG4ftbsn.js";
|
|
2
2
|
export { openapiDocument, operationManifest };
|