threeu-sdk 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 +176 -0
- package/dist/admin/index.d.ts +22 -0
- package/dist/admin/index.js +17 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/chunk-2TGBSV6L.js +197 -0
- package/dist/chunk-2TGBSV6L.js.map +1 -0
- package/dist/chunk-6S7OFN23.js +81 -0
- package/dist/chunk-6S7OFN23.js.map +1 -0
- package/dist/chunk-6ZCBDWWQ.js +140 -0
- package/dist/chunk-6ZCBDWWQ.js.map +1 -0
- package/dist/chunk-BCUODRZW.js +78 -0
- package/dist/chunk-BCUODRZW.js.map +1 -0
- package/dist/chunk-H3XILKGI.js +70 -0
- package/dist/chunk-H3XILKGI.js.map +1 -0
- package/dist/chunk-HYSJ6YPN.js +425 -0
- package/dist/chunk-HYSJ6YPN.js.map +1 -0
- package/dist/chunk-LFF5LPWT.js +122 -0
- package/dist/chunk-LFF5LPWT.js.map +1 -0
- package/dist/chunk-OXAQGEMQ.js +3 -0
- package/dist/chunk-OXAQGEMQ.js.map +1 -0
- package/dist/chunk-USFJIM5K.js +195 -0
- package/dist/chunk-USFJIM5K.js.map +1 -0
- package/dist/chunk-ZWLFJIAM.js +69 -0
- package/dist/chunk-ZWLFJIAM.js.map +1 -0
- package/dist/define-B6ZJMWDI.d.ts +24 -0
- package/dist/developer/index.d.ts +120 -0
- package/dist/developer/index.js +106 -0
- package/dist/developer/index.js.map +1 -0
- package/dist/errors/index.d.ts +83 -0
- package/dist/errors/index.js +4 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index-ksmDFDZc.d.ts +90 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/index.d.ts +13 -0
- package/dist/plugin/index.js +27 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/server/index.d.ts +70 -0
- package/dist/plugin/server/index.js +105 -0
- package/dist/plugin/server/index.js.map +1 -0
- package/dist/pos/index.d.ts +26 -0
- package/dist/pos/index.js +24 -0
- package/dist/pos/index.js.map +1 -0
- package/dist/react/index.d.ts +5 -0
- package/dist/react/index.js +7 -0
- package/dist/react/index.js.map +1 -0
- package/dist/session-BFDRm-KJ.d.ts +94 -0
- package/dist/storefront/index.d.ts +76 -0
- package/dist/storefront/index.js +6 -0
- package/dist/storefront/index.js.map +1 -0
- package/dist/testing/index.d.ts +82 -0
- package/dist/testing/index.js +106 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/theme/index.d.ts +109 -0
- package/dist/theme/index.js +152 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/threeu-BesFjXdw.d.ts +143 -0
- package/dist/types-BoGD3IXz.d.ts +173 -0
- package/dist/types-DfyYnoLn.d.ts +248 -0
- package/dist/webhooks/index.d.ts +36 -0
- package/dist/webhooks/index.js +4 -0
- package/dist/webhooks/index.js.map +1 -0
- package/package.json +110 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { isDevelopment, warnOnce } from './chunk-H3XILKGI.js';
|
|
2
|
+
import { ThreeuManifestError } from './chunk-LFF5LPWT.js';
|
|
3
|
+
|
|
4
|
+
// src/core/validate.ts
|
|
5
|
+
var Issues = class {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.list = [];
|
|
8
|
+
}
|
|
9
|
+
add(path, message) {
|
|
10
|
+
this.list.push({ path, message });
|
|
11
|
+
}
|
|
12
|
+
child(path) {
|
|
13
|
+
return new ScopedIssues(this, path);
|
|
14
|
+
}
|
|
15
|
+
get ok() {
|
|
16
|
+
return this.list.length === 0;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var ScopedIssues = class {
|
|
20
|
+
constructor(parent, base) {
|
|
21
|
+
this.parent = parent;
|
|
22
|
+
this.base = base;
|
|
23
|
+
}
|
|
24
|
+
add(path, message) {
|
|
25
|
+
this.parent.add(path ? `${this.base}.${path}` : this.base, message);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var isString = (v) => typeof v === "string";
|
|
29
|
+
var isNonEmptyString = (v) => typeof v === "string" && v.trim().length > 0;
|
|
30
|
+
var isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
31
|
+
var KEY_RE = /^[a-z][a-z0-9]*([._:][a-z0-9]+)*$/i;
|
|
32
|
+
var isKeyLike = (v) => isString(v) && KEY_RE.test(v);
|
|
33
|
+
var SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
34
|
+
var isSlug = (v) => isString(v) && SLUG_RE.test(v);
|
|
35
|
+
function parseHttpUrl(v) {
|
|
36
|
+
if (!isString(v)) return null;
|
|
37
|
+
try {
|
|
38
|
+
const url = new URL(v);
|
|
39
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url : null;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function isPrivateHost(host) {
|
|
45
|
+
const h = host.toLowerCase();
|
|
46
|
+
if (h === "localhost" || h === "127.0.0.1" || h === "::1" || h.endsWith(".local")) return true;
|
|
47
|
+
if (/^10\./.test(h)) return true;
|
|
48
|
+
if (/^192\.168\./.test(h)) return true;
|
|
49
|
+
if (/^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
|
|
50
|
+
if (/^169\.254\./.test(h)) return true;
|
|
51
|
+
if (/^fe80:/.test(h) || /^fc00:/.test(h) || /^fd[0-9a-f]{2}:/.test(h)) return true;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/manifests/build.ts
|
|
56
|
+
var DEFAULT_SIGNATURE_HEADER = "X-ThreeU-Signature";
|
|
57
|
+
var DEFAULT_TIMESTAMP_HEADER = "X-ThreeU-Timestamp";
|
|
58
|
+
var WEBHOOK_SIGNATURE_HEADER = "X-Plugin-Signature";
|
|
59
|
+
var WEBHOOK_BRAND_HEADER = "X-Brand-ID";
|
|
60
|
+
var PERMISSION_RE = /^[a-z_]+:(read|write|delete|\*)$/;
|
|
61
|
+
function fail(issues) {
|
|
62
|
+
throw new ThreeuManifestError(`Manifest is invalid (${issues.list.length} issue(s))`, {
|
|
63
|
+
issues: issues.list.map((i) => `${i.path}: ${i.message}`),
|
|
64
|
+
details: issues.list
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function validateProvider(provider, issues) {
|
|
68
|
+
if (!provider || !isObject(provider)) {
|
|
69
|
+
issues.add("provider", "is required");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!isNonEmptyString(provider.type)) {
|
|
73
|
+
issues.add("provider.type", "is required");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (provider.type === "remote_http") {
|
|
77
|
+
const url = parseHttpUrl(provider.baseUrl);
|
|
78
|
+
if (!url) {
|
|
79
|
+
issues.add("provider.baseUrl", "must be an absolute http(s) URL");
|
|
80
|
+
} else {
|
|
81
|
+
if (url.protocol === "http:" && !isDevelopment()) {
|
|
82
|
+
issues.add("provider.baseUrl", "must use https in production");
|
|
83
|
+
}
|
|
84
|
+
if (isPrivateHost(url.hostname) && !isDevelopment()) {
|
|
85
|
+
issues.add("provider.baseUrl", "must not point at a private/localhost address in production (SSRF)");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!provider.healthPath) {
|
|
89
|
+
warnOnce(`no-health-path:${provider.baseUrl}`, "remote_http provider has no healthPath; health checks will be skipped.");
|
|
90
|
+
}
|
|
91
|
+
for (const [name, action] of Object.entries(provider.actions ?? {})) {
|
|
92
|
+
if (!isKeyLike(name)) issues.add(`provider.actions.${name}`, "action name must be dotted lowercase, e.g. shipping.get_rates");
|
|
93
|
+
if (!isNonEmptyString(action.path)) issues.add(`provider.actions.${name}.path`, "is required");
|
|
94
|
+
if (!action.method) issues.add(`provider.actions.${name}.method`, "is required");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function buildProviderManifest(provider) {
|
|
99
|
+
const manifest = { type: provider.type };
|
|
100
|
+
if (provider.baseUrl) manifest.base_url = provider.baseUrl;
|
|
101
|
+
if (provider.healthPath) manifest.health_path = provider.healthPath;
|
|
102
|
+
if (provider.type === "remote_http") {
|
|
103
|
+
manifest.auth = {
|
|
104
|
+
type: "hmac_sha256",
|
|
105
|
+
signature_header: DEFAULT_SIGNATURE_HEADER,
|
|
106
|
+
timestamp_header: DEFAULT_TIMESTAMP_HEADER
|
|
107
|
+
};
|
|
108
|
+
if (provider.actions) {
|
|
109
|
+
manifest.actions = {};
|
|
110
|
+
for (const [name, action] of Object.entries(provider.actions)) {
|
|
111
|
+
manifest.actions[name] = {
|
|
112
|
+
method: action.method,
|
|
113
|
+
path: action.path,
|
|
114
|
+
...action.timeoutMs ? { timeout_ms: action.timeoutMs } : {}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (provider.webhooks) {
|
|
119
|
+
manifest.webhooks = {
|
|
120
|
+
inbound_url_mode: "threeu_generated",
|
|
121
|
+
signature_header: WEBHOOK_SIGNATURE_HEADER,
|
|
122
|
+
brand_header: WEBHOOK_BRAND_HEADER,
|
|
123
|
+
events: provider.webhooks.events
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return manifest;
|
|
128
|
+
}
|
|
129
|
+
function buildPluginManifest(def) {
|
|
130
|
+
const issues = new Issues();
|
|
131
|
+
if (!isNonEmptyString(def.name)) issues.add("name", "is required");
|
|
132
|
+
if (!isSlug(def.slug)) issues.add("slug", "must be a slug (lowercase letters, digits, hyphens)");
|
|
133
|
+
if (!isNonEmptyString(def.category)) issues.add("category", "is required");
|
|
134
|
+
for (const perm of def.permissions ?? []) {
|
|
135
|
+
if (!PERMISSION_RE.test(perm)) issues.add("permissions", `"${perm}" must look like "resource:read|write|delete|*"`);
|
|
136
|
+
}
|
|
137
|
+
validateProvider(def.provider, issues);
|
|
138
|
+
if (!issues.ok) fail(issues);
|
|
139
|
+
return {
|
|
140
|
+
type: "plugin",
|
|
141
|
+
runtime: def.provider.type,
|
|
142
|
+
name: def.name,
|
|
143
|
+
slug: def.slug,
|
|
144
|
+
version: def.version ?? "1.0.0",
|
|
145
|
+
category: def.category,
|
|
146
|
+
surface_type: def.surfaceType ?? "plugin",
|
|
147
|
+
trigger_mode: def.triggerMode ?? "manual",
|
|
148
|
+
ownership: def.ownership ?? "community",
|
|
149
|
+
...def.description ? { description: def.description } : {},
|
|
150
|
+
permissions: def.permissions ?? [],
|
|
151
|
+
...def.settings ? { config_schema: def.settings } : {},
|
|
152
|
+
...def.subscribedActions ? { subscribed_actions: def.subscribedActions } : {},
|
|
153
|
+
provider: buildProviderManifest(def.provider),
|
|
154
|
+
...def.adminBlocks ? { admin_blocks: def.adminBlocks } : {}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function buildAppManifest(def) {
|
|
158
|
+
const issues = new Issues();
|
|
159
|
+
if (!isNonEmptyString(def.name)) issues.add("name", "is required");
|
|
160
|
+
if (!isSlug(def.slug)) issues.add("slug", "must be a slug");
|
|
161
|
+
if (def.surface !== "admin" && def.surface !== "pos") issues.add("surface", 'must be "admin" or "pos"');
|
|
162
|
+
if (def.provider) validateProvider(def.provider, issues);
|
|
163
|
+
if (!issues.ok) fail(issues);
|
|
164
|
+
const base = {
|
|
165
|
+
name: def.name,
|
|
166
|
+
slug: def.slug,
|
|
167
|
+
version: def.version ?? "1.0.0",
|
|
168
|
+
permissions: def.permissions ?? [],
|
|
169
|
+
...def.settings ? { config_schema: def.settings } : {},
|
|
170
|
+
...def.blocks ? { blocks: def.blocks } : {},
|
|
171
|
+
...def.extensionPoints ? { extension_points: def.extensionPoints } : {},
|
|
172
|
+
...def.provider ? { provider: buildProviderManifest(def.provider) } : {}
|
|
173
|
+
};
|
|
174
|
+
return def.surface === "pos" ? { type: "pos_app", ...base } : { type: "admin_app", ...base };
|
|
175
|
+
}
|
|
176
|
+
function buildThemeManifest(def) {
|
|
177
|
+
const issues = new Issues();
|
|
178
|
+
if (!isNonEmptyString(def.name)) issues.add("name", "is required");
|
|
179
|
+
if (!isSlug(def.slug)) issues.add("slug", "must be a slug");
|
|
180
|
+
if (!Array.isArray(def.pages) || def.pages.length === 0) issues.add("pages", "must list at least one page");
|
|
181
|
+
if (!issues.ok) fail(issues);
|
|
182
|
+
return {
|
|
183
|
+
type: "theme",
|
|
184
|
+
name: def.name,
|
|
185
|
+
slug: def.slug,
|
|
186
|
+
version: def.version ?? "1.0.0",
|
|
187
|
+
visibility: def.visibility ?? "public",
|
|
188
|
+
pages: def.pages,
|
|
189
|
+
editable_fields: def.fields ?? []
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export { buildAppManifest, buildPluginManifest, buildThemeManifest };
|
|
194
|
+
//# sourceMappingURL=chunk-USFJIM5K.js.map
|
|
195
|
+
//# sourceMappingURL=chunk-USFJIM5K.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/validate.ts","../src/manifests/build.ts"],"names":[],"mappings":"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","file":"chunk-USFJIM5K.js","sourcesContent":["/**\n * Lightweight, dependency-free validation toolkit.\n *\n * We deliberately avoid bundling zod/valibot: the SDK ships zero runtime deps and\n * only validates a handful of security-critical, developer-authored inputs\n * (manifests, provider URLs, action/permission names). The brief explicitly\n * permits \"lightweight internal validators\".\n */\n\nexport interface ValidationIssue {\n path: string;\n message: string;\n}\n\n/** Accumulates issues while walking a nested structure. */\nexport class Issues {\n readonly list: ValidationIssue[] = [];\n\n add(path: string, message: string): void {\n this.list.push({ path, message });\n }\n\n child(path: string): ScopedIssues {\n return new ScopedIssues(this, path);\n }\n\n get ok(): boolean {\n return this.list.length === 0;\n }\n}\n\nexport class ScopedIssues {\n constructor(\n private readonly parent: Issues,\n private readonly base: string,\n ) {}\n add(path: string, message: string): void {\n this.parent.add(path ? `${this.base}.${path}` : this.base, message);\n }\n}\n\nexport const isString = (v: unknown): v is string => typeof v === \"string\";\nexport const isNonEmptyString = (v: unknown): v is string => typeof v === \"string\" && v.trim().length > 0;\nexport const isObject = (v: unknown): v is Record<string, unknown> =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\nexport const isArray = (v: unknown): v is unknown[] => Array.isArray(v);\n\n/** `orders:read`, `shipping.get_rates`, `home.hero.title` — token-ish keys. */\nconst KEY_RE = /^[a-z][a-z0-9]*([._:][a-z0-9]+)*$/i;\nexport const isKeyLike = (v: unknown): v is string => isString(v) && KEY_RE.test(v);\n\n/** URL/manifest slug: lowercase letters, digits, hyphens, e.g. `modern-luxury`. */\nconst SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;\nexport const isSlug = (v: unknown): v is string => isString(v) && SLUG_RE.test(v);\n\n/** Validate an absolute http(s) URL. Returns the URL or null. */\nexport function parseHttpUrl(v: unknown): URL | null {\n if (!isString(v)) return null;\n try {\n const url = new URL(v);\n return url.protocol === \"http:\" || url.protocol === \"https:\" ? url : null;\n } catch {\n return null;\n }\n}\n\n/** RFC1918 / loopback / link-local host check (used for SSRF-style warnings). */\nexport function isPrivateHost(host: string): boolean {\n const h = host.toLowerCase();\n if (h === \"localhost\" || h === \"127.0.0.1\" || h === \"::1\" || h.endsWith(\".local\")) return true;\n if (/^10\\./.test(h)) return true;\n if (/^192\\.168\\./.test(h)) return true;\n if (/^172\\.(1[6-9]|2\\d|3[01])\\./.test(h)) return true;\n if (/^169\\.254\\./.test(h)) return true;\n if (/^fe80:/.test(h) || /^fc00:/.test(h) || /^fd[0-9a-f]{2}:/.test(h)) return true;\n return false;\n}\n\n/** Convert camelCase keys to snake_case recursively (backend manifest convention). */\nexport function camelToSnakeKeys(input: unknown): unknown {\n if (Array.isArray(input)) return input.map(camelToSnakeKeys);\n if (input && typeof input === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(input)) {\n const snake = key.replace(/([A-Z])/g, \"_$1\").replace(/__+/g, \"_\").toLowerCase();\n out[snake] = camelToSnakeKeys(value);\n }\n return out;\n }\n return input;\n}\n","/**\n * Manifest validation + normalization. Turns developer-authored *Definition*\n * objects into the snake_case *Manifest* JSON the backend stores, surfacing\n * problems as a `ThreeuManifestError` with a precise issue list.\n *\n * Security rule (brief §18): community developers declare `provider.type` only.\n * We never emit `provider_class` — that mapping is the backend's job.\n */\nimport { ThreeuManifestError } from \"../core/errors\";\nimport { Issues, isNonEmptyString, isObject, isKeyLike, isSlug, parseHttpUrl, isPrivateHost } from \"../core/validate\";\nimport { isDevelopment } from \"../core/runtime\";\nimport { warnOnce } from \"../core/safety\";\nimport type {\n AdminAppManifest,\n AppDefinition,\n PluginDefinition,\n PluginManifest,\n POSAppManifest,\n ProviderDefinition,\n ProviderManifest,\n ThemeDefinition,\n ThemeManifest,\n} from \"./types\";\n\nconst DEFAULT_SIGNATURE_HEADER = \"X-ThreeU-Signature\";\nconst DEFAULT_TIMESTAMP_HEADER = \"X-ThreeU-Timestamp\";\nconst WEBHOOK_SIGNATURE_HEADER = \"X-Plugin-Signature\";\nconst WEBHOOK_BRAND_HEADER = \"X-Brand-ID\";\n\nconst PERMISSION_RE = /^[a-z_]+:(read|write|delete|\\*)$/;\n\nfunction fail(issues: Issues): never {\n throw new ThreeuManifestError(`Manifest is invalid (${issues.list.length} issue(s))`, {\n issues: issues.list.map((i) => `${i.path}: ${i.message}`),\n details: issues.list,\n });\n}\n\nfunction validateProvider(provider: ProviderDefinition | undefined, issues: Issues): void {\n if (!provider || !isObject(provider)) {\n issues.add(\"provider\", \"is required\");\n return;\n }\n if (!isNonEmptyString(provider.type)) {\n issues.add(\"provider.type\", \"is required\");\n return;\n }\n if (provider.type === \"remote_http\") {\n const url = parseHttpUrl(provider.baseUrl);\n if (!url) {\n issues.add(\"provider.baseUrl\", \"must be an absolute http(s) URL\");\n } else {\n if (url.protocol === \"http:\" && !isDevelopment()) {\n issues.add(\"provider.baseUrl\", \"must use https in production\");\n }\n if (isPrivateHost(url.hostname) && !isDevelopment()) {\n issues.add(\"provider.baseUrl\", \"must not point at a private/localhost address in production (SSRF)\");\n }\n }\n if (!provider.healthPath) {\n warnOnce(`no-health-path:${provider.baseUrl}`, \"remote_http provider has no healthPath; health checks will be skipped.\");\n }\n for (const [name, action] of Object.entries(provider.actions ?? {})) {\n if (!isKeyLike(name)) issues.add(`provider.actions.${name}`, \"action name must be dotted lowercase, e.g. shipping.get_rates\");\n if (!isNonEmptyString(action.path)) issues.add(`provider.actions.${name}.path`, \"is required\");\n if (!action.method) issues.add(`provider.actions.${name}.method`, \"is required\");\n }\n }\n}\n\nfunction buildProviderManifest(provider: ProviderDefinition): ProviderManifest {\n const manifest: ProviderManifest = { type: provider.type };\n if (provider.baseUrl) manifest.base_url = provider.baseUrl;\n if (provider.healthPath) manifest.health_path = provider.healthPath;\n\n if (provider.type === \"remote_http\") {\n manifest.auth = {\n type: \"hmac_sha256\",\n signature_header: DEFAULT_SIGNATURE_HEADER,\n timestamp_header: DEFAULT_TIMESTAMP_HEADER,\n };\n if (provider.actions) {\n manifest.actions = {};\n for (const [name, action] of Object.entries(provider.actions)) {\n manifest.actions[name] = {\n method: action.method,\n path: action.path,\n ...(action.timeoutMs ? { timeout_ms: action.timeoutMs } : {}),\n };\n }\n }\n if (provider.webhooks) {\n manifest.webhooks = {\n inbound_url_mode: \"threeu_generated\",\n signature_header: WEBHOOK_SIGNATURE_HEADER,\n brand_header: WEBHOOK_BRAND_HEADER,\n events: provider.webhooks.events,\n };\n }\n }\n return manifest;\n}\n\n/** Validate + normalize a plugin definition into its manifest JSON. */\nexport function buildPluginManifest(def: PluginDefinition): PluginManifest {\n const issues = new Issues();\n if (!isNonEmptyString(def.name)) issues.add(\"name\", \"is required\");\n if (!isSlug(def.slug)) issues.add(\"slug\", \"must be a slug (lowercase letters, digits, hyphens)\");\n if (!isNonEmptyString(def.category)) issues.add(\"category\", \"is required\");\n for (const perm of def.permissions ?? []) {\n if (!PERMISSION_RE.test(perm)) issues.add(\"permissions\", `\"${perm}\" must look like \"resource:read|write|delete|*\"`);\n }\n validateProvider(def.provider, issues);\n if (!issues.ok) fail(issues);\n\n return {\n type: \"plugin\",\n runtime: def.provider.type,\n name: def.name,\n slug: def.slug,\n version: def.version ?? \"1.0.0\",\n category: def.category,\n surface_type: def.surfaceType ?? \"plugin\",\n trigger_mode: def.triggerMode ?? \"manual\",\n ownership: def.ownership ?? \"community\",\n ...(def.description ? { description: def.description } : {}),\n permissions: def.permissions ?? [],\n ...(def.settings ? { config_schema: def.settings } : {}),\n ...(def.subscribedActions ? { subscribed_actions: def.subscribedActions } : {}),\n provider: buildProviderManifest(def.provider),\n ...(def.adminBlocks ? { admin_blocks: def.adminBlocks } : {}),\n };\n}\n\n/** Validate + normalize an admin/POS app definition into its manifest JSON. */\nexport function buildAppManifest(def: AppDefinition): AdminAppManifest | POSAppManifest {\n const issues = new Issues();\n if (!isNonEmptyString(def.name)) issues.add(\"name\", \"is required\");\n if (!isSlug(def.slug)) issues.add(\"slug\", \"must be a slug\");\n if (def.surface !== \"admin\" && def.surface !== \"pos\") issues.add(\"surface\", 'must be \"admin\" or \"pos\"');\n if (def.provider) validateProvider(def.provider, issues);\n if (!issues.ok) fail(issues);\n\n const base = {\n name: def.name,\n slug: def.slug,\n version: def.version ?? \"1.0.0\",\n permissions: def.permissions ?? [],\n ...(def.settings ? { config_schema: def.settings } : {}),\n ...(def.blocks ? { blocks: def.blocks } : {}),\n ...(def.extensionPoints ? { extension_points: def.extensionPoints } : {}),\n ...(def.provider ? { provider: buildProviderManifest(def.provider) } : {}),\n };\n return def.surface === \"pos\"\n ? ({ type: \"pos_app\", ...base } as POSAppManifest)\n : ({ type: \"admin_app\", ...base } as AdminAppManifest);\n}\n\n/** Validate + normalize a theme definition into its manifest JSON. */\nexport function buildThemeManifest(def: ThemeDefinition): ThemeManifest {\n const issues = new Issues();\n if (!isNonEmptyString(def.name)) issues.add(\"name\", \"is required\");\n if (!isSlug(def.slug)) issues.add(\"slug\", \"must be a slug\");\n if (!Array.isArray(def.pages) || def.pages.length === 0) issues.add(\"pages\", \"must list at least one page\");\n if (!issues.ok) fail(issues);\n\n return {\n type: \"theme\",\n name: def.name,\n slug: def.slug,\n version: def.version ?? \"1.0.0\",\n visibility: def.visibility ?? \"public\",\n pages: def.pages,\n editable_fields: def.fields ?? [],\n };\n}\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ThreeuWebhookSignatureError } from './chunk-LFF5LPWT.js';
|
|
2
|
+
|
|
3
|
+
// src/webhooks/index.ts
|
|
4
|
+
var enc = new TextEncoder();
|
|
5
|
+
async function hmacSha256Hex(secret, message) {
|
|
6
|
+
const subtle = globalThis.crypto?.subtle;
|
|
7
|
+
if (!subtle) {
|
|
8
|
+
throw new ThreeuWebhookSignatureError(
|
|
9
|
+
"Web Crypto (crypto.subtle) is unavailable in this runtime; cannot compute HMAC."
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
const key = await subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
13
|
+
const sig = await subtle.sign("HMAC", key, enc.encode(message));
|
|
14
|
+
return bufferToHex(sig);
|
|
15
|
+
}
|
|
16
|
+
function bufferToHex(buffer) {
|
|
17
|
+
const bytes = new Uint8Array(buffer);
|
|
18
|
+
let hex = "";
|
|
19
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
20
|
+
hex += bytes[i].toString(16).padStart(2, "0");
|
|
21
|
+
}
|
|
22
|
+
return hex;
|
|
23
|
+
}
|
|
24
|
+
function timingSafeEqual(a, b) {
|
|
25
|
+
if (a.length !== b.length) return false;
|
|
26
|
+
let mismatch = 0;
|
|
27
|
+
for (let i = 0; i < a.length; i++) {
|
|
28
|
+
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
29
|
+
}
|
|
30
|
+
return mismatch === 0;
|
|
31
|
+
}
|
|
32
|
+
async function signWebhookPayload(params) {
|
|
33
|
+
const timestamp = params.timestamp ?? Math.floor(Date.now() / 1e3);
|
|
34
|
+
const hex = await hmacSha256Hex(params.secret, `${timestamp}.${params.rawBody}`);
|
|
35
|
+
return { signature: `sha256=${hex}`, timestamp };
|
|
36
|
+
}
|
|
37
|
+
async function verifyWebhookSignature(params) {
|
|
38
|
+
const toleranceSeconds = params.toleranceSeconds ?? 300;
|
|
39
|
+
const ts = typeof params.timestamp === "string" ? Number(params.timestamp) : params.timestamp;
|
|
40
|
+
if (!Number.isFinite(ts)) return false;
|
|
41
|
+
const now = params.now ?? Math.floor(Date.now() / 1e3);
|
|
42
|
+
if (Math.abs(now - ts) > toleranceSeconds) return false;
|
|
43
|
+
const provided = parseWebhookSignatureHeader(params.signature);
|
|
44
|
+
const expected = await hmacSha256Hex(params.secret, `${ts}.${params.rawBody}`);
|
|
45
|
+
return timingSafeEqual(expected, provided);
|
|
46
|
+
}
|
|
47
|
+
async function assertWebhookSignature(params) {
|
|
48
|
+
const ok = await verifyWebhookSignature(params);
|
|
49
|
+
if (!ok) throw new ThreeuWebhookSignatureError();
|
|
50
|
+
}
|
|
51
|
+
function parseWebhookSignatureHeader(header) {
|
|
52
|
+
const trimmed = header.trim();
|
|
53
|
+
const eq = trimmed.indexOf("=");
|
|
54
|
+
if (trimmed.startsWith("sha256=") || eq > 0 && /^sha\d+$/i.test(trimmed.slice(0, eq))) {
|
|
55
|
+
return trimmed.slice(eq + 1);
|
|
56
|
+
}
|
|
57
|
+
return trimmed;
|
|
58
|
+
}
|
|
59
|
+
async function signLegacyWebhook(secret, payload) {
|
|
60
|
+
return hmacSha256Hex(secret, JSON.stringify(payload));
|
|
61
|
+
}
|
|
62
|
+
async function verifyLegacyWebhook(secret, payload, signature) {
|
|
63
|
+
const expected = await hmacSha256Hex(secret, JSON.stringify(payload));
|
|
64
|
+
return timingSafeEqual(expected, parseWebhookSignatureHeader(signature));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { assertWebhookSignature, parseWebhookSignatureHeader, signLegacyWebhook, signWebhookPayload, timingSafeEqual, verifyLegacyWebhook, verifyWebhookSignature };
|
|
68
|
+
//# sourceMappingURL=chunk-ZWLFJIAM.js.map
|
|
69
|
+
//# sourceMappingURL=chunk-ZWLFJIAM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/webhooks/index.ts"],"names":[],"mappings":";;;AAkBA,IAAM,GAAA,GAAM,IAAI,WAAA,EAAY;AAE5B,eAAe,aAAA,CAAc,QAAgB,OAAA,EAAkC;AAC7E,EAAA,MAAM,MAAA,GAAS,WAAW,MAAA,EAAQ,MAAA;AAClC,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,MAAM,IAAI,2BAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,MAAM,MAAM,MAAA,CAAO,UAAU,KAAA,EAAO,GAAA,CAAI,OAAO,MAAM,CAAA,EAAG,EAAE,IAAA,EAAM,QAAQ,IAAA,EAAM,SAAA,IAAa,KAAA,EAAO,CAAC,MAAM,CAAC,CAAA;AAChH,EAAA,MAAM,GAAA,GAAM,MAAM,MAAA,CAAO,IAAA,CAAK,QAAQ,GAAA,EAAK,GAAA,CAAI,MAAA,CAAO,OAAO,CAAC,CAAA;AAC9D,EAAA,OAAO,YAAY,GAAG,CAAA;AACxB;AAEA,SAAS,YAAY,MAAA,EAA6B;AAChD,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,MAAM,CAAA;AACnC,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,GAAA,IAAQ,KAAA,CAAM,CAAC,CAAA,CAAa,QAAA,CAAS,EAAE,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAAA,EAC1D;AACA,EAAA,OAAO,GAAA;AACT;AAGO,SAAS,eAAA,CAAgB,GAAW,CAAA,EAAoB;AAC7D,EAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,MAAA,EAAQ,OAAO,KAAA;AAClC,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,QAAQ,CAAA,EAAA,EAAK;AACjC,IAAA,QAAA,IAAY,EAAE,UAAA,CAAW,CAAC,CAAA,GAAI,CAAA,CAAE,WAAW,CAAC,CAAA;AAAA,EAC9C;AACA,EAAA,OAAO,QAAA,KAAa,CAAA;AACtB;AAeA,eAAsB,mBAAmB,MAAA,EAAmD;AAC1F,EAAA,MAAM,SAAA,GAAY,OAAO,SAAA,IAAa,IAAA,CAAK,MAAM,IAAA,CAAK,GAAA,KAAQ,GAAI,CAAA;AAClE,EAAA,MAAM,GAAA,GAAM,MAAM,aAAA,CAAc,MAAA,CAAO,MAAA,EAAQ,GAAG,SAAS,CAAA,CAAA,EAAI,MAAA,CAAO,OAAO,CAAA,CAAE,CAAA;AAC/E,EAAA,OAAO,EAAE,SAAA,EAAW,CAAA,OAAA,EAAU,GAAG,IAAI,SAAA,EAAU;AACjD;AAcA,eAAsB,uBAAuB,MAAA,EAA+C;AAC1F,EAAA,MAAM,gBAAA,GAAmB,OAAO,gBAAA,IAAoB,GAAA;AACpD,EAAA,MAAM,EAAA,GAAK,OAAO,MAAA,CAAO,SAAA,KAAc,WAAW,MAAA,CAAO,MAAA,CAAO,SAAS,CAAA,GAAI,MAAA,CAAO,SAAA;AACpF,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,EAAE,GAAG,OAAO,KAAA;AAEjC,EAAA,MAAM,GAAA,GAAM,OAAO,GAAA,IAAO,IAAA,CAAK,MAAM,IAAA,CAAK,GAAA,KAAQ,GAAI,CAAA;AACtD,EAAA,IAAI,KAAK,GAAA,CAAI,GAAA,GAAM,EAAE,CAAA,GAAI,kBAAkB,OAAO,KAAA;AAElD,EAAA,MAAM,QAAA,GAAW,2BAAA,CAA4B,MAAA,CAAO,SAAS,CAAA;AAC7D,EAAA,MAAM,QAAA,GAAW,MAAM,aAAA,CAAc,MAAA,CAAO,MAAA,EAAQ,GAAG,EAAE,CAAA,CAAA,EAAI,MAAA,CAAO,OAAO,CAAA,CAAE,CAAA;AAC7E,EAAA,OAAO,eAAA,CAAgB,UAAU,QAAQ,CAAA;AAC3C;AAGA,eAAsB,uBAAuB,MAAA,EAA4C;AACvF,EAAA,MAAM,EAAA,GAAK,MAAM,sBAAA,CAAuB,MAAM,CAAA;AAC9C,EAAA,IAAI,CAAC,EAAA,EAAI,MAAM,IAAI,2BAAA,EAA4B;AACjD;AAGO,SAAS,4BAA4B,MAAA,EAAwB;AAClE,EAAA,MAAM,OAAA,GAAU,OAAO,IAAA,EAAK;AAC5B,EAAA,MAAM,EAAA,GAAK,OAAA,CAAQ,OAAA,CAAQ,GAAG,CAAA;AAC9B,EAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,SAAS,CAAA,IAAM,EAAA,GAAK,CAAA,IAAK,WAAA,CAAY,IAAA,CAAK,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,EAAI;AACvF,IAAA,OAAO,OAAA,CAAQ,KAAA,CAAM,EAAA,GAAK,CAAC,CAAA;AAAA,EAC7B;AACA,EAAA,OAAO,OAAA;AACT;AAOA,eAAsB,iBAAA,CAAkB,QAAgB,OAAA,EAAmC;AACzF,EAAA,OAAO,aAAA,CAAc,MAAA,EAAQ,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AACtD;AAGA,eAAsB,mBAAA,CACpB,MAAA,EACA,OAAA,EACA,SAAA,EACkB;AAClB,EAAA,MAAM,WAAW,MAAM,aAAA,CAAc,QAAQ,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AACpE,EAAA,OAAO,eAAA,CAAgB,QAAA,EAAU,2BAAA,CAA4B,SAAS,CAAC,CAAA;AACzE","file":"chunk-ZWLFJIAM.js","sourcesContent":["/**\n * @threeu/threeu/webhooks — inbound webhook signing & verification.\n *\n * Recommended (target) scheme — raw-body signing with replay protection:\n * X-Plugin-Signature: sha256=<hex>\n * X-Plugin-Timestamp: <unix seconds>\n * signed message = `${timestamp}.${rawBody}`\n * algorithm = HMAC-SHA256 with brand_plugins.webhook_secret\n *\n * Legacy scheme — what the backend signs TODAY (docs/ARCHITECTURE_MAP.md §6):\n * HMAC-SHA256 over json_encode(payload), no timestamp. Use `verifyLegacy*`\n * for compatibility until the backend is hardened (M8).\n *\n * Uses Web Crypto (`globalThis.crypto.subtle`), available in Node 18+, modern\n * browsers, and edge runtimes — so a single implementation works everywhere.\n */\nimport { ThreeuWebhookSignatureError } from \"../core/errors\";\n\nconst enc = new TextEncoder();\n\nasync function hmacSha256Hex(secret: string, message: string): Promise<string> {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n throw new ThreeuWebhookSignatureError(\n \"Web Crypto (crypto.subtle) is unavailable in this runtime; cannot compute HMAC.\",\n );\n }\n const key = await subtle.importKey(\"raw\", enc.encode(secret), { name: \"HMAC\", hash: \"SHA-256\" }, false, [\"sign\"]);\n const sig = await subtle.sign(\"HMAC\", key, enc.encode(message));\n return bufferToHex(sig);\n}\n\nfunction bufferToHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let hex = \"\";\n for (let i = 0; i < bytes.length; i++) {\n hex += (bytes[i] as number).toString(16).padStart(2, \"0\");\n }\n return hex;\n}\n\n/** Constant-time string comparison to avoid timing leaks. */\nexport function timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let mismatch = 0;\n for (let i = 0; i < a.length; i++) {\n mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return mismatch === 0;\n}\n\nexport interface SignWebhookParams {\n secret: string;\n rawBody: string;\n /** Unix seconds. Defaults to now. */\n timestamp?: number;\n}\n\nexport interface SignedWebhook {\n signature: string; // \"sha256=<hex>\"\n timestamp: number;\n}\n\n/** Sign a raw body with the target scheme. Returns the `sha256=` header value. */\nexport async function signWebhookPayload(params: SignWebhookParams): Promise<SignedWebhook> {\n const timestamp = params.timestamp ?? Math.floor(Date.now() / 1000);\n const hex = await hmacSha256Hex(params.secret, `${timestamp}.${params.rawBody}`);\n return { signature: `sha256=${hex}`, timestamp };\n}\n\nexport interface VerifyWebhookParams {\n secret: string;\n rawBody: string;\n timestamp: number | string;\n signature: string; // accepts \"sha256=<hex>\" or bare hex\n /** Reject timestamps older/newer than this many seconds. Default 300. */\n toleranceSeconds?: number;\n /** Override \"now\" (unix seconds) — useful in tests. */\n now?: number;\n}\n\n/** Verify a raw-body signature with replay-window enforcement. Boolean result. */\nexport async function verifyWebhookSignature(params: VerifyWebhookParams): Promise<boolean> {\n const toleranceSeconds = params.toleranceSeconds ?? 300;\n const ts = typeof params.timestamp === \"string\" ? Number(params.timestamp) : params.timestamp;\n if (!Number.isFinite(ts)) return false;\n\n const now = params.now ?? Math.floor(Date.now() / 1000);\n if (Math.abs(now - ts) > toleranceSeconds) return false;\n\n const provided = parseWebhookSignatureHeader(params.signature);\n const expected = await hmacSha256Hex(params.secret, `${ts}.${params.rawBody}`);\n return timingSafeEqual(expected, provided);\n}\n\n/** Like `verifyWebhookSignature` but throws `ThreeuWebhookSignatureError` on failure. */\nexport async function assertWebhookSignature(params: VerifyWebhookParams): Promise<void> {\n const ok = await verifyWebhookSignature(params);\n if (!ok) throw new ThreeuWebhookSignatureError();\n}\n\n/** Extract the hex digest from a `sha256=<hex>` header (or pass-through bare hex). */\nexport function parseWebhookSignatureHeader(header: string): string {\n const trimmed = header.trim();\n const eq = trimmed.indexOf(\"=\");\n if (trimmed.startsWith(\"sha256=\") || (eq > 0 && /^sha\\d+$/i.test(trimmed.slice(0, eq)))) {\n return trimmed.slice(eq + 1);\n }\n return trimmed;\n}\n\n// ---------------------------------------------------------------------------\n// Legacy compatibility (matches the backend's current json_encode signing)\n// ---------------------------------------------------------------------------\n\n/** Sign the legacy way: HMAC over the JSON-encoded payload, no timestamp. */\nexport async function signLegacyWebhook(secret: string, payload: unknown): Promise<string> {\n return hmacSha256Hex(secret, JSON.stringify(payload));\n}\n\n/** Verify a legacy signature (no timestamp / replay window). */\nexport async function verifyLegacyWebhook(\n secret: string,\n payload: unknown,\n signature: string,\n): Promise<boolean> {\n const expected = await hmacSha256Hex(secret, JSON.stringify(payload));\n return timingSafeEqual(expected, parseWebhookSignatureHeader(signature));\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { A as AppDefinition, a as AdminAppManifest, b as POSAppManifest, c as PluginDefinition, P as PluginManifest } from './types-BoGD3IXz.js';
|
|
2
|
+
|
|
3
|
+
interface DefinedPlugin {
|
|
4
|
+
readonly definition: PluginDefinition;
|
|
5
|
+
readonly manifest: PluginManifest;
|
|
6
|
+
/** The manifest JSON to PUT to the backend / publish. */
|
|
7
|
+
toJSON(): PluginManifest;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Validate a plugin definition and return a handle exposing its manifest.
|
|
11
|
+
* Throws `ThreeuManifestError` if the definition is invalid.
|
|
12
|
+
*/
|
|
13
|
+
declare function definePlugin(definition: PluginDefinition): DefinedPlugin;
|
|
14
|
+
/** Alias — some developers prefer `createPlugin`. */
|
|
15
|
+
declare const createPlugin: typeof definePlugin;
|
|
16
|
+
interface DefinedApp {
|
|
17
|
+
readonly definition: AppDefinition;
|
|
18
|
+
readonly manifest: AdminAppManifest | POSAppManifest;
|
|
19
|
+
toJSON(): AdminAppManifest | POSAppManifest;
|
|
20
|
+
}
|
|
21
|
+
/** Validate an admin/POS embedded-app definition and expose its manifest. */
|
|
22
|
+
declare function defineApp(definition: AppDefinition): DefinedApp;
|
|
23
|
+
|
|
24
|
+
export { type DefinedApp as D, type DefinedPlugin as a, definePlugin as b, createPlugin as c, defineApp as d };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { I as Id, T as ThreeuHttpClient, f as Page, g as ThreeuLogger, h as ThreeuTelemetryOptions } from '../types-DfyYnoLn.js';
|
|
2
|
+
import { P as PluginManifest } from '../types-BoGD3IXz.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @threeu/threeu/developer — developer.threeu.app API client.
|
|
6
|
+
*
|
|
7
|
+
* const dev = new ThreeuDeveloper({ token: process.env.THREEU_DEVELOPER_TOKEN! });
|
|
8
|
+
* const me = await dev.me();
|
|
9
|
+
* const plugins = await dev.plugins.list();
|
|
10
|
+
*
|
|
11
|
+
* LIVE endpoints (docs/ARCHITECTURE_MAP.md §8 + M8 backend): auth, me,
|
|
12
|
+
* plugins(list), webhooks, contracts CRUD, plus the M8 manifest-write +
|
|
13
|
+
* health-check + observability (usage/health/deliveries/errors) endpoints.
|
|
14
|
+
* Still PLANNED (throw `ThreeuNotImplementedError`): API tokens, profile/password,
|
|
15
|
+
* payouts/finance. We do not pretend planned endpoints work.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
interface ThreeuDeveloperOptions {
|
|
19
|
+
token?: string;
|
|
20
|
+
/** API origin; `/api/developer` is appended. */
|
|
21
|
+
baseUrl?: string;
|
|
22
|
+
fetch?: typeof fetch;
|
|
23
|
+
logger?: ThreeuLogger;
|
|
24
|
+
telemetry?: ThreeuTelemetryOptions;
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
}
|
|
27
|
+
interface DeveloperPlugin {
|
|
28
|
+
id: Id;
|
|
29
|
+
name: string;
|
|
30
|
+
slug: string;
|
|
31
|
+
manifest?: PluginManifest;
|
|
32
|
+
status?: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
interface Contract {
|
|
36
|
+
id: Id;
|
|
37
|
+
status?: string;
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
}
|
|
40
|
+
interface ManifestUpdateResult {
|
|
41
|
+
plugin: Partial<DeveloperPlugin>;
|
|
42
|
+
health: HealthResult | null;
|
|
43
|
+
}
|
|
44
|
+
interface HealthResult {
|
|
45
|
+
status: "healthy" | "degraded" | "unhealthy" | "unreachable" | "unknown";
|
|
46
|
+
healthy: boolean;
|
|
47
|
+
status_code: number | null;
|
|
48
|
+
duration_ms: number | null;
|
|
49
|
+
error: string | null;
|
|
50
|
+
url: string | null;
|
|
51
|
+
}
|
|
52
|
+
interface DeliveryLog {
|
|
53
|
+
id: Id;
|
|
54
|
+
direction: "inbound" | "outbound";
|
|
55
|
+
kind: "webhook" | "action" | "health_check";
|
|
56
|
+
event_or_action?: string;
|
|
57
|
+
status_code?: number;
|
|
58
|
+
success?: boolean;
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
}
|
|
61
|
+
/** Plugins owned by the authenticated developer. */
|
|
62
|
+
declare class DeveloperPlugins {
|
|
63
|
+
private readonly http;
|
|
64
|
+
constructor(http: ThreeuHttpClient);
|
|
65
|
+
/** LIVE — GET /developer/plugins */
|
|
66
|
+
list(): Promise<Page<DeveloperPlugin>>;
|
|
67
|
+
/** LIVE (M8) — PUT /developer/plugins/{id}/manifest */
|
|
68
|
+
updateManifest(pluginId: Id, manifest: PluginManifest, options?: {
|
|
69
|
+
runHealthCheck?: boolean;
|
|
70
|
+
}): Promise<ManifestUpdateResult>;
|
|
71
|
+
/** LIVE (M8) — POST /developer/plugins/{id}/health-check */
|
|
72
|
+
healthCheck(pluginId: Id): Promise<HealthResult>;
|
|
73
|
+
/** LIVE (M8) — GET /developer/plugins/{id}/health (latest persisted) */
|
|
74
|
+
health(pluginId: Id): Promise<Record<string, unknown>>;
|
|
75
|
+
/** LIVE (M8) — GET /developer/plugins/{id}/usage */
|
|
76
|
+
usage(pluginId: Id, params?: {
|
|
77
|
+
days?: number;
|
|
78
|
+
}): Promise<Record<string, unknown>>;
|
|
79
|
+
/** LIVE (M8) — GET /developer/plugins/{id}/deliveries */
|
|
80
|
+
deliveries(pluginId: Id, params?: {
|
|
81
|
+
kind?: string;
|
|
82
|
+
direction?: string;
|
|
83
|
+
per_page?: number;
|
|
84
|
+
page?: number;
|
|
85
|
+
}): Promise<Page<DeliveryLog>>;
|
|
86
|
+
/** LIVE (M8) — GET /developer/plugins/{id}/errors */
|
|
87
|
+
errors(pluginId: Id, params?: {
|
|
88
|
+
per_page?: number;
|
|
89
|
+
page?: number;
|
|
90
|
+
}): Promise<Page<Record<string, unknown>>>;
|
|
91
|
+
}
|
|
92
|
+
/** Bilateral developer↔partner contracts. */
|
|
93
|
+
declare class DeveloperContracts {
|
|
94
|
+
private readonly http;
|
|
95
|
+
constructor(http: ThreeuHttpClient);
|
|
96
|
+
list(): Promise<Page<Contract>>;
|
|
97
|
+
get(id: Id): Promise<Contract>;
|
|
98
|
+
create(body: Record<string, unknown>): Promise<Contract>;
|
|
99
|
+
update(id: Id, body: Record<string, unknown>): Promise<Contract>;
|
|
100
|
+
accept(id: Id): Promise<Contract>;
|
|
101
|
+
decline(id: Id): Promise<Contract>;
|
|
102
|
+
terminate(id: Id): Promise<Contract>;
|
|
103
|
+
}
|
|
104
|
+
declare class ThreeuDeveloper {
|
|
105
|
+
readonly http: ThreeuHttpClient;
|
|
106
|
+
readonly plugins: DeveloperPlugins;
|
|
107
|
+
readonly contracts: DeveloperContracts;
|
|
108
|
+
constructor(options?: ThreeuDeveloperOptions);
|
|
109
|
+
/** LIVE — POST /developer/login. Returns the token payload. */
|
|
110
|
+
login(email: string, password: string): Promise<Record<string, unknown>>;
|
|
111
|
+
/** LIVE — GET /developer/me */
|
|
112
|
+
me(): Promise<Record<string, unknown>>;
|
|
113
|
+
/** LIVE — GET /developer/webhooks (placeholder data until delivery logs land). */
|
|
114
|
+
webhooks(): Promise<unknown>;
|
|
115
|
+
/** LIVE — POST /developer/logout */
|
|
116
|
+
logout(): Promise<unknown>;
|
|
117
|
+
close(): Promise<void>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export { type Contract, type DeliveryLog, type DeveloperPlugin, type HealthResult, type ManifestUpdateResult, ThreeuDeveloper, type ThreeuDeveloperOptions };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ThreeuHttpClient } from '../chunk-HYSJ6YPN.js';
|
|
2
|
+
import '../chunk-H3XILKGI.js';
|
|
3
|
+
import '../chunk-LFF5LPWT.js';
|
|
4
|
+
|
|
5
|
+
// src/developer/index.ts
|
|
6
|
+
var DEFAULT_ORIGIN = "https://api.threeu.app";
|
|
7
|
+
var DeveloperPlugins = class {
|
|
8
|
+
constructor(http) {
|
|
9
|
+
this.http = http;
|
|
10
|
+
}
|
|
11
|
+
/** LIVE — GET /developer/plugins */
|
|
12
|
+
list() {
|
|
13
|
+
return this.http.list("plugins");
|
|
14
|
+
}
|
|
15
|
+
/** LIVE (M8) — PUT /developer/plugins/{id}/manifest */
|
|
16
|
+
updateManifest(pluginId, manifest, options) {
|
|
17
|
+
return this.http.put(`plugins/${pluginId}/manifest`, {
|
|
18
|
+
body: { manifest, run_health_check: options?.runHealthCheck ?? false }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/** LIVE (M8) — POST /developer/plugins/{id}/health-check */
|
|
22
|
+
healthCheck(pluginId) {
|
|
23
|
+
return this.http.post(`plugins/${pluginId}/health-check`, { idempotent: true });
|
|
24
|
+
}
|
|
25
|
+
/** LIVE (M8) — GET /developer/plugins/{id}/health (latest persisted) */
|
|
26
|
+
health(pluginId) {
|
|
27
|
+
return this.http.get(`plugins/${pluginId}/health`);
|
|
28
|
+
}
|
|
29
|
+
/** LIVE (M8) — GET /developer/plugins/{id}/usage */
|
|
30
|
+
usage(pluginId, params) {
|
|
31
|
+
return this.http.get(`plugins/${pluginId}/usage`, { query: params });
|
|
32
|
+
}
|
|
33
|
+
/** LIVE (M8) — GET /developer/plugins/{id}/deliveries */
|
|
34
|
+
deliveries(pluginId, params) {
|
|
35
|
+
return this.http.list(`plugins/${pluginId}/deliveries`, { query: params });
|
|
36
|
+
}
|
|
37
|
+
/** LIVE (M8) — GET /developer/plugins/{id}/errors */
|
|
38
|
+
errors(pluginId, params) {
|
|
39
|
+
return this.http.list(`plugins/${pluginId}/errors`, { query: params });
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var DeveloperContracts = class {
|
|
43
|
+
constructor(http) {
|
|
44
|
+
this.http = http;
|
|
45
|
+
}
|
|
46
|
+
list() {
|
|
47
|
+
return this.http.list("contracts");
|
|
48
|
+
}
|
|
49
|
+
get(id) {
|
|
50
|
+
return this.http.get(`contracts/${id}`);
|
|
51
|
+
}
|
|
52
|
+
create(body) {
|
|
53
|
+
return this.http.post("contracts", { body, idempotent: true });
|
|
54
|
+
}
|
|
55
|
+
update(id, body) {
|
|
56
|
+
return this.http.put(`contracts/${id}`, { body });
|
|
57
|
+
}
|
|
58
|
+
accept(id) {
|
|
59
|
+
return this.http.post(`contracts/${id}/accept`, { idempotent: true });
|
|
60
|
+
}
|
|
61
|
+
decline(id) {
|
|
62
|
+
return this.http.post(`contracts/${id}/decline`, { idempotent: true });
|
|
63
|
+
}
|
|
64
|
+
terminate(id) {
|
|
65
|
+
return this.http.post(`contracts/${id}/terminate`, { idempotent: true });
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var ThreeuDeveloper = class {
|
|
69
|
+
constructor(options = {}) {
|
|
70
|
+
const origin = (options.baseUrl ?? DEFAULT_ORIGIN).replace(/\/+$/, "").replace(/\/api\/developer$/, "");
|
|
71
|
+
this.http = new ThreeuHttpClient({
|
|
72
|
+
baseUrl: `${origin}/api/developer`,
|
|
73
|
+
token: options.token,
|
|
74
|
+
tokenType: "developer",
|
|
75
|
+
fetch: options.fetch,
|
|
76
|
+
logger: options.logger,
|
|
77
|
+
telemetry: options.telemetry,
|
|
78
|
+
timeoutMs: options.timeoutMs
|
|
79
|
+
});
|
|
80
|
+
this.plugins = new DeveloperPlugins(this.http);
|
|
81
|
+
this.contracts = new DeveloperContracts(this.http);
|
|
82
|
+
}
|
|
83
|
+
/** LIVE — POST /developer/login. Returns the token payload. */
|
|
84
|
+
login(email, password) {
|
|
85
|
+
return this.http.post("login", { body: { email, password } });
|
|
86
|
+
}
|
|
87
|
+
/** LIVE — GET /developer/me */
|
|
88
|
+
me() {
|
|
89
|
+
return this.http.get("me");
|
|
90
|
+
}
|
|
91
|
+
/** LIVE — GET /developer/webhooks (placeholder data until delivery logs land). */
|
|
92
|
+
webhooks() {
|
|
93
|
+
return this.http.get("webhooks");
|
|
94
|
+
}
|
|
95
|
+
/** LIVE — POST /developer/logout */
|
|
96
|
+
logout() {
|
|
97
|
+
return this.http.post("logout", { idempotent: true });
|
|
98
|
+
}
|
|
99
|
+
close() {
|
|
100
|
+
return this.http.close();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export { ThreeuDeveloper };
|
|
105
|
+
//# sourceMappingURL=index.js.map
|
|
106
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/developer/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @threeu/threeu/developer — developer.threeu.app API client.\n *\n * const dev = new ThreeuDeveloper({ token: process.env.THREEU_DEVELOPER_TOKEN! });\n * const me = await dev.me();\n * const plugins = await dev.plugins.list();\n *\n * LIVE endpoints (docs/ARCHITECTURE_MAP.md §8 + M8 backend): auth, me,\n * plugins(list), webhooks, contracts CRUD, plus the M8 manifest-write +\n * health-check + observability (usage/health/deliveries/errors) endpoints.\n * Still PLANNED (throw `ThreeuNotImplementedError`): API tokens, profile/password,\n * payouts/finance. We do not pretend planned endpoints work.\n */\nimport { ThreeuHttpClient } from \"../core/http-client\";\nimport type { Page } from \"../core/pagination\";\nimport type { ThreeuLogger, ThreeuTelemetryOptions } from \"../core/types\";\nimport type { Id } from \"../resources/types\";\nimport type { PluginManifest } from \"../manifests/types\";\n\nconst DEFAULT_ORIGIN = \"https://api.threeu.app\";\n\nexport interface ThreeuDeveloperOptions {\n token?: string;\n /** API origin; `/api/developer` is appended. */\n baseUrl?: string;\n fetch?: typeof fetch;\n logger?: ThreeuLogger;\n telemetry?: ThreeuTelemetryOptions;\n timeoutMs?: number;\n}\n\nexport interface DeveloperPlugin {\n id: Id;\n name: string;\n slug: string;\n manifest?: PluginManifest;\n status?: string;\n [key: string]: unknown;\n}\n\nexport interface Contract {\n id: Id;\n status?: string;\n [key: string]: unknown;\n}\n\nexport interface ManifestUpdateResult {\n plugin: Partial<DeveloperPlugin>;\n health: HealthResult | null;\n}\n\nexport interface HealthResult {\n status: \"healthy\" | \"degraded\" | \"unhealthy\" | \"unreachable\" | \"unknown\";\n healthy: boolean;\n status_code: number | null;\n duration_ms: number | null;\n error: string | null;\n url: string | null;\n}\n\nexport interface DeliveryLog {\n id: Id;\n direction: \"inbound\" | \"outbound\";\n kind: \"webhook\" | \"action\" | \"health_check\";\n event_or_action?: string;\n status_code?: number;\n success?: boolean;\n [key: string]: unknown;\n}\n\n/** Plugins owned by the authenticated developer. */\nclass DeveloperPlugins {\n constructor(private readonly http: ThreeuHttpClient) {}\n\n /** LIVE — GET /developer/plugins */\n list(): Promise<Page<DeveloperPlugin>> {\n return this.http.list<DeveloperPlugin>(\"plugins\");\n }\n\n /** LIVE (M8) — PUT /developer/plugins/{id}/manifest */\n updateManifest(\n pluginId: Id,\n manifest: PluginManifest,\n options?: { runHealthCheck?: boolean },\n ): Promise<ManifestUpdateResult> {\n return this.http.put(`plugins/${pluginId}/manifest`, {\n body: { manifest, run_health_check: options?.runHealthCheck ?? false },\n }) as Promise<ManifestUpdateResult>;\n }\n\n /** LIVE (M8) — POST /developer/plugins/{id}/health-check */\n healthCheck(pluginId: Id): Promise<HealthResult> {\n return this.http.post(`plugins/${pluginId}/health-check`, { idempotent: true }) as Promise<HealthResult>;\n }\n\n /** LIVE (M8) — GET /developer/plugins/{id}/health (latest persisted) */\n health(pluginId: Id): Promise<Record<string, unknown>> {\n return this.http.get(`plugins/${pluginId}/health`) as Promise<Record<string, unknown>>;\n }\n\n /** LIVE (M8) — GET /developer/plugins/{id}/usage */\n usage(pluginId: Id, params?: { days?: number }): Promise<Record<string, unknown>> {\n return this.http.get(`plugins/${pluginId}/usage`, { query: params }) as Promise<Record<string, unknown>>;\n }\n\n /** LIVE (M8) — GET /developer/plugins/{id}/deliveries */\n deliveries(\n pluginId: Id,\n params?: { kind?: string; direction?: string; per_page?: number; page?: number },\n ): Promise<Page<DeliveryLog>> {\n return this.http.list<DeliveryLog>(`plugins/${pluginId}/deliveries`, { query: params });\n }\n\n /** LIVE (M8) — GET /developer/plugins/{id}/errors */\n errors(pluginId: Id, params?: { per_page?: number; page?: number }): Promise<Page<Record<string, unknown>>> {\n return this.http.list<Record<string, unknown>>(`plugins/${pluginId}/errors`, { query: params });\n }\n}\n\n/** Bilateral developer↔partner contracts. */\nclass DeveloperContracts {\n constructor(private readonly http: ThreeuHttpClient) {}\n list(): Promise<Page<Contract>> {\n return this.http.list<Contract>(\"contracts\");\n }\n get(id: Id): Promise<Contract> {\n return this.http.get(`contracts/${id}`) as Promise<Contract>;\n }\n create(body: Record<string, unknown>): Promise<Contract> {\n return this.http.post(\"contracts\", { body, idempotent: true }) as Promise<Contract>;\n }\n update(id: Id, body: Record<string, unknown>): Promise<Contract> {\n return this.http.put(`contracts/${id}`, { body }) as Promise<Contract>;\n }\n accept(id: Id): Promise<Contract> {\n return this.http.post(`contracts/${id}/accept`, { idempotent: true }) as Promise<Contract>;\n }\n decline(id: Id): Promise<Contract> {\n return this.http.post(`contracts/${id}/decline`, { idempotent: true }) as Promise<Contract>;\n }\n terminate(id: Id): Promise<Contract> {\n return this.http.post(`contracts/${id}/terminate`, { idempotent: true }) as Promise<Contract>;\n }\n}\n\nexport class ThreeuDeveloper {\n readonly http: ThreeuHttpClient;\n readonly plugins: DeveloperPlugins;\n readonly contracts: DeveloperContracts;\n\n constructor(options: ThreeuDeveloperOptions = {}) {\n const origin = (options.baseUrl ?? DEFAULT_ORIGIN).replace(/\\/+$/, \"\").replace(/\\/api\\/developer$/, \"\");\n this.http = new ThreeuHttpClient({\n baseUrl: `${origin}/api/developer`,\n token: options.token,\n tokenType: \"developer\",\n fetch: options.fetch,\n logger: options.logger,\n telemetry: options.telemetry,\n timeoutMs: options.timeoutMs,\n });\n this.plugins = new DeveloperPlugins(this.http);\n this.contracts = new DeveloperContracts(this.http);\n }\n\n /** LIVE — POST /developer/login. Returns the token payload. */\n login(email: string, password: string): Promise<Record<string, unknown>> {\n return this.http.post(\"login\", { body: { email, password } }) as Promise<Record<string, unknown>>;\n }\n\n /** LIVE — GET /developer/me */\n me(): Promise<Record<string, unknown>> {\n return this.http.get(\"me\") as Promise<Record<string, unknown>>;\n }\n\n /** LIVE — GET /developer/webhooks (placeholder data until delivery logs land). */\n webhooks(): Promise<unknown> {\n return this.http.get(\"webhooks\");\n }\n\n /** LIVE — POST /developer/logout */\n logout(): Promise<unknown> {\n return this.http.post(\"logout\", { idempotent: true });\n }\n\n close(): Promise<void> {\n return this.http.close();\n }\n}\n"]}
|