@volter/teams 0.1.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 +21 -0
- package/admission.mjs +62 -0
- package/browser-channel.mjs +157 -0
- package/channel.mjs +319 -0
- package/client.mjs +119 -0
- package/home.mjs +220 -0
- package/identity.mjs +85 -0
- package/index.mjs +8 -0
- package/machine-core.mjs +397 -0
- package/package.json +39 -0
- package/products.mjs +157 -0
- package/server/auth.mjs +13 -0
- package/server/core.mjs +64 -0
- package/server/events.mjs +3 -0
- package/server/http.mjs +211 -0
- package/server/identity.mjs +15 -0
- package/server/machine-link.mjs +228 -0
- package/server/migrations/001.mjs +298 -0
- package/server/migrations/002.mjs +13 -0
- package/server/migrations/003.mjs +11 -0
- package/server/migrations/004.mjs +35 -0
- package/server/migrations/index.mjs +6 -0
- package/server/request-json.mjs +74 -0
- package/server/wire.mjs +10 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ContractError } from './wire.mjs';
|
|
2
|
+
|
|
3
|
+
function rejectDuplicateKeys(text) {
|
|
4
|
+
let at = 0;
|
|
5
|
+
const whitespace = () => { while (/\s/.test(text[at] ?? '')) at++; };
|
|
6
|
+
const string = () => {
|
|
7
|
+
const start = at++;
|
|
8
|
+
while (at < text.length) {
|
|
9
|
+
if (text[at] === '\\') { at += 2; continue; }
|
|
10
|
+
if (text[at++] === '"') return JSON.parse(text.slice(start, at));
|
|
11
|
+
}
|
|
12
|
+
throw new Error('unterminated string');
|
|
13
|
+
};
|
|
14
|
+
const value = () => {
|
|
15
|
+
whitespace();
|
|
16
|
+
if (text[at] === '{') return object();
|
|
17
|
+
if (text[at] === '[') {
|
|
18
|
+
at++; whitespace();
|
|
19
|
+
if (text[at] === ']') { at++; return; }
|
|
20
|
+
for (;;) {
|
|
21
|
+
value(); whitespace();
|
|
22
|
+
if (text[at] === ']') { at++; return; }
|
|
23
|
+
if (text[at++] !== ',') throw new Error('invalid array');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (text[at] === '"') { string(); return; }
|
|
27
|
+
const match = /^(?:-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?|true|false|null)/.exec(text.slice(at));
|
|
28
|
+
if (!match) throw new Error('invalid value');
|
|
29
|
+
at += match[0].length;
|
|
30
|
+
};
|
|
31
|
+
const object = () => {
|
|
32
|
+
at++; const keys = new Set(); whitespace();
|
|
33
|
+
if (text[at] === '}') { at++; return; }
|
|
34
|
+
for (;;) {
|
|
35
|
+
whitespace();
|
|
36
|
+
if (text[at] !== '"') throw new Error('invalid object key');
|
|
37
|
+
const key = string();
|
|
38
|
+
if (keys.has(key)) throw new Error('duplicate object key');
|
|
39
|
+
keys.add(key); whitespace();
|
|
40
|
+
if (text[at++] !== ':') throw new Error('invalid object');
|
|
41
|
+
value(); whitespace();
|
|
42
|
+
if (text[at] === '}') { at++; return; }
|
|
43
|
+
if (text[at++] !== ',') throw new Error('invalid object');
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
value(); whitespace();
|
|
47
|
+
if (at !== text.length) throw new Error('trailing JSON');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function parseJson(req, fail, maxBytes = 8 * 1024 * 1024) {
|
|
51
|
+
if (Object.hasOwn(req, '_parsedBody')) return req._parsedBody;
|
|
52
|
+
let size = 0; const chunks = [];
|
|
53
|
+
for await (const chunk of req) {
|
|
54
|
+
size += chunk.length;
|
|
55
|
+
if (size > maxBytes) fail(413, 'request_too_large', 'request body exceeds 8 MiB');
|
|
56
|
+
chunks.push(chunk);
|
|
57
|
+
}
|
|
58
|
+
if (!size) return (req._parsedBody = null);
|
|
59
|
+
try {
|
|
60
|
+
const text = new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks));
|
|
61
|
+
rejectDuplicateKeys(text);
|
|
62
|
+
return (req._parsedBody = JSON.parse(text, (_key, value) => {
|
|
63
|
+
if (typeof value === 'number' && !Number.isFinite(value)) throw new Error('non-finite number');
|
|
64
|
+
return value;
|
|
65
|
+
}));
|
|
66
|
+
} catch {
|
|
67
|
+
fail(400, 'invalid_request', 'request body must be JSON with unique object keys');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function exact(value, keys, name = 'body') {
|
|
72
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new ContractError(`${name} must be an object`);
|
|
73
|
+
for (const key of Object.keys(value)) if (!keys.includes(key)) throw new ContractError(`${name}.${key} is unknown`);
|
|
74
|
+
}
|
package/server/wire.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// The Teams wire's shared pieces: the contract error and its failure body, canonical
|
|
2
|
+
// JSON (what digests are taken over), and the opaque cursor and hash helpers.
|
|
3
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
4
|
+
|
|
5
|
+
export class ContractError extends Error { constructor(message, details = null) { super(message); this.name = 'ContractError'; this.code = 'invalid_request'; this.details = details; } }
|
|
6
|
+
export function canonicalJson(value) { if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`; if (value && typeof value === 'object') return `{${Object.keys(value).sort().map(k => `${JSON.stringify(k)}:${canonicalJson(value[k])}`).join(',')}}`; return JSON.stringify(value); }
|
|
7
|
+
export function validationFailure(error, requestId) { return { version:1, error:{ code:error?.code ?? 'invalid_request', message:error?.message ?? 'invalid request', request_id:requestId, retryable:false, details:error?.details ?? null } }; }
|
|
8
|
+
export const cursorToken = () => randomBytes(32).toString('base64url');
|
|
9
|
+
export const cursorHash = token => createHash('sha256').update(token).digest('hex');
|
|
10
|
+
export const sha256 = value => createHash('sha256').update(value).digest('hex');
|