@stack0/sdk 0.5.18 → 0.6.1
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/dist/cdn/index.d.mts +188 -1
- package/dist/cdn/index.d.ts +188 -1
- package/dist/cdn/index.js +107 -0
- package/dist/cdn/index.js.map +1 -1
- package/dist/cdn/index.mjs +107 -0
- package/dist/cdn/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +204 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +203 -2
- package/dist/index.mjs.map +1 -1
- package/dist/mail/index.d.mts +76 -4
- package/dist/mail/index.d.ts +76 -4
- package/dist/mail/index.js +97 -1
- package/dist/mail/index.js.map +1 -1
- package/dist/mail/index.mjs +96 -2
- package/dist/mail/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -432,11 +432,19 @@ var Mailboxes = class {
|
|
|
432
432
|
}
|
|
433
433
|
http;
|
|
434
434
|
/**
|
|
435
|
-
* Create a new mailbox
|
|
435
|
+
* Create a new mailbox. The result carries `webhookSecret`, which signs every
|
|
436
|
+
* inbound delivery; store it now (only `create` and `rotateSecret` return it).
|
|
436
437
|
*/
|
|
437
438
|
async create(request) {
|
|
438
439
|
return this.http.post("/mail/mailboxes", request);
|
|
439
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Replace a mailbox's webhook secret and return the new one. Deliveries are
|
|
443
|
+
* signed with the new secret from then on.
|
|
444
|
+
*/
|
|
445
|
+
async rotateSecret(id) {
|
|
446
|
+
return this.http.post(`/mail/mailboxes/${id}/rotate-secret`, {});
|
|
447
|
+
}
|
|
440
448
|
/**
|
|
441
449
|
* Get a mailbox by ID
|
|
442
450
|
*/
|
|
@@ -1898,6 +1906,7 @@ var CDN = class {
|
|
|
1898
1906
|
const params = new URLSearchParams();
|
|
1899
1907
|
if (request.projectSlug) params.set("projectSlug", request.projectSlug);
|
|
1900
1908
|
if (request.environment) params.set("environment", request.environment);
|
|
1909
|
+
if (request.folder) params.set("folder", request.folder);
|
|
1901
1910
|
if (request.periodStart) {
|
|
1902
1911
|
const date = request.periodStart instanceof Date ? request.periodStart.toISOString() : request.periodStart;
|
|
1903
1912
|
params.set("periodStart", date);
|
|
@@ -2263,6 +2272,112 @@ var CDN = class {
|
|
|
2263
2272
|
return this.convertMergeJobDates(job);
|
|
2264
2273
|
}
|
|
2265
2274
|
// ============================================================================
|
|
2275
|
+
// Video Render Methods
|
|
2276
|
+
// ============================================================================
|
|
2277
|
+
/**
|
|
2278
|
+
* Render a declarative video spec into a video asset
|
|
2279
|
+
*
|
|
2280
|
+
* The spec describes scenes, layers, timing and media as data. There are no
|
|
2281
|
+
* compositions to write and no code to upload — Stack0 owns the renderer.
|
|
2282
|
+
*
|
|
2283
|
+
* `@stack0/video-spec` has the full types, validation and worked examples;
|
|
2284
|
+
* validating locally before you POST turns a round trip into a type error.
|
|
2285
|
+
*
|
|
2286
|
+
* @example
|
|
2287
|
+
* ```typescript
|
|
2288
|
+
* const job = await cdn.createRenderJob({
|
|
2289
|
+
* projectSlug: 'my-project',
|
|
2290
|
+
* spec: {
|
|
2291
|
+
* canvas: { width: 1080, height: 1920, fps: 30 },
|
|
2292
|
+
* audio: [{ src: musicAssetId, volume: 0.34 }],
|
|
2293
|
+
* scenes: [
|
|
2294
|
+
* {
|
|
2295
|
+
* id: 'hook',
|
|
2296
|
+
* duration: 2.5,
|
|
2297
|
+
* layers: [
|
|
2298
|
+
* { type: 'image', src: afterImageAssetId, zoom: { from: 1, to: 1.06 } },
|
|
2299
|
+
* { type: 'caption', text: 'i told my mom\nthe kitchen exploded', position: 'upperThird' },
|
|
2300
|
+
* ],
|
|
2301
|
+
* },
|
|
2302
|
+
* {
|
|
2303
|
+
* // No duration: the thread derives its own length from the messages
|
|
2304
|
+
* id: 'payoff',
|
|
2305
|
+
* layers: [
|
|
2306
|
+
* {
|
|
2307
|
+
* type: 'messageThread',
|
|
2308
|
+
* recipient: 'Mom',
|
|
2309
|
+
* imageSrc: afterImageAssetId,
|
|
2310
|
+
* messages: [
|
|
2311
|
+
* { sender: 'recipient', text: 'you home?', history: true },
|
|
2312
|
+
* { sender: 'user', text: '[IMAGE]' },
|
|
2313
|
+
* { sender: 'recipient', text: 'WHAT IS THAT' },
|
|
2314
|
+
* ],
|
|
2315
|
+
* },
|
|
2316
|
+
* ],
|
|
2317
|
+
* },
|
|
2318
|
+
* ],
|
|
2319
|
+
* },
|
|
2320
|
+
* webhookUrl: 'https://your-app.com/webhook',
|
|
2321
|
+
* });
|
|
2322
|
+
* console.log(job.durationInFrames); // known before rendering starts
|
|
2323
|
+
* ```
|
|
2324
|
+
*/
|
|
2325
|
+
async createRenderJob(request) {
|
|
2326
|
+
const response = await this.http.post("/cdn/video/render", request);
|
|
2327
|
+
return this.convertRenderJobDates(response);
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Get a render job by ID with output asset details
|
|
2331
|
+
*
|
|
2332
|
+
* @example
|
|
2333
|
+
* ```typescript
|
|
2334
|
+
* const job = await cdn.getRenderJob('job-id');
|
|
2335
|
+
* if (job.status === 'completed' && job.outputAsset) {
|
|
2336
|
+
* console.log(`Output video: ${job.outputAsset.cdnUrl}`);
|
|
2337
|
+
* }
|
|
2338
|
+
* ```
|
|
2339
|
+
*/
|
|
2340
|
+
async getRenderJob(jobId) {
|
|
2341
|
+
const response = await this.http.get(`/cdn/video/render/${jobId}`);
|
|
2342
|
+
return this.convertRenderJobDates(response);
|
|
2343
|
+
}
|
|
2344
|
+
/**
|
|
2345
|
+
* List render jobs with optional filters
|
|
2346
|
+
*/
|
|
2347
|
+
async listRenderJobs(request) {
|
|
2348
|
+
const params = new URLSearchParams();
|
|
2349
|
+
params.set("projectSlug", request.projectSlug);
|
|
2350
|
+
if (request.status) params.set("status", request.status);
|
|
2351
|
+
if (request.limit) params.set("limit", request.limit.toString());
|
|
2352
|
+
if (request.offset) params.set("offset", request.offset.toString());
|
|
2353
|
+
const response = await this.http.get(`/cdn/video/render?${params.toString()}`);
|
|
2354
|
+
return {
|
|
2355
|
+
...response,
|
|
2356
|
+
jobs: response.jobs.map((job) => this.convertRenderJobDates(job))
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
/**
|
|
2360
|
+
* Cancel a pending or processing render job
|
|
2361
|
+
*/
|
|
2362
|
+
async cancelRenderJob(jobId) {
|
|
2363
|
+
return this.http.post(`/cdn/video/render/${jobId}/cancel`, {});
|
|
2364
|
+
}
|
|
2365
|
+
convertRenderJobDates(job) {
|
|
2366
|
+
if (typeof job.createdAt === "string") {
|
|
2367
|
+
job.createdAt = new Date(job.createdAt);
|
|
2368
|
+
}
|
|
2369
|
+
if (job.updatedAt && typeof job.updatedAt === "string") {
|
|
2370
|
+
job.updatedAt = new Date(job.updatedAt);
|
|
2371
|
+
}
|
|
2372
|
+
if (job.startedAt && typeof job.startedAt === "string") {
|
|
2373
|
+
job.startedAt = new Date(job.startedAt);
|
|
2374
|
+
}
|
|
2375
|
+
if (job.completedAt && typeof job.completedAt === "string") {
|
|
2376
|
+
job.completedAt = new Date(job.completedAt);
|
|
2377
|
+
}
|
|
2378
|
+
return job;
|
|
2379
|
+
}
|
|
2380
|
+
// ============================================================================
|
|
2266
2381
|
// S3 Import Methods
|
|
2267
2382
|
// ============================================================================
|
|
2268
2383
|
/**
|
|
@@ -5659,6 +5774,92 @@ var Workflows = class {
|
|
|
5659
5774
|
}
|
|
5660
5775
|
};
|
|
5661
5776
|
|
|
5777
|
+
// src/mail/webhooks.ts
|
|
5778
|
+
var WebhookVerificationError = class extends Error {
|
|
5779
|
+
constructor(message) {
|
|
5780
|
+
super(message);
|
|
5781
|
+
this.name = "WebhookVerificationError";
|
|
5782
|
+
}
|
|
5783
|
+
};
|
|
5784
|
+
function header(headers, name) {
|
|
5785
|
+
if (typeof headers.get === "function") return headers.get(name) ?? void 0;
|
|
5786
|
+
const record = headers;
|
|
5787
|
+
for (const key of Object.keys(record)) {
|
|
5788
|
+
if (key.toLowerCase() === name) {
|
|
5789
|
+
const v = record[key];
|
|
5790
|
+
return Array.isArray(v) ? v[0] : v;
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5793
|
+
return void 0;
|
|
5794
|
+
}
|
|
5795
|
+
function base64ToBytes(b64) {
|
|
5796
|
+
const bin = atob(b64);
|
|
5797
|
+
const out = new Uint8Array(bin.length);
|
|
5798
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
5799
|
+
return out;
|
|
5800
|
+
}
|
|
5801
|
+
function bytesToBase64(bytes) {
|
|
5802
|
+
let bin = "";
|
|
5803
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
5804
|
+
return btoa(bin);
|
|
5805
|
+
}
|
|
5806
|
+
async function subtle() {
|
|
5807
|
+
const global = globalThis.crypto;
|
|
5808
|
+
if (global?.subtle) return global.subtle;
|
|
5809
|
+
const specifier = "node:crypto";
|
|
5810
|
+
const nodeCrypto = await import(
|
|
5811
|
+
/* webpackIgnore: true */
|
|
5812
|
+
/* @vite-ignore */
|
|
5813
|
+
specifier
|
|
5814
|
+
);
|
|
5815
|
+
return nodeCrypto.webcrypto.subtle;
|
|
5816
|
+
}
|
|
5817
|
+
function timingSafeEqual(a, b) {
|
|
5818
|
+
if (a.length !== b.length) return false;
|
|
5819
|
+
let diff = 0;
|
|
5820
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
5821
|
+
return diff === 0;
|
|
5822
|
+
}
|
|
5823
|
+
async function verifyInboundWebhook(options) {
|
|
5824
|
+
const id = header(options.headers, "webhook-id");
|
|
5825
|
+
const timestamp = header(options.headers, "webhook-timestamp");
|
|
5826
|
+
const signatures = header(options.headers, "webhook-signature");
|
|
5827
|
+
if (!id || !timestamp || !signatures) {
|
|
5828
|
+
throw new WebhookVerificationError("missing webhook-id, webhook-timestamp, or webhook-signature header");
|
|
5829
|
+
}
|
|
5830
|
+
const seconds = Number(timestamp);
|
|
5831
|
+
if (!Number.isInteger(seconds)) throw new WebhookVerificationError("webhook-timestamp is not a whole number");
|
|
5832
|
+
const now = Math.floor((options.now ?? /* @__PURE__ */ new Date()).getTime() / 1e3);
|
|
5833
|
+
const tolerance = options.toleranceSeconds ?? 300;
|
|
5834
|
+
if (Math.abs(now - seconds) > tolerance) {
|
|
5835
|
+
throw new WebhookVerificationError(`webhook-timestamp is more than ${tolerance} seconds from now`);
|
|
5836
|
+
}
|
|
5837
|
+
const body = typeof options.payload === "string" ? options.payload : new TextDecoder().decode(options.payload instanceof ArrayBuffer ? new Uint8Array(options.payload) : options.payload);
|
|
5838
|
+
const secret = options.secret.startsWith("whsec_") ? options.secret.slice(6) : options.secret;
|
|
5839
|
+
const webcrypto = await subtle();
|
|
5840
|
+
const key = await webcrypto.importKey(
|
|
5841
|
+
"raw",
|
|
5842
|
+
base64ToBytes(secret),
|
|
5843
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
5844
|
+
false,
|
|
5845
|
+
["sign"]
|
|
5846
|
+
);
|
|
5847
|
+
const mac = new Uint8Array(
|
|
5848
|
+
await webcrypto.sign("HMAC", key, new TextEncoder().encode(`${id}.${seconds}.${body}`))
|
|
5849
|
+
);
|
|
5850
|
+
const expected = bytesToBase64(mac);
|
|
5851
|
+
const matched = signatures.split(" ").map((s) => s.split(",", 2)).some(([version, sig]) => version === "v1" && sig !== void 0 && timingSafeEqual(sig, expected));
|
|
5852
|
+
if (!matched) throw new WebhookVerificationError("no webhook-signature matches");
|
|
5853
|
+
let payload;
|
|
5854
|
+
try {
|
|
5855
|
+
payload = JSON.parse(body);
|
|
5856
|
+
} catch {
|
|
5857
|
+
throw new WebhookVerificationError("the payload is not JSON");
|
|
5858
|
+
}
|
|
5859
|
+
if (payload.id !== id) throw new WebhookVerificationError("the payload id does not match webhook-id");
|
|
5860
|
+
return payload;
|
|
5861
|
+
}
|
|
5862
|
+
|
|
5662
5863
|
// src/index.ts
|
|
5663
5864
|
var Stack0 = class {
|
|
5664
5865
|
http;
|
|
@@ -5740,7 +5941,9 @@ exports.Sequences = Sequences;
|
|
|
5740
5941
|
exports.Stack0 = Stack0;
|
|
5741
5942
|
exports.Templates = Templates;
|
|
5742
5943
|
exports.Webdata = Webdata;
|
|
5944
|
+
exports.WebhookVerificationError = WebhookVerificationError;
|
|
5743
5945
|
exports.Workflows = Workflows;
|
|
5744
5946
|
exports.default = src_default;
|
|
5947
|
+
exports.verifyInboundWebhook = verifyInboundWebhook;
|
|
5745
5948
|
//# sourceMappingURL=index.js.map
|
|
5746
5949
|
//# sourceMappingURL=index.js.map
|