@witnium-tech/witniumchain 0.2.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 +160 -0
- package/dist/index.d.mts +5696 -0
- package/dist/index.d.ts +5696 -0
- package/dist/index.js +740 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +729 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +63 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var WitniumAccountsApiError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
errorLabel;
|
|
5
|
+
body;
|
|
6
|
+
constructor(args) {
|
|
7
|
+
super(args.message);
|
|
8
|
+
this.name = "WitniumAccountsApiError";
|
|
9
|
+
this.status = args.status;
|
|
10
|
+
this.errorLabel = args.errorLabel;
|
|
11
|
+
this.body = args.body ?? null;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/client.ts
|
|
16
|
+
var WitniumAccountsClient = class {
|
|
17
|
+
baseUrl;
|
|
18
|
+
cfg;
|
|
19
|
+
timeout;
|
|
20
|
+
fetchImpl;
|
|
21
|
+
/** Subscriptions / billing helpers. See {@link Subscriptions}. */
|
|
22
|
+
subscriptions;
|
|
23
|
+
/** Delegated-key namespace including the one-call {@link DelegatedKeys.provision} flow. */
|
|
24
|
+
delegatedKeys;
|
|
25
|
+
/** Owner signing-key management (list / add / revoke). */
|
|
26
|
+
keys;
|
|
27
|
+
/** OAuth session management. Accessed as `client.oauth.sessions.*`. */
|
|
28
|
+
oauth;
|
|
29
|
+
constructor(config) {
|
|
30
|
+
if (!config.baseUrl) {
|
|
31
|
+
throw new Error("WitniumAccountsClient: baseUrl is required");
|
|
32
|
+
}
|
|
33
|
+
this.cfg = config;
|
|
34
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
35
|
+
this.timeout = config.timeout ?? 3e4;
|
|
36
|
+
this.fetchImpl = config.fetch ?? globalThis.fetch;
|
|
37
|
+
if (!this.fetchImpl) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
"WitniumAccountsClient: no fetch implementation available. Pass `config.fetch`."
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
this.subscriptions = new Subscriptions(this);
|
|
43
|
+
this.delegatedKeys = new DelegatedKeys(this);
|
|
44
|
+
this.keys = new SigningKeys(this);
|
|
45
|
+
this.oauth = new OauthNamespace(this);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Convenience alias for {@link getAccount} — returns the authenticated
|
|
49
|
+
* user's profile, the org they belong to, and their signing keys.
|
|
50
|
+
*/
|
|
51
|
+
me() {
|
|
52
|
+
return this.getAccount();
|
|
53
|
+
}
|
|
54
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
55
|
+
// Auth (/v1/auth/*)
|
|
56
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
57
|
+
signup(body) {
|
|
58
|
+
return this.req("POST", "/v1/auth/signup", { auth: "Public", body });
|
|
59
|
+
}
|
|
60
|
+
verifyEmail(token) {
|
|
61
|
+
return this.req("GET", "/v1/auth/verify", {
|
|
62
|
+
auth: "Public",
|
|
63
|
+
query: { token }
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
login(body) {
|
|
67
|
+
return this.req("POST", "/v1/auth/login", { auth: "Public", body });
|
|
68
|
+
}
|
|
69
|
+
logout() {
|
|
70
|
+
return this.req("POST", "/v1/auth/logout", { auth: "Public" });
|
|
71
|
+
}
|
|
72
|
+
forgotPassword(body) {
|
|
73
|
+
return this.req("POST", "/v1/auth/forgot-password", { auth: "Public", body });
|
|
74
|
+
}
|
|
75
|
+
resetPassword(body) {
|
|
76
|
+
return this.req("POST", "/v1/auth/reset-password", { auth: "Public", body });
|
|
77
|
+
}
|
|
78
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
79
|
+
// Billing (/v1/billing/*)
|
|
80
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
81
|
+
createCheckoutSession(body) {
|
|
82
|
+
return this.req("POST", "/v1/billing/checkout", {
|
|
83
|
+
auth: "SessionCookie",
|
|
84
|
+
body
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
createPortalSession() {
|
|
88
|
+
return this.req("GET", "/v1/billing/portal", { auth: "SessionCookie" });
|
|
89
|
+
}
|
|
90
|
+
// Webhook endpoint is intentionally NOT exposed: only Stripe should call it.
|
|
91
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
92
|
+
// Orgs (/v1/orgs/me/*)
|
|
93
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
94
|
+
getMyOrg() {
|
|
95
|
+
return this.req("GET", "/v1/orgs/me", { auth: "OrgApiKey" });
|
|
96
|
+
}
|
|
97
|
+
createOrgUser(body) {
|
|
98
|
+
return this.req("POST", "/v1/orgs/me/users", { auth: "OrgApiKey", body });
|
|
99
|
+
}
|
|
100
|
+
listOrgUsers() {
|
|
101
|
+
return this.req("GET", "/v1/orgs/me/users", { auth: "OrgApiKey" });
|
|
102
|
+
}
|
|
103
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
104
|
+
// Admin (/v1/admin/organizations/*)
|
|
105
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
106
|
+
createOrganization(body) {
|
|
107
|
+
return this.req("POST", "/v1/admin/organizations", {
|
|
108
|
+
auth: "AdminToken",
|
|
109
|
+
body
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
setOrgAccountType(id, body) {
|
|
113
|
+
return this.req(
|
|
114
|
+
"PATCH",
|
|
115
|
+
`/v1/admin/organizations/${encodeURIComponent(id)}/account-type`,
|
|
116
|
+
{ auth: "AdminToken", body }
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
verifyOrganization(id) {
|
|
120
|
+
return this.req(
|
|
121
|
+
"PATCH",
|
|
122
|
+
`/v1/admin/organizations/${encodeURIComponent(id)}/verify`,
|
|
123
|
+
{ auth: "AdminToken" }
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
rotateOrgApiKey(id) {
|
|
127
|
+
return this.req(
|
|
128
|
+
"POST",
|
|
129
|
+
`/v1/admin/organizations/${encodeURIComponent(id)}/rotate-key`,
|
|
130
|
+
{ auth: "AdminToken" }
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
adjustOrgCredits(id, body) {
|
|
134
|
+
return this.req(
|
|
135
|
+
"POST",
|
|
136
|
+
`/v1/admin/organizations/${encodeURIComponent(id)}/adjust-credits`,
|
|
137
|
+
{ auth: "AdminToken", body }
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
141
|
+
// Delegated keys (/v1/users/me/delegated-keys/*)
|
|
142
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
143
|
+
listDelegatedKeys(query) {
|
|
144
|
+
return this.req("GET", "/v1/users/me/delegated-keys", {
|
|
145
|
+
auth: "BearerJWT",
|
|
146
|
+
query: query ?? {}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
prepareDelegatedKey(body) {
|
|
150
|
+
return this.req("POST", "/v1/users/me/delegated-keys", {
|
|
151
|
+
auth: "BearerJWT",
|
|
152
|
+
body
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
submitDelegatedKey(id, body) {
|
|
156
|
+
return this.req(
|
|
157
|
+
"POST",
|
|
158
|
+
`/v1/users/me/delegated-keys/${encodeURIComponent(id)}/submit`,
|
|
159
|
+
{ auth: "BearerJWT", body }
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
revokeDelegatedKey(id) {
|
|
163
|
+
return this.req(
|
|
164
|
+
"DELETE",
|
|
165
|
+
`/v1/users/me/delegated-keys/${encodeURIComponent(id)}`,
|
|
166
|
+
{ auth: "BearerJWT" }
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
170
|
+
// Sign (/v1/sign)
|
|
171
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
172
|
+
sign(body, requestId) {
|
|
173
|
+
const headers = requestId ? { "x-request-id": requestId } : void 0;
|
|
174
|
+
return this.req("POST", "/v1/sign", {
|
|
175
|
+
auth: "BearerJWT",
|
|
176
|
+
body,
|
|
177
|
+
...headers ? { headers } : {}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
181
|
+
// Contracts (/v1/contracts/* + /v1/keys/*)
|
|
182
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
183
|
+
provisionContract(body) {
|
|
184
|
+
return this.req("POST", "/v1/contracts/provision", {
|
|
185
|
+
auth: "Public",
|
|
186
|
+
body
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
addSigningKey(body) {
|
|
190
|
+
return this.req("POST", "/v1/keys", { auth: "SessionCookie", body });
|
|
191
|
+
}
|
|
192
|
+
revokeSigningKey(body) {
|
|
193
|
+
return this.req("POST", "/v1/keys/revoke", {
|
|
194
|
+
auth: "SessionCookie",
|
|
195
|
+
body
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
pauseContract(body) {
|
|
199
|
+
return this.req("POST", "/v1/contracts/pause", {
|
|
200
|
+
auth: "SessionCookie",
|
|
201
|
+
body
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
unpauseContract(body) {
|
|
205
|
+
return this.req("POST", "/v1/contracts/unpause", {
|
|
206
|
+
auth: "SessionCookie",
|
|
207
|
+
body
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
211
|
+
// Witnesses (/v1/contracts/{addr}/witnesses/*)
|
|
212
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
213
|
+
proposeWitness(contractAddress, body, idempotencyKey) {
|
|
214
|
+
const key = idempotencyKey ?? randomUUID();
|
|
215
|
+
return this.req(
|
|
216
|
+
"POST",
|
|
217
|
+
`/v1/contracts/${encodeURIComponent(contractAddress)}/witnesses/propose`,
|
|
218
|
+
{ auth: "SignedRequest", body, headers: { "idempotency-key": key } }
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
signWitness(contractAddress, witnessId, body) {
|
|
222
|
+
return this.req(
|
|
223
|
+
"POST",
|
|
224
|
+
`/v1/contracts/${encodeURIComponent(contractAddress)}/witnesses/${encodeURIComponent(witnessId)}/sign`,
|
|
225
|
+
{ auth: "SignedRequest", body }
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
finalizeWitness(contractAddress, witnessId) {
|
|
229
|
+
return this.req(
|
|
230
|
+
"POST",
|
|
231
|
+
`/v1/contracts/${encodeURIComponent(contractAddress)}/witnesses/${encodeURIComponent(witnessId)}/finalize`,
|
|
232
|
+
{ auth: "SignedRequest" }
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
revokeWitness(contractAddress, witnessId, body, idempotencyKey) {
|
|
236
|
+
const key = idempotencyKey ?? randomUUID();
|
|
237
|
+
return this.req(
|
|
238
|
+
"POST",
|
|
239
|
+
`/v1/contracts/${encodeURIComponent(contractAddress)}/witnesses/${encodeURIComponent(witnessId)}/revoke`,
|
|
240
|
+
{ auth: "SignedRequest", body, headers: { "idempotency-key": key } }
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
getWitness(contractAddress, witnessId) {
|
|
244
|
+
return this.req(
|
|
245
|
+
"GET",
|
|
246
|
+
`/v1/contracts/${encodeURIComponent(contractAddress)}/witnesses/${encodeURIComponent(witnessId)}`,
|
|
247
|
+
{ auth: "Public" }
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
251
|
+
// v5 Witnesses (/v5/contracts/{addr}/witnesses/*)
|
|
252
|
+
//
|
|
253
|
+
// The v5 write surface is a metered proxy in accounts: every billable
|
|
254
|
+
// call (propose, revoke) reserves a credit; sign/finalize forward to
|
|
255
|
+
// chain-api with the admin token. Auth is OAuth Bearer; the URL
|
|
256
|
+
// contract must match the user's bound contract.
|
|
257
|
+
//
|
|
258
|
+
// The propose/revoke methods auto-generate an Idempotency-Key when the
|
|
259
|
+
// caller doesn't supply one — server side that header is part of the
|
|
260
|
+
// billing identity and is required.
|
|
261
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
262
|
+
proposeWitnessV5(contractAddress, body, idempotencyKey) {
|
|
263
|
+
const key = idempotencyKey ?? randomUUID();
|
|
264
|
+
return this.req(
|
|
265
|
+
"POST",
|
|
266
|
+
`/v5/contracts/${encodeURIComponent(contractAddress)}/witnesses/propose`,
|
|
267
|
+
{ auth: "BearerJWT", body, headers: { "idempotency-key": key } }
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
signWitnessV5(contractAddress, intentId, body) {
|
|
271
|
+
return this.req(
|
|
272
|
+
"POST",
|
|
273
|
+
`/v5/contracts/${encodeURIComponent(contractAddress)}/witnesses/${encodeURIComponent(intentId)}/sign`,
|
|
274
|
+
{ auth: "BearerJWT", body }
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
finalizeWitnessV5(contractAddress, intentId) {
|
|
278
|
+
return this.req(
|
|
279
|
+
"POST",
|
|
280
|
+
`/v5/contracts/${encodeURIComponent(contractAddress)}/witnesses/${encodeURIComponent(intentId)}/finalize`,
|
|
281
|
+
{ auth: "BearerJWT" }
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
revokeWitnessV5(contractAddress, witnessId, body, idempotencyKey) {
|
|
285
|
+
const key = idempotencyKey ?? randomUUID();
|
|
286
|
+
return this.req(
|
|
287
|
+
"POST",
|
|
288
|
+
`/v5/contracts/${encodeURIComponent(contractAddress)}/witnesses/${encodeURIComponent(witnessId)}/revoke`,
|
|
289
|
+
{ auth: "BearerJWT", body, headers: { "idempotency-key": key } }
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
293
|
+
// Users / account (/v1/account/*)
|
|
294
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
295
|
+
getAccount() {
|
|
296
|
+
return this.req("GET", "/v1/account", { auth: "SessionCookie" });
|
|
297
|
+
}
|
|
298
|
+
getLedger() {
|
|
299
|
+
return this.req("GET", "/v1/account/ledger", { auth: "SessionCookie" });
|
|
300
|
+
}
|
|
301
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
302
|
+
// OAuth sessions (/v1/oauth/sessions*)
|
|
303
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
304
|
+
listOauthSessions() {
|
|
305
|
+
return this.req("GET", "/v1/oauth/sessions", { auth: "SessionCookie" });
|
|
306
|
+
}
|
|
307
|
+
revokeOauthSession(jti) {
|
|
308
|
+
return this.req("DELETE", `/v1/oauth/sessions/${encodeURIComponent(jti)}`, {
|
|
309
|
+
auth: "SessionCookie",
|
|
310
|
+
expectNoContent: true
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
revokeAllOauthSessions() {
|
|
314
|
+
return this.req("DELETE", "/v1/oauth/sessions", {
|
|
315
|
+
auth: "SessionCookie",
|
|
316
|
+
expectNoContent: true
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
320
|
+
// Health (/health/*)
|
|
321
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
322
|
+
healthLive() {
|
|
323
|
+
return this.req("GET", "/health/live", { auth: "Public" });
|
|
324
|
+
}
|
|
325
|
+
healthReady() {
|
|
326
|
+
return this.req("GET", "/health/ready", { auth: "Public" });
|
|
327
|
+
}
|
|
328
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
329
|
+
// Internal: fetch wrapper that maps non-2xx → WitniumAccountsApiError
|
|
330
|
+
// and applies the configured credential to the request.
|
|
331
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
332
|
+
async req(method, path, opts) {
|
|
333
|
+
const url = this.buildUrl(path, opts.query);
|
|
334
|
+
const headers = {
|
|
335
|
+
accept: "application/json",
|
|
336
|
+
...opts.headers ?? {}
|
|
337
|
+
};
|
|
338
|
+
if (opts.body !== void 0) {
|
|
339
|
+
headers["content-type"] = "application/json";
|
|
340
|
+
}
|
|
341
|
+
const bodyString = opts.body !== void 0 ? JSON.stringify(opts.body) : void 0;
|
|
342
|
+
await this.applyAuth(headers, opts.auth, method, path, bodyString ?? "");
|
|
343
|
+
const controller = new AbortController();
|
|
344
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
345
|
+
let res;
|
|
346
|
+
try {
|
|
347
|
+
res = await this.fetchImpl(url, {
|
|
348
|
+
method,
|
|
349
|
+
headers,
|
|
350
|
+
body: bodyString,
|
|
351
|
+
signal: controller.signal,
|
|
352
|
+
// Send cookies cross-origin when the consumer is a browser using
|
|
353
|
+
// SessionCookie auth via document.cookie.
|
|
354
|
+
credentials: "include"
|
|
355
|
+
});
|
|
356
|
+
} catch (err) {
|
|
357
|
+
throw new WitniumAccountsApiError({
|
|
358
|
+
status: 0,
|
|
359
|
+
message: err instanceof Error ? `Network error contacting ${this.baseUrl}: ${err.message}` : `Network error contacting ${this.baseUrl}`
|
|
360
|
+
});
|
|
361
|
+
} finally {
|
|
362
|
+
clearTimeout(timer);
|
|
363
|
+
}
|
|
364
|
+
if (opts.expectNoContent) {
|
|
365
|
+
if (!res.ok) {
|
|
366
|
+
throw await this.toApiError(res);
|
|
367
|
+
}
|
|
368
|
+
return void 0;
|
|
369
|
+
}
|
|
370
|
+
const text = await res.text();
|
|
371
|
+
let parsed = null;
|
|
372
|
+
if (text.length > 0) {
|
|
373
|
+
try {
|
|
374
|
+
parsed = JSON.parse(text);
|
|
375
|
+
} catch {
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (!res.ok) {
|
|
379
|
+
throw this.parseApiError(res.status, parsed, text);
|
|
380
|
+
}
|
|
381
|
+
return parsed;
|
|
382
|
+
}
|
|
383
|
+
buildUrl(path, query) {
|
|
384
|
+
if (!query) return `${this.baseUrl}${path}`;
|
|
385
|
+
const qs = new URLSearchParams();
|
|
386
|
+
for (const [k, v] of Object.entries(query)) {
|
|
387
|
+
if (v !== void 0) qs.set(k, String(v));
|
|
388
|
+
}
|
|
389
|
+
const suffix = qs.toString();
|
|
390
|
+
return suffix ? `${this.baseUrl}${path}?${suffix}` : `${this.baseUrl}${path}`;
|
|
391
|
+
}
|
|
392
|
+
async applyAuth(headers, auth, method, path, bodyString) {
|
|
393
|
+
switch (auth) {
|
|
394
|
+
case "Public":
|
|
395
|
+
return;
|
|
396
|
+
case "SessionCookie": {
|
|
397
|
+
if (this.cfg.sessionCookie) {
|
|
398
|
+
headers["cookie"] = `wac_session=${this.cfg.sessionCookie}`;
|
|
399
|
+
}
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
case "BearerJWT": {
|
|
403
|
+
if (!this.cfg.accessToken) {
|
|
404
|
+
throw new Error(
|
|
405
|
+
`WitniumAccountsClient: ${method} ${path} requires an OAuth access token. Pass \`accessToken\` to the constructor.`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
headers["authorization"] = `Bearer ${this.cfg.accessToken}`;
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
case "OrgApiKey": {
|
|
412
|
+
if (!this.cfg.orgApiKey) {
|
|
413
|
+
throw new Error(
|
|
414
|
+
`WitniumAccountsClient: ${method} ${path} requires an organisation API key. Pass \`orgApiKey\` to the constructor.`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
headers["authorization"] = `Bearer ${this.cfg.orgApiKey}`;
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
case "AdminToken": {
|
|
421
|
+
if (!this.cfg.adminToken) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`WitniumAccountsClient: ${method} ${path} requires an admin token. Pass \`adminToken\` to the constructor.`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
headers["authorization"] = `Bearer ${this.cfg.adminToken}`;
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
case "SignedRequest": {
|
|
430
|
+
if (!this.cfg.signedRequest) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`WitniumAccountsClient: ${method} ${path} requires a signed-request signer. Pass \`signedRequest\` to the constructor.`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
const timestamp = Math.floor(Date.now() / 1e3).toString();
|
|
436
|
+
const bodyHash = await sha256Hex(bodyString);
|
|
437
|
+
const idemKey = headers["idempotency-key"] ?? "";
|
|
438
|
+
const canonical = `${method.toUpperCase()}
|
|
439
|
+
${path}
|
|
440
|
+
${timestamp}
|
|
441
|
+
${idemKey}
|
|
442
|
+
${bodyHash}`;
|
|
443
|
+
const signature = await this.cfg.signedRequest.sign(canonical);
|
|
444
|
+
headers["x-witnium-key"] = this.cfg.signedRequest.publicKeyHex;
|
|
445
|
+
headers["x-witnium-timestamp"] = timestamp;
|
|
446
|
+
headers["x-witnium-signature"] = signature;
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
parseApiError(status, parsed, rawText) {
|
|
452
|
+
const body = parsed;
|
|
453
|
+
const message = Array.isArray(body?.message) ? body.message.join("; ") : typeof body?.message === "string" ? body.message : body?.error ?? `HTTP ${status}`;
|
|
454
|
+
return new WitniumAccountsApiError({
|
|
455
|
+
status,
|
|
456
|
+
message,
|
|
457
|
+
errorLabel: body?.error,
|
|
458
|
+
body: parsed ?? rawText
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
async toApiError(res) {
|
|
462
|
+
const text = await res.text();
|
|
463
|
+
let parsed = null;
|
|
464
|
+
if (text.length > 0) {
|
|
465
|
+
try {
|
|
466
|
+
parsed = JSON.parse(text);
|
|
467
|
+
} catch {
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return this.parseApiError(res.status, parsed, text);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
var Subscriptions = class {
|
|
474
|
+
constructor(client) {
|
|
475
|
+
this.client = client;
|
|
476
|
+
}
|
|
477
|
+
client;
|
|
478
|
+
/**
|
|
479
|
+
* Start a Stripe Checkout session for the supplied price. Returns the
|
|
480
|
+
* hosted Checkout URL; redirect the user to it. Stripe's
|
|
481
|
+
* `checkout.session.completed` webhook grants credits on success.
|
|
482
|
+
*/
|
|
483
|
+
subscribe(body) {
|
|
484
|
+
return this.client.createCheckoutSession(body);
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Open the Stripe Billing Portal for the calling user's org. Returns the
|
|
488
|
+
* hosted portal URL — redirect the user there for subscription /
|
|
489
|
+
* payment-method management.
|
|
490
|
+
*/
|
|
491
|
+
manage() {
|
|
492
|
+
return this.client.createPortalSession();
|
|
493
|
+
}
|
|
494
|
+
/** Recent credit-ledger entries (most recent 200). */
|
|
495
|
+
getLedger() {
|
|
496
|
+
return this.client.getLedger();
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
var DelegatedKeys = class {
|
|
500
|
+
constructor(client) {
|
|
501
|
+
this.client = client;
|
|
502
|
+
}
|
|
503
|
+
client;
|
|
504
|
+
/** List the caller's delegated keys, optionally filtered by contract or active flag. */
|
|
505
|
+
list(query) {
|
|
506
|
+
return this.client.listDelegatedKeys(query);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* One-call delegated-key provisioning: prepare → owner-sign → submit →
|
|
510
|
+
* poll until the on-chain `addSigningKey` tx confirms (or the polling
|
|
511
|
+
* budget elapses). The server mints the delegated key in Vault; the caller
|
|
512
|
+
* never sees its private key.
|
|
513
|
+
*
|
|
514
|
+
* Failure modes that surface as thrown {@link WitniumAccountsApiError}:
|
|
515
|
+
* - 409 from prepare → an active key already exists for this contract;
|
|
516
|
+
* caller must revoke the existing one first.
|
|
517
|
+
* - 400 from submit → ownerSignature didn't verify against the prepared
|
|
518
|
+
* message (wrong owner key, or the on-chain nonce shifted between
|
|
519
|
+
* prepare and submit and the caller must re-provision).
|
|
520
|
+
*
|
|
521
|
+
* Returns `confirmed: false` (without throwing) when the on-chain tx is
|
|
522
|
+
* still pending after `pollTimeoutMs` — caller can keep polling via
|
|
523
|
+
* {@link list} or chain-api's receipt endpoint.
|
|
524
|
+
*/
|
|
525
|
+
async provision(args) {
|
|
526
|
+
const prep = await this.client.prepareDelegatedKey({
|
|
527
|
+
contractAddress: args.contractAddress
|
|
528
|
+
});
|
|
529
|
+
const ownerSignature = await args.ownerSigner.sign(prep.messageToSign);
|
|
530
|
+
let res = await this.client.submitDelegatedKey(prep.id, { ownerSignature });
|
|
531
|
+
if (!res.confirmed) {
|
|
532
|
+
const interval = args.pollIntervalMs ?? 2e3;
|
|
533
|
+
const timeout = args.pollTimeoutMs ?? 6e4;
|
|
534
|
+
const deadline = Date.now() + timeout;
|
|
535
|
+
while (!res.confirmed && Date.now() < deadline) {
|
|
536
|
+
await sleep(interval);
|
|
537
|
+
res = await this.client.submitDelegatedKey(prep.id, {});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return {
|
|
541
|
+
id: prep.id,
|
|
542
|
+
publicKey: prep.publicKey,
|
|
543
|
+
transactionHash: res.transactionHash,
|
|
544
|
+
confirmed: res.confirmed,
|
|
545
|
+
blockNumber: res.blockNumber
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Locally revoke a delegated key. Wipes the Vault Transit key and sets
|
|
550
|
+
* `revoked_at` on the row. The on-chain trust record persists — the caller
|
|
551
|
+
* must invoke `WitnessRegistryV3.revokeSigningKey` with their owner key to
|
|
552
|
+
* fully un-trust the key on the contract.
|
|
553
|
+
*/
|
|
554
|
+
revoke(id) {
|
|
555
|
+
return this.client.revokeDelegatedKey(id);
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
var SigningKeys = class {
|
|
559
|
+
constructor(client) {
|
|
560
|
+
this.client = client;
|
|
561
|
+
}
|
|
562
|
+
client;
|
|
563
|
+
/**
|
|
564
|
+
* The signing keys attached to the calling user's contract. There is no
|
|
565
|
+
* dedicated list endpoint; this method calls {@link
|
|
566
|
+
* WitniumAccountsClient.getAccount} and returns the `signingKeys` slice.
|
|
567
|
+
*/
|
|
568
|
+
async list() {
|
|
569
|
+
const account = await this.client.getAccount();
|
|
570
|
+
return account.signingKeys;
|
|
571
|
+
}
|
|
572
|
+
add(body) {
|
|
573
|
+
return this.client.addSigningKey(body);
|
|
574
|
+
}
|
|
575
|
+
revoke(body) {
|
|
576
|
+
return this.client.revokeSigningKey(body);
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
var OauthNamespace = class {
|
|
580
|
+
sessions;
|
|
581
|
+
constructor(client) {
|
|
582
|
+
this.sessions = new OauthSessions(client);
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
var OauthSessions = class {
|
|
586
|
+
constructor(client) {
|
|
587
|
+
this.client = client;
|
|
588
|
+
}
|
|
589
|
+
client;
|
|
590
|
+
list() {
|
|
591
|
+
return this.client.listOauthSessions();
|
|
592
|
+
}
|
|
593
|
+
revoke(jti) {
|
|
594
|
+
return this.client.revokeOauthSession(jti);
|
|
595
|
+
}
|
|
596
|
+
revokeAll() {
|
|
597
|
+
return this.client.revokeAllOauthSessions();
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
function sleep(ms) {
|
|
601
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
602
|
+
}
|
|
603
|
+
function randomUUID() {
|
|
604
|
+
const c = globalThis.crypto;
|
|
605
|
+
if (!c?.randomUUID) {
|
|
606
|
+
throw new Error(
|
|
607
|
+
"WitniumAccountsClient: globalThis.crypto.randomUUID is required (Node 19+ or modern browser). Polyfill for older runtimes."
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
return c.randomUUID();
|
|
611
|
+
}
|
|
612
|
+
async function sha256Hex(input) {
|
|
613
|
+
const subtle = globalThis.crypto?.subtle;
|
|
614
|
+
if (!subtle) {
|
|
615
|
+
throw new Error(
|
|
616
|
+
"WitniumAccountsClient: SubtleCrypto is not available. Polyfill `globalThis.crypto.subtle` for SignedRequest auth."
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
const data = new TextEncoder().encode(input);
|
|
620
|
+
const digest = await subtle.digest("SHA-256", data);
|
|
621
|
+
const bytes = new Uint8Array(digest);
|
|
622
|
+
let out = "";
|
|
623
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
624
|
+
return out;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// src/admin-client.ts
|
|
628
|
+
var WitniumAccountsAdminClient = class {
|
|
629
|
+
inner;
|
|
630
|
+
constructor(config) {
|
|
631
|
+
if (!config.adminToken) {
|
|
632
|
+
throw new Error("WitniumAccountsAdminClient: adminToken is required");
|
|
633
|
+
}
|
|
634
|
+
this.inner = new WitniumAccountsClient({
|
|
635
|
+
baseUrl: config.baseUrl,
|
|
636
|
+
adminToken: config.adminToken,
|
|
637
|
+
timeout: config.timeout,
|
|
638
|
+
fetch: config.fetch
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Mint a new organisation. The returned `apiKey` is shown ONCE — the server
|
|
643
|
+
* only retains its SHA-256 hash. Persist it before the response leaves
|
|
644
|
+
* scope; there is no recovery path.
|
|
645
|
+
*
|
|
646
|
+
* @param body Organisation seed: name, email, optional accountType and
|
|
647
|
+
* signup credit grant, optional skip-email-verification flag.
|
|
648
|
+
*/
|
|
649
|
+
createOrganization(body) {
|
|
650
|
+
return this.inner.createOrganization(body);
|
|
651
|
+
}
|
|
652
|
+
/** Flip an org between `metered` (Stripe checkout + credit ledger) and `unlimited` (flat-fee). */
|
|
653
|
+
setAccountType(orgId, accountType) {
|
|
654
|
+
return this.inner.setOrgAccountType(orgId, { accountType });
|
|
655
|
+
}
|
|
656
|
+
/** Mark an org's email as verified. Prerequisite for the org to create users. */
|
|
657
|
+
verifyEmail(orgId) {
|
|
658
|
+
return this.inner.verifyOrganization(orgId);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Rotate the org's API key. The previous `wcorg_live_…` is invalidated and
|
|
662
|
+
* the new key is returned ONCE — same one-time-secret semantics as
|
|
663
|
+
* {@link createOrganization}.
|
|
664
|
+
*/
|
|
665
|
+
rotateApiKey(orgId) {
|
|
666
|
+
return this.inner.rotateOrgApiKey(orgId);
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Apply a signed credit delta to an org's ledger. Positive `delta` grants
|
|
670
|
+
* credits (goodwill, migration backfill); negative claws them back.
|
|
671
|
+
* Recorded as `reason: adjustment` with the supplied `note`.
|
|
672
|
+
*
|
|
673
|
+
* Use sparingly — this bypasses Stripe and should be auditable from the
|
|
674
|
+
* `note` alone.
|
|
675
|
+
*/
|
|
676
|
+
adjustCredits(orgId, delta, note) {
|
|
677
|
+
return this.inner.adjustOrgCredits(orgId, { delta, note });
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// src/org-client.ts
|
|
682
|
+
var WitniumAccountsOrgClient = class {
|
|
683
|
+
inner;
|
|
684
|
+
/** User-management namespace — `client.users.create/list`. */
|
|
685
|
+
users;
|
|
686
|
+
constructor(config) {
|
|
687
|
+
if (!config.orgApiKey) {
|
|
688
|
+
throw new Error("WitniumAccountsOrgClient: orgApiKey is required");
|
|
689
|
+
}
|
|
690
|
+
this.inner = new WitniumAccountsClient({
|
|
691
|
+
baseUrl: config.baseUrl,
|
|
692
|
+
orgApiKey: config.orgApiKey,
|
|
693
|
+
timeout: config.timeout,
|
|
694
|
+
fetch: config.fetch
|
|
695
|
+
});
|
|
696
|
+
this.users = new OrgUsers(this.inner);
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* The org's own profile — name, email, account type, cached credit
|
|
700
|
+
* balance (null for `unlimited` accounts), and the `isPersonal` flag.
|
|
701
|
+
*
|
|
702
|
+
* Note: a 401 here usually means the API key was rotated; rotate-key
|
|
703
|
+
* invalidates the previous one.
|
|
704
|
+
*/
|
|
705
|
+
me() {
|
|
706
|
+
return this.inner.getMyOrg();
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
var OrgUsers = class {
|
|
710
|
+
constructor(inner) {
|
|
711
|
+
this.inner = inner;
|
|
712
|
+
}
|
|
713
|
+
inner;
|
|
714
|
+
/**
|
|
715
|
+
* Provision a new user inside the org. Requires the org's email to have
|
|
716
|
+
* been verified (sysadmin gate) — otherwise the server returns 403.
|
|
717
|
+
*/
|
|
718
|
+
create(body) {
|
|
719
|
+
return this.inner.createOrgUser(body);
|
|
720
|
+
}
|
|
721
|
+
/** List the org's users. */
|
|
722
|
+
list() {
|
|
723
|
+
return this.inner.listOrgUsers();
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
export { DelegatedKeys, OauthNamespace, OauthSessions, OrgUsers, SigningKeys, Subscriptions, WitniumAccountsAdminClient, WitniumAccountsApiError, WitniumAccountsClient, WitniumAccountsOrgClient };
|
|
728
|
+
//# sourceMappingURL=index.mjs.map
|
|
729
|
+
//# sourceMappingURL=index.mjs.map
|