@wakeshift/sdk 0.2.0 → 0.4.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/CHANGELOG.md +182 -0
- package/README.md +81 -64
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +92 -48
- package/dist/events.js +217 -76
- package/dist/events.js.map +1 -1
- package/dist/harness.js +2 -2
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/key.d.ts +1 -6
- package/dist/key.js +9 -5
- package/dist/key.js.map +1 -1
- package/dist/retry.js +1 -1
- package/dist/retry.js.map +1 -1
- package/dist/spec.d.ts +2 -2
- package/dist/spec.js +2 -2
- package/dist/spec.js.map +1 -1
- package/dist/toolcall.d.ts +154 -0
- package/dist/toolcall.js +558 -0
- package/dist/toolcall.js.map +1 -0
- package/dist/wakeshift.d.ts +75 -241
- package/dist/wakeshift.js +157 -247
- package/dist/wakeshift.js.map +1 -1
- package/package.json +17 -3
- package/dist/guest.d.ts +0 -119
- package/dist/guest.js +0 -506
- package/dist/guest.js.map +0 -1
- package/dist/limits.d.ts +0 -19
- package/dist/limits.js +0 -105
- package/dist/limits.js.map +0 -1
- package/dist/sealed.d.ts +0 -4
- package/dist/sealed.js +0 -38
- package/dist/sealed.js.map +0 -1
package/dist/limits.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// Per-address limits for unauthenticated endpoints (GuestAuth's register
|
|
2
|
-
// and token): who a request is from, behind trusted proxies, and how many
|
|
3
|
-
// requests each address may make. In memory, per process.
|
|
4
|
-
import { isIP } from "node:net";
|
|
5
|
-
// LIMITER_KEYS bounds a limiter's memory: past it, a new address is refused
|
|
6
|
-
// until old ones expire (the endpoints are unauthenticated, and IPv6 makes
|
|
7
|
-
// addresses cheap).
|
|
8
|
-
const LIMITER_KEYS = 100_000;
|
|
9
|
-
/** A rate limiter: at most max events per window (ms) for each key, in memory, pruning idle keys as it goes. A negative max turns it off. */
|
|
10
|
-
export class RateLimiter {
|
|
11
|
-
max;
|
|
12
|
-
window;
|
|
13
|
-
keys;
|
|
14
|
-
seen = new Map();
|
|
15
|
-
pruned = 0;
|
|
16
|
-
constructor(max, window, keys = LIMITER_KEYS) {
|
|
17
|
-
this.max = max;
|
|
18
|
-
this.window = window;
|
|
19
|
-
this.keys = keys;
|
|
20
|
-
}
|
|
21
|
-
allow(key, now) {
|
|
22
|
-
if (this.max < 0)
|
|
23
|
-
return true;
|
|
24
|
-
key = limitKey(key);
|
|
25
|
-
const cut = now - this.window;
|
|
26
|
-
const prune = () => {
|
|
27
|
-
for (const [k, ts] of this.seen)
|
|
28
|
-
if (ts.length === 0 || ts[ts.length - 1] <= cut)
|
|
29
|
-
this.seen.delete(k);
|
|
30
|
-
this.pruned = now;
|
|
31
|
-
};
|
|
32
|
-
if (now - this.pruned > this.window)
|
|
33
|
-
prune();
|
|
34
|
-
if (!this.seen.has(key) && this.seen.size >= this.keys) {
|
|
35
|
-
prune();
|
|
36
|
-
if (this.seen.size >= this.keys)
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
const ts = (this.seen.get(key) ?? []).filter((t) => t > cut);
|
|
40
|
-
if (ts.length >= this.max) {
|
|
41
|
-
this.seen.set(key, ts);
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
ts.push(now);
|
|
45
|
-
this.seen.set(key, ts);
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
/** What an address counts as for the limits: an IPv6 address's /64 (one host usually has a whole /64), anything else as it is. */
|
|
50
|
-
export function limitKey(addr) {
|
|
51
|
-
const a = addr.replace(/%.*$/, "");
|
|
52
|
-
if (isIP(a) !== 6 || /^::ffff:\d+\.\d+\.\d+\.\d+$/i.test(a))
|
|
53
|
-
return addr;
|
|
54
|
-
const groups = expandIPv6(a);
|
|
55
|
-
return groups ? groups.slice(0, 4).join(":") + "::/64" : addr;
|
|
56
|
-
}
|
|
57
|
-
// expandIPv6 is an IPv6 address's eight groups (a dotted IPv4 tail as two).
|
|
58
|
-
function expandIPv6(a) {
|
|
59
|
-
let s = a.toLowerCase();
|
|
60
|
-
const v4 = /(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(s);
|
|
61
|
-
if (v4) {
|
|
62
|
-
const b = v4.slice(1).map(Number);
|
|
63
|
-
s = s.slice(0, v4.index) + ((b[0] << 8) | b[1]).toString(16) + ":" + ((b[2] << 8) | b[3]).toString(16);
|
|
64
|
-
}
|
|
65
|
-
const halves = s.split("::");
|
|
66
|
-
if (halves.length > 2)
|
|
67
|
-
return null;
|
|
68
|
-
const head = halves[0] ? halves[0].split(":") : [];
|
|
69
|
-
const tail = halves.length === 2 && halves[1] ? halves[1].split(":") : [];
|
|
70
|
-
const fill = halves.length === 2 ? 8 - head.length - tail.length : 0;
|
|
71
|
-
const all = [...head, ...Array(Math.max(fill, 0)).fill("0"), ...tail];
|
|
72
|
-
if (all.length !== 8)
|
|
73
|
-
return null;
|
|
74
|
-
return all.map((g) => parseInt(g, 16).toString(16));
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* The request's client address: with hops trusted proxies in front (each
|
|
78
|
-
* appending to X-Forwarded-For), the entry the outermost one added; entries
|
|
79
|
-
* before it are the client's own claims.
|
|
80
|
-
*/
|
|
81
|
-
export function clientAddr(req, hops) {
|
|
82
|
-
// The first X-Forwarded-For header only, as Go's Header.Get reads it.
|
|
83
|
-
const distinct = req.headersDistinct?.["x-forwarded-for"];
|
|
84
|
-
const raw = req.headers["x-forwarded-for"];
|
|
85
|
-
const f = distinct?.[0] ?? (Array.isArray(raw) ? raw[0] : raw);
|
|
86
|
-
if (f && hops > 0) {
|
|
87
|
-
// Only an entry the proxies added: with fewer entries than hops, or one
|
|
88
|
-
// that isn't an address, the header isn't the proxies' (a request that
|
|
89
|
-
// reached the app some other way), so it's ignored.
|
|
90
|
-
const parts = f.split(",");
|
|
91
|
-
if (parts.length >= hops) {
|
|
92
|
-
const ip = parts[parts.length - hops].trim();
|
|
93
|
-
if (isIP(ip))
|
|
94
|
-
return normalizeAddr(ip);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return normalizeAddr(req.socket?.remoteAddress ?? "");
|
|
98
|
-
}
|
|
99
|
-
// normalizeAddr writes an IPv4 address a dual-stack socket reports as
|
|
100
|
-
// ::ffff:a.b.c.d as a.b.c.d, as Go does.
|
|
101
|
-
function normalizeAddr(a) {
|
|
102
|
-
const m = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i.exec(a);
|
|
103
|
-
return m ? m[1] : a;
|
|
104
|
-
}
|
|
105
|
-
//# sourceMappingURL=limits.js.map
|
package/dist/limits.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"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","sourcesContent":["// Per-address limits for unauthenticated endpoints (GuestAuth's register\n// and token): who a request is from, behind trusted proxies, and how many\n// requests each address may make. In memory, per process.\n\nimport type { IncomingMessage } from \"node:http\";\nimport { isIP } from \"node:net\";\n\n// LIMITER_KEYS bounds a limiter's memory: past it, a new address is refused\n// until old ones expire (the endpoints are unauthenticated, and IPv6 makes\n// addresses cheap).\nconst LIMITER_KEYS = 100_000;\n\n/** A rate limiter: at most max events per window (ms) for each key, in memory, pruning idle keys as it goes. A negative max turns it off. */\nexport class RateLimiter {\n private readonly seen = new Map<string, number[]>();\n private pruned = 0;\n\n constructor(\n readonly max: number,\n readonly window: number,\n readonly keys = LIMITER_KEYS,\n ) {}\n\n allow(key: string, now: number): boolean {\n if (this.max < 0) return true;\n key = limitKey(key);\n const cut = now - this.window;\n const prune = () => {\n for (const [k, ts] of this.seen) if (ts.length === 0 || ts[ts.length - 1] <= cut) this.seen.delete(k);\n this.pruned = now;\n };\n if (now - this.pruned > this.window) prune();\n if (!this.seen.has(key) && this.seen.size >= this.keys) {\n prune();\n if (this.seen.size >= this.keys) return false;\n }\n const ts = (this.seen.get(key) ?? []).filter((t) => t > cut);\n if (ts.length >= this.max) {\n this.seen.set(key, ts);\n return false;\n }\n ts.push(now);\n this.seen.set(key, ts);\n return true;\n }\n}\n\n/** What an address counts as for the limits: an IPv6 address's /64 (one host usually has a whole /64), anything else as it is. */\nexport function limitKey(addr: string): string {\n const a = addr.replace(/%.*$/, \"\");\n if (isIP(a) !== 6 || /^::ffff:\\d+\\.\\d+\\.\\d+\\.\\d+$/i.test(a)) return addr;\n const groups = expandIPv6(a);\n return groups ? groups.slice(0, 4).join(\":\") + \"::/64\" : addr;\n}\n\n// expandIPv6 is an IPv6 address's eight groups (a dotted IPv4 tail as two).\nfunction expandIPv6(a: string): string[] | null {\n let s = a.toLowerCase();\n const v4 = /(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)$/.exec(s);\n if (v4) {\n const b = v4.slice(1).map(Number);\n s = s.slice(0, v4.index) + ((b[0] << 8) | b[1]).toString(16) + \":\" + ((b[2] << 8) | b[3]).toString(16);\n }\n const halves = s.split(\"::\");\n if (halves.length > 2) return null;\n const head = halves[0] ? halves[0].split(\":\") : [];\n const tail = halves.length === 2 && halves[1] ? halves[1].split(\":\") : [];\n const fill = halves.length === 2 ? 8 - head.length - tail.length : 0;\n const all = [...head, ...Array(Math.max(fill, 0)).fill(\"0\"), ...tail];\n if (all.length !== 8) return null;\n return all.map((g) => parseInt(g, 16).toString(16));\n}\n\n/**\n * The request's client address: with hops trusted proxies in front (each\n * appending to X-Forwarded-For), the entry the outermost one added; entries\n * before it are the client's own claims.\n */\nexport function clientAddr(req: IncomingMessage, hops: number): string {\n // The first X-Forwarded-For header only, as Go's Header.Get reads it.\n const distinct = (req as IncomingMessage & { headersDistinct?: Record<string, string[] | undefined> }).headersDistinct?.[\"x-forwarded-for\"];\n const raw = req.headers[\"x-forwarded-for\"];\n const f = distinct?.[0] ?? (Array.isArray(raw) ? raw[0] : raw);\n if (f && hops > 0) {\n // Only an entry the proxies added: with fewer entries than hops, or one\n // that isn't an address, the header isn't the proxies' (a request that\n // reached the app some other way), so it's ignored.\n const parts = f.split(\",\");\n if (parts.length >= hops) {\n const ip = parts[parts.length - hops].trim();\n if (isIP(ip)) return normalizeAddr(ip);\n }\n }\n return normalizeAddr(req.socket?.remoteAddress ?? \"\");\n}\n\n// normalizeAddr writes an IPv4 address a dual-stack socket reports as\n// ::ffff:a.b.c.d as a.b.c.d, as Go does.\nfunction normalizeAddr(a: string): string {\n const m = /^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/i.exec(a);\n return m ? m[1] : a;\n}\n"]}
|
package/dist/sealed.d.ts
DELETED
package/dist/sealed.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// Signed values: base64url JSON, a ".", and its HMAC-SHA256 under key, so
|
|
2
|
-
// only the app can make one and nobody can change one. Signed, not
|
|
3
|
-
// encrypted: whoever holds one can read it. GuestAuth's clients, codes and
|
|
4
|
-
// tokens use these, each with its own key.
|
|
5
|
-
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
6
|
-
const B64URL_RE = /^[A-Za-z0-9_-]*$/;
|
|
7
|
-
/** The signed form of v. */
|
|
8
|
-
export function seal(key, v) {
|
|
9
|
-
const p = Buffer.from(JSON.stringify(v)).toString("base64url");
|
|
10
|
-
return p + "." + createHmac("sha256", key).update(p).digest("base64url");
|
|
11
|
-
}
|
|
12
|
-
/** The JSON object s carries, if key signed it; null for anything else. */
|
|
13
|
-
export function unseal(key, s) {
|
|
14
|
-
if (typeof s !== "string")
|
|
15
|
-
return null;
|
|
16
|
-
const i = s.indexOf(".");
|
|
17
|
-
if (i < 0)
|
|
18
|
-
return null;
|
|
19
|
-
const p = s.slice(0, i);
|
|
20
|
-
const sig = s.slice(i + 1);
|
|
21
|
-
if (!B64URL_RE.test(p) || !B64URL_RE.test(sig))
|
|
22
|
-
return null;
|
|
23
|
-
const got = Buffer.from(sig, "base64url");
|
|
24
|
-
// Only the canonical encoding: no second spelling of the same signature.
|
|
25
|
-
if (got.toString("base64url") !== sig)
|
|
26
|
-
return null;
|
|
27
|
-
const want = createHmac("sha256", key).update(p).digest();
|
|
28
|
-
if (got.length !== want.length || !timingSafeEqual(got, want))
|
|
29
|
-
return null;
|
|
30
|
-
try {
|
|
31
|
-
const v = JSON.parse(Buffer.from(p, "base64url").toString("utf8"));
|
|
32
|
-
return v !== null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
//# sourceMappingURL=sealed.js.map
|
package/dist/sealed.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sealed.js","sourceRoot":"","sources":["../src/sealed.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,mEAAmE;AACnE,2EAA2E;AAC3E,2CAA2C;AAE3C,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,SAAS,GAAG,kBAAkB,CAAC;AAErC,4BAA4B;AAC5B,MAAM,UAAU,IAAI,CAAC,GAAW,EAAE,CAAU;IAC1C,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAC3E,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,MAAM,CAAI,GAAW,EAAE,CAAU;IAC/C,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACzB,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACxB,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAC1C,yEAAyE;IACzE,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACnD,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC1D,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3E,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACnE,OAAO,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACpF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["// Signed values: base64url JSON, a \".\", and its HMAC-SHA256 under key, so\n// only the app can make one and nobody can change one. Signed, not\n// encrypted: whoever holds one can read it. GuestAuth's clients, codes and\n// tokens use these, each with its own key.\n\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\n\nconst B64URL_RE = /^[A-Za-z0-9_-]*$/;\n\n/** The signed form of v. */\nexport function seal(key: Buffer, v: unknown): string {\n const p = Buffer.from(JSON.stringify(v)).toString(\"base64url\");\n return p + \".\" + createHmac(\"sha256\", key).update(p).digest(\"base64url\");\n}\n\n/** The JSON object s carries, if key signed it; null for anything else. */\nexport function unseal<T>(key: Buffer, s: unknown): T | null {\n if (typeof s !== \"string\") return null;\n const i = s.indexOf(\".\");\n if (i < 0) return null;\n const p = s.slice(0, i);\n const sig = s.slice(i + 1);\n if (!B64URL_RE.test(p) || !B64URL_RE.test(sig)) return null;\n const got = Buffer.from(sig, \"base64url\");\n // Only the canonical encoding: no second spelling of the same signature.\n if (got.toString(\"base64url\") !== sig) return null;\n const want = createHmac(\"sha256\", key).update(p).digest();\n if (got.length !== want.length || !timingSafeEqual(got, want)) return null;\n try {\n const v = JSON.parse(Buffer.from(p, \"base64url\").toString(\"utf8\"));\n return v !== null && typeof v === \"object\" && !Array.isArray(v) ? (v as T) : null;\n } catch {\n return null;\n }\n}\n"]}
|