@nodaro/sdk 2.28.0 → 2.29.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 +2 -2
- package/dist/index.cjs +602 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6901 -5658
- package/dist/index.d.ts +6901 -5658
- package/dist/index.js +592 -29
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -190,36 +190,57 @@ var JobHeldError = class extends NodaroError {
|
|
|
190
190
|
}
|
|
191
191
|
jobId;
|
|
192
192
|
};
|
|
193
|
+
var finite = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
194
|
+
var BARE_CODE = /^[a-z][a-z0-9_]*$/;
|
|
195
|
+
var nonEmpty = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
|
|
196
|
+
function readErrorBody(status, body) {
|
|
197
|
+
const raw = body.error;
|
|
198
|
+
const envelope = raw && typeof raw === "object" ? raw : {};
|
|
199
|
+
const bare = typeof raw === "string" ? raw : void 0;
|
|
200
|
+
const bareCode = bare !== void 0 && BARE_CODE.test(bare) ? bare : void 0;
|
|
201
|
+
const bareSentence = bare !== void 0 && bareCode === void 0 && /\s/.test(bare.trim()) ? bare : void 0;
|
|
202
|
+
const code = nonEmpty(envelope.code) ?? bareCode ?? (status === 400 && raw === void 0 && Array.isArray(body.issues) ? "validation_error" : void 0);
|
|
203
|
+
const outside = status >= 500 ? void 0 : nonEmpty(body.message) ?? nonEmpty(body.error_description) ?? bareSentence;
|
|
204
|
+
const message = nonEmpty(envelope.message) ?? outside;
|
|
205
|
+
return { code, message, envelope };
|
|
206
|
+
}
|
|
193
207
|
function throwFromResponse(status, body) {
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
|
|
208
|
+
const parsed = body && typeof body === "object" ? body : {};
|
|
209
|
+
const read = readErrorBody(status, parsed);
|
|
210
|
+
const envelope = read.envelope;
|
|
211
|
+
const code = read.code ?? "internal_error";
|
|
212
|
+
const message = read.message ?? (read.code ? read.code : "Request failed");
|
|
213
|
+
if (status === 401) throw new UnauthorizedError(read.message);
|
|
197
214
|
if (status === 409 && (code === "workflow_conflict" || code === "production_busy")) {
|
|
198
215
|
throw new WorkflowConflictError(
|
|
199
|
-
message,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
216
|
+
read.message,
|
|
217
|
+
envelope.currentUpdatedAt,
|
|
218
|
+
envelope.currentVersion,
|
|
219
|
+
envelope.currentRecord,
|
|
203
220
|
code
|
|
204
221
|
);
|
|
205
222
|
}
|
|
206
|
-
if (status >= 400 && status < 500 && typeof
|
|
207
|
-
throw new StudioOpError(message, code, status,
|
|
223
|
+
if (status >= 400 && status < 500 && typeof envelope.opIndex === "number") {
|
|
224
|
+
throw new StudioOpError(message, code, status, envelope.opIndex);
|
|
208
225
|
}
|
|
209
|
-
if (status === 409 && CONTINUATION_REFUSAL_CODES.has(code) && typeof
|
|
210
|
-
throw new ContinuationRefusedError(message, code,
|
|
226
|
+
if (status === 409 && CONTINUATION_REFUSAL_CODES.has(code) && typeof parsed.jobId === "string") {
|
|
227
|
+
throw new ContinuationRefusedError(message, code, parsed.jobId);
|
|
211
228
|
}
|
|
212
|
-
if (status === 422 && code === "job_blocked") throw new JobBlockedError(message);
|
|
229
|
+
if (status === 422 && code === "job_blocked") throw new JobBlockedError(read.message);
|
|
213
230
|
if (status === 403 && code === "insufficient_scope") {
|
|
214
|
-
throw new ForbiddenError(message,
|
|
231
|
+
throw new ForbiddenError(read.message, envelope.missingScope);
|
|
215
232
|
}
|
|
216
|
-
if (status === 403) throw new ForbiddenError(message);
|
|
217
|
-
if (status === 404) throw new NotFoundError(
|
|
218
|
-
if (status === 429) throw new RateLimitedError(message);
|
|
233
|
+
if (status === 403) throw new ForbiddenError(read.message);
|
|
234
|
+
if (status === 404) throw new NotFoundError(read.message);
|
|
235
|
+
if (status === 429) throw new RateLimitedError(read.message);
|
|
219
236
|
if (status === 402) {
|
|
220
|
-
throw new InsufficientCreditsError(
|
|
237
|
+
throw new InsufficientCreditsError(
|
|
238
|
+
read.message,
|
|
239
|
+
finite(envelope.required) ?? finite(parsed.required),
|
|
240
|
+
finite(envelope.available) ?? finite(parsed.balance)
|
|
241
|
+
);
|
|
221
242
|
}
|
|
222
|
-
if (status === 413) throw new StorageExceededError(message,
|
|
243
|
+
if (status === 413) throw new StorageExceededError(read.message, envelope.limitBytes);
|
|
223
244
|
throw new NodaroError(message, code, status);
|
|
224
245
|
}
|
|
225
246
|
|
|
@@ -509,6 +530,10 @@ var SessionsResource = class {
|
|
|
509
530
|
};
|
|
510
531
|
|
|
511
532
|
// src/resources/jobs.ts
|
|
533
|
+
function listParam(value) {
|
|
534
|
+
if (value === void 0) return void 0;
|
|
535
|
+
return typeof value === "string" ? value : value.join(",");
|
|
536
|
+
}
|
|
512
537
|
var JobsResource = class {
|
|
513
538
|
constructor(client) {
|
|
514
539
|
this.client = client;
|
|
@@ -522,9 +547,26 @@ var JobsResource = class {
|
|
|
522
547
|
* Your jobs, newest first (`GET /v1/jobs`), cursor-paginated. `type` and
|
|
523
548
|
* `origin` filter on the job's own `input_data`, which every job carries —
|
|
524
549
|
* a client app lists its runs with `{ type: "llm-structured", origin: "studio" }`.
|
|
550
|
+
* `status` and `type` take one value or a list; `favorited` and `rating`
|
|
551
|
+
* filter on your own marks. Every filter combines with AND, and each job
|
|
552
|
+
* carries `favorited` and `rating`.
|
|
525
553
|
*/
|
|
526
554
|
list(params = {}) {
|
|
527
|
-
|
|
555
|
+
const { status, type, ...rest } = params;
|
|
556
|
+
return this.client.request("GET", "/v1/jobs", {
|
|
557
|
+
query: { ...rest, status: listParam(status), type: listParam(type) }
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Rate one of your jobs `"good"` or `"bad"`, or clear the rating with
|
|
562
|
+
* `null` (`PUT /v1/jobs/:id/rating`). Only the job's owner rates it — anyone
|
|
563
|
+
* else's job is a `NotFoundError`. The rating reads back as `Job.rating`
|
|
564
|
+
* and filters {@link list}. OAuth apps need the `assets:write` scope.
|
|
565
|
+
*/
|
|
566
|
+
rate(id, rating) {
|
|
567
|
+
return this.client.request("PUT", `/v1/jobs/${encodeURIComponent(id)}/rating`, {
|
|
568
|
+
body: { rating }
|
|
569
|
+
});
|
|
528
570
|
}
|
|
529
571
|
/**
|
|
530
572
|
* Get the lean status of a single job (poll-loop friendly).
|
|
@@ -651,6 +693,26 @@ function extractJobId(result, label) {
|
|
|
651
693
|
}
|
|
652
694
|
throw new JobFailedError(`${label} did not return a jobId`, "");
|
|
653
695
|
}
|
|
696
|
+
function isImageBatchRequest(type, params) {
|
|
697
|
+
if (type !== "generate-image") return false;
|
|
698
|
+
return typeof params.count === "number" && params.count > 1 || Array.isArray(params.models);
|
|
699
|
+
}
|
|
700
|
+
function batchJobIds(result) {
|
|
701
|
+
const ids = result?.jobIds;
|
|
702
|
+
return Array.isArray(ids) && ids.every((id) => typeof id === "string") ? ids : null;
|
|
703
|
+
}
|
|
704
|
+
function batchRefusal(detail) {
|
|
705
|
+
return new NodaroError(
|
|
706
|
+
`${detail}: this waits for one job \u2014 use nodes.runBatchAndWait() for a batch (count above 1, or models).`,
|
|
707
|
+
"batch_requires_run_batch_and_wait",
|
|
708
|
+
0
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
function batchResultOf(result) {
|
|
712
|
+
const r = result;
|
|
713
|
+
if (!r || typeof r.batchId !== "string" || !batchJobIds(result) || !Array.isArray(r.groups)) return null;
|
|
714
|
+
return r;
|
|
715
|
+
}
|
|
654
716
|
var NodesResource = class {
|
|
655
717
|
constructor(client) {
|
|
656
718
|
this.client = client;
|
|
@@ -683,10 +745,55 @@ var NodesResource = class {
|
|
|
683
745
|
}
|
|
684
746
|
async runAndWait(type, params = {}, opts = {}) {
|
|
685
747
|
if (opts.signal?.aborted) throw new JobAbortedError();
|
|
748
|
+
if (isImageBatchRequest(type, params)) throw batchRefusal(`${type} with count above 1 or models makes several jobs`);
|
|
686
749
|
const result = await this.run(type, params, { idempotencyKey: opts.idempotencyKey });
|
|
750
|
+
const ids = batchJobIds(result);
|
|
751
|
+
if (ids && ids.length > 1) throw batchRefusal(`${type} started ${ids.length} jobs (${ids.join(", ")}), which keep running`);
|
|
687
752
|
const jobId = extractJobId(result, type);
|
|
688
753
|
return this.pollJob(jobId, type, opts);
|
|
689
754
|
}
|
|
755
|
+
/**
|
|
756
|
+
* Run a generate-image BATCH — `count` above 1 and/or `models` — to the end:
|
|
757
|
+
* submit it once ({@link run}, with your `idempotencyKey` if any), then wait
|
|
758
|
+
* for every job at once, each the way {@link runAndWait} waits.
|
|
759
|
+
*
|
|
760
|
+
* Resolves `{ batch, results }`: the submit's answer
|
|
761
|
+
* ({@link GenerateImageBatchResult} — ids, groups by model, credits) and one
|
|
762
|
+
* {@link BatchJobOutcome} per job, in `batch.jobIds` order — its output, or the
|
|
763
|
+
* typed error that ended the wait for it. One image failing never discards the
|
|
764
|
+
* others. Rejects only before any job exists (the submit's own errors, e.g.
|
|
765
|
+
* `InsufficientCreditsError` with the batch total as `required`), and on
|
|
766
|
+
* `signal` ({@link JobAbortedError}; `cancelOnAbort` cancels every job).
|
|
767
|
+
*
|
|
768
|
+
* A request for one image is refused before anything is sent (code
|
|
769
|
+
* `not_a_batch`) — wait for it with {@link runAndWait}.
|
|
770
|
+
*/
|
|
771
|
+
async runBatchAndWait(type, params, opts = {}) {
|
|
772
|
+
if (opts.signal?.aborted) throw new JobAbortedError();
|
|
773
|
+
if (!isImageBatchRequest(type, params)) {
|
|
774
|
+
throw new NodaroError(
|
|
775
|
+
"runBatchAndWait waits for a batch \u2014 count above 1, or models; wait for one image with nodes.runAndWait().",
|
|
776
|
+
"not_a_batch",
|
|
777
|
+
0
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
const result = await this.run(type, params, { idempotencyKey: opts.idempotencyKey });
|
|
781
|
+
const batch = batchResultOf(result);
|
|
782
|
+
if (!batch) throw new JobFailedError(`${type} did not answer with a batch`, "");
|
|
783
|
+
const modelOf = new Map(batch.groups.flatMap((group) => group.jobIds.map((id) => [id, group.model])));
|
|
784
|
+
const results = await Promise.all(
|
|
785
|
+
batch.jobIds.map(async (jobId) => {
|
|
786
|
+
const model = modelOf.get(jobId) ?? "";
|
|
787
|
+
try {
|
|
788
|
+
return { jobId, model, status: "completed", output: await this.pollJob(jobId, type, opts) };
|
|
789
|
+
} catch (err) {
|
|
790
|
+
if (err instanceof JobAbortedError || !(err instanceof NodaroError)) throw err;
|
|
791
|
+
return { jobId, model, status: "error", error: err };
|
|
792
|
+
}
|
|
793
|
+
})
|
|
794
|
+
);
|
|
795
|
+
return { batch, results };
|
|
796
|
+
}
|
|
690
797
|
/**
|
|
691
798
|
* Fan out N async runs of the same node `type` to completion concurrently —
|
|
692
799
|
* the candidate-grid path (generate N stills/clips in parallel). Each runs
|
|
@@ -702,6 +809,9 @@ var NodesResource = class {
|
|
|
702
809
|
*/
|
|
703
810
|
async runMany(type, paramsList, opts = {}) {
|
|
704
811
|
if (opts.signal?.aborted) throw new JobAbortedError();
|
|
812
|
+
if (paramsList.some((params) => isImageBatchRequest(type, params))) {
|
|
813
|
+
throw batchRefusal(`a ${type} entry with count above 1 or models makes several jobs`);
|
|
814
|
+
}
|
|
705
815
|
return Promise.all(
|
|
706
816
|
paramsList.map(async (params) => {
|
|
707
817
|
if (opts.signal?.aborted) throw new JobAbortedError();
|
|
@@ -1018,6 +1128,66 @@ var OAuthResource = class {
|
|
|
1018
1128
|
}
|
|
1019
1129
|
};
|
|
1020
1130
|
|
|
1131
|
+
// src/resources/webhooks.ts
|
|
1132
|
+
var WebhooksResource = class {
|
|
1133
|
+
constructor(client) {
|
|
1134
|
+
this.client = client;
|
|
1135
|
+
}
|
|
1136
|
+
client;
|
|
1137
|
+
/** `POST /v1/webhook-endpoints` → the endpoint and its signing secret (shown this once). */
|
|
1138
|
+
create(input) {
|
|
1139
|
+
return this.client.request("POST", "/v1/webhook-endpoints", { body: input });
|
|
1140
|
+
}
|
|
1141
|
+
/** `GET /v1/webhook-endpoints` → your endpoints, newest first. Never a secret. */
|
|
1142
|
+
list() {
|
|
1143
|
+
return this.client.request("GET", "/v1/webhook-endpoints");
|
|
1144
|
+
}
|
|
1145
|
+
/** `GET /v1/webhook-endpoints/:id`. */
|
|
1146
|
+
get(id) {
|
|
1147
|
+
return this.client.request("GET", `/v1/webhook-endpoints/${encodeURIComponent(id)}`);
|
|
1148
|
+
}
|
|
1149
|
+
/** `PATCH /v1/webhook-endpoints/:id` → change the address, events, description; switch on or off. */
|
|
1150
|
+
update(id, input) {
|
|
1151
|
+
return this.client.request("PATCH", `/v1/webhook-endpoints/${encodeURIComponent(id)}`, { body: input });
|
|
1152
|
+
}
|
|
1153
|
+
/** `DELETE /v1/webhook-endpoints/:id` → the endpoint and its delivery log. */
|
|
1154
|
+
delete(id) {
|
|
1155
|
+
return this.client.request("DELETE", `/v1/webhook-endpoints/${encodeURIComponent(id)}`);
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* `POST /v1/webhook-endpoints/:id/rotate-secret` → a new signing secret,
|
|
1159
|
+
* shown this once. It signs every delivery from now on (retries included);
|
|
1160
|
+
* the one it replaces keeps signing beside it for `gracePeriodSeconds` (a
|
|
1161
|
+
* day by default), so update your receiver within that time.
|
|
1162
|
+
*/
|
|
1163
|
+
rotateSecret(id, input = {}) {
|
|
1164
|
+
return this.client.request("POST", `/v1/webhook-endpoints/${encodeURIComponent(id)}/rotate-secret`, {
|
|
1165
|
+
body: input
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
/** `GET /v1/webhook-endpoints/:id/deliveries` → the delivery log, newest first (kept 30 days). */
|
|
1169
|
+
deliveries(id, params = {}) {
|
|
1170
|
+
return this.client.request("GET", `/v1/webhook-endpoints/${encodeURIComponent(id)}/deliveries`, {
|
|
1171
|
+
query: { status: params.status, limit: params.limit, cursor: params.cursor }
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
/** `GET /v1/webhook-endpoints/:id/deliveries/:deliveryId` → one delivery (`test()` returns its id). */
|
|
1175
|
+
getDelivery(id, deliveryId) {
|
|
1176
|
+
return this.client.request(
|
|
1177
|
+
"GET",
|
|
1178
|
+
`/v1/webhook-endpoints/${encodeURIComponent(id)}/deliveries/${encodeURIComponent(deliveryId)}`
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* `POST /v1/webhook-endpoints/:id/test` → queue a `ping` (sent within
|
|
1183
|
+
* seconds, tried once — even to an endpoint that is switched off). Follow
|
|
1184
|
+
* it with `deliveries()`.
|
|
1185
|
+
*/
|
|
1186
|
+
test(id) {
|
|
1187
|
+
return this.client.request("POST", `/v1/webhook-endpoints/${encodeURIComponent(id)}/test`);
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1021
1191
|
// src/resources/apps.ts
|
|
1022
1192
|
var AppsResource = class {
|
|
1023
1193
|
constructor(client) {
|
|
@@ -1091,7 +1261,7 @@ var CharactersResource = class {
|
|
|
1091
1261
|
* List the caller's characters. By default returns active characters only;
|
|
1092
1262
|
* pass `archived: true` to fetch soft-deleted rows for an "archive" view.
|
|
1093
1263
|
* When `projectId` is set, only characters belonging to that project are
|
|
1094
|
-
* returned.
|
|
1264
|
+
* returned; `q` keeps the ones whose name or description contains it.
|
|
1095
1265
|
*
|
|
1096
1266
|
* Cursor-paginated: a response carrying a non-null `nextCursor` means more
|
|
1097
1267
|
* rows exist. Pass it back as `cursor` to fetch the next page, and keep going
|
|
@@ -1113,6 +1283,7 @@ var CharactersResource = class {
|
|
|
1113
1283
|
const query = {};
|
|
1114
1284
|
if (params.projectId) query.projectId = params.projectId;
|
|
1115
1285
|
if (params.archived) query.archived = "true";
|
|
1286
|
+
if (params.q) query.q = params.q;
|
|
1116
1287
|
if (params.limit !== void 0) query.limit = String(params.limit);
|
|
1117
1288
|
if (params.cursor) query.cursor = params.cursor;
|
|
1118
1289
|
return this.client.request("GET", "/v1/characters", { query });
|
|
@@ -1245,6 +1416,41 @@ var CharactersResource = class {
|
|
|
1245
1416
|
`/v1/characters/${encodeURIComponent(id)}/llm-caption`
|
|
1246
1417
|
);
|
|
1247
1418
|
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Start training a high-fidelity model of the character on its own images
|
|
1421
|
+
* (Nodaro Cloud only — other editions answer `NotFoundError`). Reserves the
|
|
1422
|
+
* training price up front and refunds it if the training fails or is
|
|
1423
|
+
* cancelled. Resolves at once (202); a training takes about 15 minutes —
|
|
1424
|
+
* follow it with {@link trainingStatus}. Once it succeeds, a workflow's
|
|
1425
|
+
* Generate Image node whose prompt @-mentions this one character runs on the
|
|
1426
|
+
* trained model.
|
|
1427
|
+
*
|
|
1428
|
+
* An OAuth app token needs the `assets:write` scope (`ForbiddenError`
|
|
1429
|
+
* otherwise, before anything is reserved); a session or personal API key
|
|
1430
|
+
* needs none. Refusals are `NodaroError`s whose `code` is a
|
|
1431
|
+
* {@link TrainCharacterRefusalCode}; rate-limited to 3 calls a minute.
|
|
1432
|
+
*/
|
|
1433
|
+
train(id, input = {}) {
|
|
1434
|
+
return this.client.request("POST", `/v1/characters/${encodeURIComponent(id)}/train`, { body: input });
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* The character's training record (Nodaro Cloud only). Poll it while
|
|
1438
|
+
* `status` is `queued` or `training` — every 10 s or so is plenty.
|
|
1439
|
+
*/
|
|
1440
|
+
trainingStatus(id) {
|
|
1441
|
+
return this.client.request("GET", `/v1/characters/${encodeURIComponent(id)}/training`);
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Remove the character's trained model (Nodaro Cloud only): cancels a
|
|
1445
|
+
* training in flight and refunds its reservation, deletes the model, and
|
|
1446
|
+
* clears the training record (`trainingStatus` then reads `untrained`).
|
|
1447
|
+
* Generations go back to reference images. Cannot be undone — a new model
|
|
1448
|
+
* means a new, paid training. An OAuth app token needs the `assets:write`
|
|
1449
|
+
* scope (`ForbiddenError` otherwise), like deleting the character.
|
|
1450
|
+
*/
|
|
1451
|
+
async deleteLora(id) {
|
|
1452
|
+
await this.client.request("DELETE", `/v1/characters/${encodeURIComponent(id)}/lora`);
|
|
1453
|
+
}
|
|
1248
1454
|
};
|
|
1249
1455
|
function buildPersonSeedPrompt(value) {
|
|
1250
1456
|
return buildPersonHints(value).join(", ");
|
|
@@ -1256,11 +1462,13 @@ var LocationsResource = class {
|
|
|
1256
1462
|
client;
|
|
1257
1463
|
/**
|
|
1258
1464
|
* List the caller's locations. By default returns active locations only;
|
|
1259
|
-
* pass `archived: true` to fetch soft-deleted rows for an "archive" view
|
|
1465
|
+
* pass `archived: true` to fetch soft-deleted rows for an "archive" view,
|
|
1466
|
+
* and `q` to keep the ones whose name or description contains it.
|
|
1260
1467
|
*/
|
|
1261
1468
|
list(params = {}) {
|
|
1262
1469
|
const query = {};
|
|
1263
1470
|
if (params.archived) query.archived = "true";
|
|
1471
|
+
if (params.q) query.q = params.q;
|
|
1264
1472
|
if (params.limit !== void 0) query.limit = String(params.limit);
|
|
1265
1473
|
if (params.cursor) query.cursor = params.cursor;
|
|
1266
1474
|
return this.client.request("GET", "/v1/locations", { query });
|
|
@@ -1434,12 +1642,14 @@ var ObjectsResource = class {
|
|
|
1434
1642
|
/**
|
|
1435
1643
|
* List the caller's objects. By default returns active objects only;
|
|
1436
1644
|
* pass `archived: true` to fetch soft-deleted rows for an "archive" view.
|
|
1437
|
-
* Optional `projectId` scopes the result to a single project
|
|
1645
|
+
* Optional `projectId` scopes the result to a single project; `q` keeps the
|
|
1646
|
+
* objects whose name or description contains it.
|
|
1438
1647
|
*/
|
|
1439
1648
|
list(params = {}) {
|
|
1440
1649
|
const query = {};
|
|
1441
1650
|
if (params.archived) query.archived = "true";
|
|
1442
1651
|
if (params.projectId) query.projectId = params.projectId;
|
|
1652
|
+
if (params.q) query.q = params.q;
|
|
1443
1653
|
if (params.limit !== void 0) query.limit = String(params.limit);
|
|
1444
1654
|
if (params.cursor) query.cursor = params.cursor;
|
|
1445
1655
|
return this.client.request("GET", "/v1/objects", { query });
|
|
@@ -1620,12 +1830,14 @@ var CreaturesResource = class {
|
|
|
1620
1830
|
/**
|
|
1621
1831
|
* List the caller's creatures. By default returns active creatures only;
|
|
1622
1832
|
* pass `archived: true` to fetch soft-deleted rows for an "archive" view.
|
|
1623
|
-
* Optional `projectId` scopes the result to a single project
|
|
1833
|
+
* Optional `projectId` scopes the result to a single project; `q` keeps the
|
|
1834
|
+
* creatures whose name or description contains it.
|
|
1624
1835
|
*/
|
|
1625
1836
|
list(params = {}) {
|
|
1626
1837
|
const query = {};
|
|
1627
1838
|
if (params.archived) query.archived = "true";
|
|
1628
1839
|
if (params.projectId) query.projectId = params.projectId;
|
|
1840
|
+
if (params.q) query.q = params.q;
|
|
1629
1841
|
if (params.limit !== void 0) query.limit = String(params.limit);
|
|
1630
1842
|
if (params.cursor) query.cursor = params.cursor;
|
|
1631
1843
|
return this.client.request("GET", "/v1/creatures", { query });
|
|
@@ -2749,6 +2961,53 @@ var CreditsResource = class {
|
|
|
2749
2961
|
// a consumer needs it, shaped to that need.
|
|
2750
2962
|
};
|
|
2751
2963
|
|
|
2964
|
+
// src/resources/billing.ts
|
|
2965
|
+
var BillingResource = class {
|
|
2966
|
+
constructor(client) {
|
|
2967
|
+
this.client = client;
|
|
2968
|
+
}
|
|
2969
|
+
client;
|
|
2970
|
+
/**
|
|
2971
|
+
* `GET /v1/billing/plans` → the plans and credit packs on sale, with their
|
|
2972
|
+
* prices. Public: works with no credential, the same for every caller.
|
|
2973
|
+
* Nodaro Cloud only — a self-hosted instance answers 404. Throws on `403
|
|
2974
|
+
* self_serve_disabled` (this deployment does not sell to its users) and on
|
|
2975
|
+
* `503` (`stripe_not_configured`, or prices briefly unavailable).
|
|
2976
|
+
*/
|
|
2977
|
+
plans() {
|
|
2978
|
+
return this.client.request("GET", "/v1/billing/plans");
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* `GET /v1/billing/subscription` → the account's latest subscription
|
|
2982
|
+
* (`null` when it never subscribed) and the tier its entitlements follow.
|
|
2983
|
+
* Match `effectiveTier` to a {@link BillingPlan} `id` to mark the current
|
|
2984
|
+
* plan (no match: pay-as-you-go, or a tier not on sale). Needs a signed-in
|
|
2985
|
+
* session, a personal API token without a workflow limit, or an app's OAuth
|
|
2986
|
+
* token. Nodaro Cloud only — a self-hosted instance answers 404.
|
|
2987
|
+
*/
|
|
2988
|
+
subscription() {
|
|
2989
|
+
return this.client.request("GET", "/v1/billing/subscription");
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* `GET /v1/billing/surface` → how this deployment bills: whether it meters
|
|
2993
|
+
* at all (`providerId !== "none"`) and in which unit costs display. Public,
|
|
2994
|
+
* every edition.
|
|
2995
|
+
*/
|
|
2996
|
+
async surface() {
|
|
2997
|
+
const res = await this.client.request("GET", "/v1/billing/surface");
|
|
2998
|
+
return res.data;
|
|
2999
|
+
}
|
|
3000
|
+
/**
|
|
3001
|
+
* `GET /v1/billing/account` → the caller's plan and balance as the
|
|
3002
|
+
* deployment's billing provider reports them, in its display unit. `null`
|
|
3003
|
+
* when the provider could not answer — not a zero balance. Every edition.
|
|
3004
|
+
*/
|
|
3005
|
+
async account() {
|
|
3006
|
+
const res = await this.client.request("GET", "/v1/billing/account");
|
|
3007
|
+
return res.data;
|
|
3008
|
+
}
|
|
3009
|
+
};
|
|
3010
|
+
|
|
2752
3011
|
// src/resources/uploads.ts
|
|
2753
3012
|
var UploadsResource = class {
|
|
2754
3013
|
constructor(client) {
|
|
@@ -2784,14 +3043,15 @@ var LibraryResource = class {
|
|
|
2784
3043
|
/**
|
|
2785
3044
|
* `GET /v1/library` → a page of the caller's media assets (newest first) plus
|
|
2786
3045
|
* a `nextCursor`. Pass the returned `nextCursor` back as `cursor` for the next
|
|
2787
|
-
* page. Filter by `type` and
|
|
2788
|
-
* full Storage set (uploads + generations), the default only
|
|
2789
|
-
* shared items.
|
|
3046
|
+
* page. Filter by `type` and search the filename with `q`; `owned: true`
|
|
3047
|
+
* returns the full Storage set (uploads + generations), the default only
|
|
3048
|
+
* library-saved + shared items.
|
|
2790
3049
|
*/
|
|
2791
3050
|
list(params = {}) {
|
|
2792
3051
|
const qs = new URLSearchParams();
|
|
2793
3052
|
if (params.type) qs.set("type", params.type);
|
|
2794
|
-
if (params.
|
|
3053
|
+
if (params.q) qs.set("q", params.q);
|
|
3054
|
+
else if (params.search) qs.set("search", params.search);
|
|
2795
3055
|
if (params.limit !== void 0) qs.set("limit", String(params.limit));
|
|
2796
3056
|
if (params.cursor) qs.set("cursor", params.cursor);
|
|
2797
3057
|
if (params.owned !== void 0) qs.set("owned", String(params.owned));
|
|
@@ -2801,6 +3061,46 @@ var LibraryResource = class {
|
|
|
2801
3061
|
}
|
|
2802
3062
|
};
|
|
2803
3063
|
|
|
3064
|
+
// src/resources/gallery.ts
|
|
3065
|
+
var GalleryResource = class {
|
|
3066
|
+
constructor(client) {
|
|
3067
|
+
this.client = client;
|
|
3068
|
+
}
|
|
3069
|
+
client;
|
|
3070
|
+
/**
|
|
3071
|
+
* `GET /v1/gallery` → a page of the public gallery, newest first (no auth
|
|
3072
|
+
* needed), or one person's work with `userId`.
|
|
3073
|
+
*/
|
|
3074
|
+
browse(params = {}) {
|
|
3075
|
+
return this.client.request("GET", "/v1/gallery", {
|
|
3076
|
+
query: {
|
|
3077
|
+
type: params.type,
|
|
3078
|
+
userId: params.userId,
|
|
3079
|
+
favoritesOnly: params.favoritesOnly,
|
|
3080
|
+
limit: params.limit,
|
|
3081
|
+
cursor: params.cursor
|
|
3082
|
+
}
|
|
3083
|
+
});
|
|
3084
|
+
}
|
|
3085
|
+
/**
|
|
3086
|
+
* Favorite a job, or un-favorite it when it already is
|
|
3087
|
+
* (`POST /v1/gallery/favorite`). You can favorite your own jobs and public
|
|
3088
|
+
* ones; another person's private job is a `NotFoundError`. OAuth apps need
|
|
3089
|
+
* the `assets:write` scope.
|
|
3090
|
+
*/
|
|
3091
|
+
toggleFavorite(jobId) {
|
|
3092
|
+
return this.client.request("POST", "/v1/gallery/favorite", { body: { jobId } });
|
|
3093
|
+
}
|
|
3094
|
+
/**
|
|
3095
|
+
* Your favorited job ids, newest first (`GET /v1/gallery/favorites`). OAuth
|
|
3096
|
+
* apps need the `assets:read` or `jobs:read` scope.
|
|
3097
|
+
*/
|
|
3098
|
+
async favorites() {
|
|
3099
|
+
const res = await this.client.request("GET", "/v1/gallery/favorites");
|
|
3100
|
+
return res.data;
|
|
3101
|
+
}
|
|
3102
|
+
};
|
|
3103
|
+
|
|
2804
3104
|
// src/resources/node-presets.ts
|
|
2805
3105
|
var PresetsResource = class {
|
|
2806
3106
|
constructor(client) {
|
|
@@ -2836,6 +3136,76 @@ var PresetsResource = class {
|
|
|
2836
3136
|
`/v1/node-presets/factory?nodeType=${encodeURIComponent(nodeType)}`
|
|
2837
3137
|
);
|
|
2838
3138
|
}
|
|
3139
|
+
/** `POST /v1/node-presets` → the new preset. */
|
|
3140
|
+
async create(input) {
|
|
3141
|
+
const res = await this.client.request("POST", "/v1/node-presets", { body: input });
|
|
3142
|
+
return res.data;
|
|
3143
|
+
}
|
|
3144
|
+
/**
|
|
3145
|
+
* `PATCH /v1/node-presets/:id` → the updated preset. Pass the `updatedAt` you
|
|
3146
|
+
* read as `expectedUpdatedAt` to refuse (409 `conflict`) a change made
|
|
3147
|
+
* elsewhere since.
|
|
3148
|
+
*/
|
|
3149
|
+
async update(id, patch, opts = {}) {
|
|
3150
|
+
const body = opts.expectedUpdatedAt ? { ...patch, expectedUpdatedAt: opts.expectedUpdatedAt } : patch;
|
|
3151
|
+
const res = await this.client.request("PATCH", `/v1/node-presets/${encodeURIComponent(id)}`, { body });
|
|
3152
|
+
return res.data;
|
|
3153
|
+
}
|
|
3154
|
+
/** `DELETE /v1/node-presets/:id` (404 when it is not yours). `expectedUpdatedAt` as on `update`. */
|
|
3155
|
+
async delete(id, opts = {}) {
|
|
3156
|
+
const res = await this.client.request("DELETE", `/v1/node-presets/${encodeURIComponent(id)}`, {
|
|
3157
|
+
query: { expectedUpdatedAt: opts.expectedUpdatedAt }
|
|
3158
|
+
});
|
|
3159
|
+
return res.data;
|
|
3160
|
+
}
|
|
3161
|
+
/**
|
|
3162
|
+
* `POST /v1/node-presets/import` — up to 500 presets in one call. A name you
|
|
3163
|
+
* already use gets an "(imported N)" suffix; a preset whose data is empty
|
|
3164
|
+
* after stripping runtime keys is skipped.
|
|
3165
|
+
*/
|
|
3166
|
+
async import(presets) {
|
|
3167
|
+
const res = await this.client.request("POST", "/v1/node-presets/import", { body: { presets } });
|
|
3168
|
+
return res.data;
|
|
3169
|
+
}
|
|
3170
|
+
/** `POST /v1/node-presets/reorder` — positions (and group membership) for many presets and groups at once. */
|
|
3171
|
+
async reorder(input) {
|
|
3172
|
+
const res = await this.client.request("POST", "/v1/node-presets/reorder", { body: input });
|
|
3173
|
+
return res.data;
|
|
3174
|
+
}
|
|
3175
|
+
/** `GET /v1/node-presets/favorites` → your favorite preset ids for `nodeType`, newest first. */
|
|
3176
|
+
async favorites(nodeType) {
|
|
3177
|
+
const res = await this.client.request("GET", "/v1/node-presets/favorites", { query: { nodeType } });
|
|
3178
|
+
return res.data;
|
|
3179
|
+
}
|
|
3180
|
+
/** `POST /v1/node-presets/favorites` — idempotent. */
|
|
3181
|
+
async addFavorite(input) {
|
|
3182
|
+
const res = await this.client.request("POST", "/v1/node-presets/favorites", { body: input });
|
|
3183
|
+
return res.data;
|
|
3184
|
+
}
|
|
3185
|
+
/** `DELETE /v1/node-presets/favorites` — idempotent. */
|
|
3186
|
+
async removeFavorite(input) {
|
|
3187
|
+
const res = await this.client.request("DELETE", "/v1/node-presets/favorites", {
|
|
3188
|
+
query: { nodeType: input.nodeType, presetId: input.presetId }
|
|
3189
|
+
});
|
|
3190
|
+
return res.data;
|
|
3191
|
+
}
|
|
3192
|
+
/** `POST /v1/node-preset-groups` → the new folder or section. */
|
|
3193
|
+
async createGroup(input) {
|
|
3194
|
+
const res = await this.client.request("POST", "/v1/node-preset-groups", { body: input });
|
|
3195
|
+
return res.data;
|
|
3196
|
+
}
|
|
3197
|
+
/** `PATCH /v1/node-preset-groups/:id` → the renamed or moved group. */
|
|
3198
|
+
async updateGroup(id, patch) {
|
|
3199
|
+
const res = await this.client.request("PATCH", `/v1/node-preset-groups/${encodeURIComponent(id)}`, {
|
|
3200
|
+
body: patch
|
|
3201
|
+
});
|
|
3202
|
+
return res.data;
|
|
3203
|
+
}
|
|
3204
|
+
/** `DELETE /v1/node-preset-groups/:id` — its presets move out of the group; none are deleted. */
|
|
3205
|
+
async deleteGroup(id) {
|
|
3206
|
+
const res = await this.client.request("DELETE", `/v1/node-preset-groups/${encodeURIComponent(id)}`);
|
|
3207
|
+
return res.data;
|
|
3208
|
+
}
|
|
2839
3209
|
};
|
|
2840
3210
|
|
|
2841
3211
|
// src/resources/saved-posts.ts
|
|
@@ -2976,6 +3346,68 @@ var CollectionsResource = class {
|
|
|
2976
3346
|
}
|
|
2977
3347
|
};
|
|
2978
3348
|
|
|
3349
|
+
// src/resources/palettes.ts
|
|
3350
|
+
var PALETTE_DIRECTION_PREFIX = "palette:";
|
|
3351
|
+
function paletteDirectionId(id) {
|
|
3352
|
+
return `${PALETTE_DIRECTION_PREFIX}${id}`;
|
|
3353
|
+
}
|
|
3354
|
+
var PalettesResource = class {
|
|
3355
|
+
constructor(client) {
|
|
3356
|
+
this.client = client;
|
|
3357
|
+
}
|
|
3358
|
+
client;
|
|
3359
|
+
/** Your palettes, newest first. Page with `nextCursor`. */
|
|
3360
|
+
list(params = {}) {
|
|
3361
|
+
return this.client.request("GET", "/v1/palettes", {
|
|
3362
|
+
query: { limit: params.limit, cursor: params.cursor }
|
|
3363
|
+
});
|
|
3364
|
+
}
|
|
3365
|
+
get(id) {
|
|
3366
|
+
return this.client.request("GET", `/v1/palettes/${encodeURIComponent(id)}`);
|
|
3367
|
+
}
|
|
3368
|
+
create(input) {
|
|
3369
|
+
return this.client.request("POST", "/v1/palettes", { body: input });
|
|
3370
|
+
}
|
|
3371
|
+
update(id, input) {
|
|
3372
|
+
return this.client.request("PATCH", `/v1/palettes/${encodeURIComponent(id)}`, { body: input });
|
|
3373
|
+
}
|
|
3374
|
+
/** A run that still names the palette answers 404 `direction_not_found`. */
|
|
3375
|
+
async delete(id) {
|
|
3376
|
+
await this.client.request("DELETE", `/v1/palettes/${encodeURIComponent(id)}`);
|
|
3377
|
+
}
|
|
3378
|
+
};
|
|
3379
|
+
|
|
3380
|
+
// src/resources/moodboards.ts
|
|
3381
|
+
var MOODBOARD_DIRECTION_PREFIX = "moodboard:";
|
|
3382
|
+
function moodboardDirectionId(id) {
|
|
3383
|
+
return `${MOODBOARD_DIRECTION_PREFIX}${id}`;
|
|
3384
|
+
}
|
|
3385
|
+
var MoodboardsResource = class {
|
|
3386
|
+
constructor(client) {
|
|
3387
|
+
this.client = client;
|
|
3388
|
+
}
|
|
3389
|
+
client;
|
|
3390
|
+
/** Your moodboards, newest first. Page with `nextCursor`. */
|
|
3391
|
+
list(params = {}) {
|
|
3392
|
+
return this.client.request("GET", "/v1/moodboards", {
|
|
3393
|
+
query: { limit: params.limit, cursor: params.cursor }
|
|
3394
|
+
});
|
|
3395
|
+
}
|
|
3396
|
+
get(id) {
|
|
3397
|
+
return this.client.request("GET", `/v1/moodboards/${encodeURIComponent(id)}`);
|
|
3398
|
+
}
|
|
3399
|
+
create(input) {
|
|
3400
|
+
return this.client.request("POST", "/v1/moodboards", { body: input });
|
|
3401
|
+
}
|
|
3402
|
+
update(id, input) {
|
|
3403
|
+
return this.client.request("PATCH", `/v1/moodboards/${encodeURIComponent(id)}`, { body: input });
|
|
3404
|
+
}
|
|
3405
|
+
/** Deletes the moodboard only; the images it links to are untouched. */
|
|
3406
|
+
async delete(id) {
|
|
3407
|
+
await this.client.request("DELETE", `/v1/moodboards/${encodeURIComponent(id)}`);
|
|
3408
|
+
}
|
|
3409
|
+
};
|
|
3410
|
+
|
|
2979
3411
|
// src/resources/competitors.ts
|
|
2980
3412
|
var CompetitorsResource = class {
|
|
2981
3413
|
constructor(client) {
|
|
@@ -4236,7 +4668,7 @@ var EditResource = class {
|
|
|
4236
4668
|
return remapTranscriptThroughEdl(edl, transcript);
|
|
4237
4669
|
}
|
|
4238
4670
|
};
|
|
4239
|
-
var SDK_VERSION = "2.
|
|
4671
|
+
var SDK_VERSION = "2.29.0" ;
|
|
4240
4672
|
var CLIENT_HEADER = "X-Nodaro-Client";
|
|
4241
4673
|
var isBrowser = () => typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
4242
4674
|
var NodaroClient = class _NodaroClient {
|
|
@@ -4268,6 +4700,8 @@ var NodaroClient = class _NodaroClient {
|
|
|
4268
4700
|
scene3d;
|
|
4269
4701
|
developerApps;
|
|
4270
4702
|
oauth;
|
|
4703
|
+
/** Outbound webhooks: endpoints that receive your job.completed / job.failed. */
|
|
4704
|
+
webhooks;
|
|
4271
4705
|
apps;
|
|
4272
4706
|
characters;
|
|
4273
4707
|
locations;
|
|
@@ -4281,11 +4715,19 @@ var NodaroClient = class _NodaroClient {
|
|
|
4281
4715
|
media;
|
|
4282
4716
|
audio;
|
|
4283
4717
|
credits;
|
|
4718
|
+
/** Plans and prices, the subscription, and how this deployment bills. */
|
|
4719
|
+
billing;
|
|
4284
4720
|
uploads;
|
|
4285
4721
|
library;
|
|
4722
|
+
/** The public gallery and your favorites. */
|
|
4723
|
+
gallery;
|
|
4286
4724
|
presets;
|
|
4287
4725
|
savedPosts;
|
|
4288
4726
|
collections;
|
|
4727
|
+
/** Your colour palettes — a generate-image direction (`direction.colorLook: paletteDirectionId(id)`). */
|
|
4728
|
+
palettes;
|
|
4729
|
+
/** Your moodboards — a generate-image direction (`direction.style: moodboardDirectionId(id)`). */
|
|
4730
|
+
moodboards;
|
|
4289
4731
|
competitors;
|
|
4290
4732
|
pickerCatalogs;
|
|
4291
4733
|
catalogs;
|
|
@@ -4320,6 +4762,7 @@ var NodaroClient = class _NodaroClient {
|
|
|
4320
4762
|
this.scene3d = new Scene3DResource(this);
|
|
4321
4763
|
this.developerApps = new DeveloperAppsResource(this);
|
|
4322
4764
|
this.oauth = new OAuthResource(this);
|
|
4765
|
+
this.webhooks = new WebhooksResource(this);
|
|
4323
4766
|
this.apps = new AppsResource(this);
|
|
4324
4767
|
this.characters = new CharactersResource(this);
|
|
4325
4768
|
this.locations = new LocationsResource(this);
|
|
@@ -4333,11 +4776,15 @@ var NodaroClient = class _NodaroClient {
|
|
|
4333
4776
|
this.media = new MediaResource(this);
|
|
4334
4777
|
this.audio = new AudioResource(this);
|
|
4335
4778
|
this.credits = new CreditsResource(this);
|
|
4779
|
+
this.billing = new BillingResource(this);
|
|
4336
4780
|
this.uploads = new UploadsResource(this);
|
|
4337
4781
|
this.library = new LibraryResource(this);
|
|
4782
|
+
this.gallery = new GalleryResource(this);
|
|
4338
4783
|
this.presets = new PresetsResource(this);
|
|
4339
4784
|
this.savedPosts = new SavedPostsResource(this);
|
|
4340
4785
|
this.collections = new CollectionsResource(this);
|
|
4786
|
+
this.palettes = new PalettesResource(this);
|
|
4787
|
+
this.moodboards = new MoodboardsResource(this);
|
|
4341
4788
|
this.competitors = new CompetitorsResource(this);
|
|
4342
4789
|
this.pickerCatalogs = new PickerCatalogsResource(this);
|
|
4343
4790
|
this.catalogs = new CatalogsResource(this);
|
|
@@ -4462,6 +4909,32 @@ var NodaroClient = class _NodaroClient {
|
|
|
4462
4909
|
const res = await this.request("GET", "/v1/me");
|
|
4463
4910
|
return res.data;
|
|
4464
4911
|
}
|
|
4912
|
+
/**
|
|
4913
|
+
* `GET /v1/me/connected-apps` → every app with access to the signed-in
|
|
4914
|
+
* user's account (see {@link ConnectedApp}), newest grant first.
|
|
4915
|
+
*
|
|
4916
|
+
* **A signed-in browser session only.** The route answers a Supabase
|
|
4917
|
+
* session token (`supabaseAuth(…)`, `createSharedSupabaseClient`, or a
|
|
4918
|
+
* `StaticTokenAuth` holding one) and refuses every other credential with
|
|
4919
|
+
* `UnauthorizedError` ("Sign in required"): an app token or an API key must
|
|
4920
|
+
* not list the grants it lives under. Build your "Connected apps" settings
|
|
4921
|
+
* screen on this; a server-side integration cannot call it.
|
|
4922
|
+
*/
|
|
4923
|
+
async connectedApps() {
|
|
4924
|
+
const res = await this.request("GET", "/v1/me/connected-apps");
|
|
4925
|
+
return res.apps;
|
|
4926
|
+
}
|
|
4927
|
+
/**
|
|
4928
|
+
* `POST /v1/me/connected-apps/:authorizationId/revoke` — ends an app's
|
|
4929
|
+
* access: the grant and every token issued under it, at once (the app's next
|
|
4930
|
+
* call is a 401). Resolves when done.
|
|
4931
|
+
*
|
|
4932
|
+
* A signed-in browser session only, like {@link connectedApps}. Throws
|
|
4933
|
+
* `NotFoundError` when the grant is not the user's or is already revoked.
|
|
4934
|
+
*/
|
|
4935
|
+
async revokeConnectedApp(authorizationId) {
|
|
4936
|
+
await this.request("POST", `/v1/me/connected-apps/${encodeURIComponent(authorizationId)}/revoke`);
|
|
4937
|
+
}
|
|
4465
4938
|
buildUrl(path2, query) {
|
|
4466
4939
|
const base = this.baseUrl || (typeof window !== "undefined" ? window.location.origin : "http://placeholder");
|
|
4467
4940
|
const url = new URL(path2, base);
|
|
@@ -4508,6 +4981,96 @@ function supabaseAuth(supabase) {
|
|
|
4508
4981
|
};
|
|
4509
4982
|
}
|
|
4510
4983
|
|
|
4511
|
-
|
|
4984
|
+
// src/webhook-signature.ts
|
|
4985
|
+
var WebhookSignatureError = class extends Error {
|
|
4986
|
+
code;
|
|
4987
|
+
constructor(code, message) {
|
|
4988
|
+
super(message);
|
|
4989
|
+
this.name = "WebhookSignatureError";
|
|
4990
|
+
this.code = code;
|
|
4991
|
+
}
|
|
4992
|
+
};
|
|
4993
|
+
var DEFAULT_TOLERANCE_SECONDS = 300;
|
|
4994
|
+
var HEX_SIGNATURE = /^[0-9a-f]{64}$/;
|
|
4995
|
+
function toBytes(payload) {
|
|
4996
|
+
if (typeof payload === "string") return new TextEncoder().encode(payload);
|
|
4997
|
+
if (payload instanceof Uint8Array) return payload;
|
|
4998
|
+
return new Uint8Array(payload);
|
|
4999
|
+
}
|
|
5000
|
+
function hexToBytes(hex) {
|
|
5001
|
+
const out = new Uint8Array(hex.length / 2);
|
|
5002
|
+
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
5003
|
+
return out;
|
|
5004
|
+
}
|
|
5005
|
+
function constantTimeEqual(a, b) {
|
|
5006
|
+
if (a.length !== b.length) return false;
|
|
5007
|
+
let diff = 0;
|
|
5008
|
+
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
5009
|
+
return diff === 0;
|
|
5010
|
+
}
|
|
5011
|
+
function parseHeader(raw) {
|
|
5012
|
+
let timestamp = null;
|
|
5013
|
+
const signatures = [];
|
|
5014
|
+
for (const part of raw.split(",")) {
|
|
5015
|
+
const eq = part.indexOf("=");
|
|
5016
|
+
if (eq <= 0) continue;
|
|
5017
|
+
const key = part.slice(0, eq).trim();
|
|
5018
|
+
const value = part.slice(eq + 1).trim();
|
|
5019
|
+
if (key === "t") {
|
|
5020
|
+
if (!/^\d{1,15}$/.test(value)) {
|
|
5021
|
+
throw new WebhookSignatureError("malformed_header", "Nodaro-Signature has no valid timestamp (t=)");
|
|
5022
|
+
}
|
|
5023
|
+
timestamp = Number(value);
|
|
5024
|
+
} else if (key === "v1") {
|
|
5025
|
+
signatures.push(value.toLowerCase());
|
|
5026
|
+
}
|
|
5027
|
+
}
|
|
5028
|
+
if (timestamp === null) throw new WebhookSignatureError("malformed_header", "Nodaro-Signature has no timestamp (t=)");
|
|
5029
|
+
if (signatures.length === 0) throw new WebhookSignatureError("malformed_header", "Nodaro-Signature has no v1 signature");
|
|
5030
|
+
return { timestamp, signatures };
|
|
5031
|
+
}
|
|
5032
|
+
async function verifyWebhookSignature(options) {
|
|
5033
|
+
const { payload, secret } = options;
|
|
5034
|
+
const tolerance = options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
|
|
5035
|
+
if (typeof tolerance !== "number" || !Number.isFinite(tolerance) || tolerance < 0) {
|
|
5036
|
+
throw new RangeError("toleranceSeconds must be a finite number of seconds, 0 or more");
|
|
5037
|
+
}
|
|
5038
|
+
if (!secret) throw new WebhookSignatureError("missing_secret", "No signing secret was given");
|
|
5039
|
+
const header = typeof options.header === "string" ? options.header : options.header?.[0];
|
|
5040
|
+
if (!header || !header.trim()) throw new WebhookSignatureError("missing_header", "The request has no Nodaro-Signature header");
|
|
5041
|
+
const { timestamp, signatures } = parseHeader(header);
|
|
5042
|
+
const subtle = globalThis.crypto?.subtle;
|
|
5043
|
+
if (!subtle) throw new WebhookSignatureError("crypto_unavailable", "Web Crypto (crypto.subtle) is not available in this runtime");
|
|
5044
|
+
const body = toBytes(payload);
|
|
5045
|
+
const prefix = new TextEncoder().encode(`${timestamp}.`);
|
|
5046
|
+
const message = new Uint8Array(prefix.length + body.length);
|
|
5047
|
+
message.set(prefix, 0);
|
|
5048
|
+
message.set(body, prefix.length);
|
|
5049
|
+
const key = await subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
5050
|
+
const expected = new Uint8Array(await subtle.sign("HMAC", key, message));
|
|
5051
|
+
let matched = false;
|
|
5052
|
+
for (const candidate of signatures) {
|
|
5053
|
+
const wellFormed = HEX_SIGNATURE.test(candidate);
|
|
5054
|
+
const bytes = wellFormed ? hexToBytes(candidate) : new Uint8Array(expected.length);
|
|
5055
|
+
if (constantTimeEqual(bytes, expected) && wellFormed) matched = true;
|
|
5056
|
+
}
|
|
5057
|
+
if (!matched) throw new WebhookSignatureError("signature_mismatch", "The Nodaro-Signature does not match the body");
|
|
5058
|
+
if (tolerance > 0) {
|
|
5059
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
5060
|
+
if (Math.abs(now - timestamp) > tolerance) {
|
|
5061
|
+
throw new WebhookSignatureError(
|
|
5062
|
+
"timestamp_outside_tolerance",
|
|
5063
|
+
`The signature's timestamp is more than ${tolerance} seconds from now`
|
|
5064
|
+
);
|
|
5065
|
+
}
|
|
5066
|
+
}
|
|
5067
|
+
try {
|
|
5068
|
+
return JSON.parse(new TextDecoder().decode(body));
|
|
5069
|
+
} catch {
|
|
5070
|
+
throw new WebhookSignatureError("invalid_payload", "The signed body is not JSON");
|
|
5071
|
+
}
|
|
5072
|
+
}
|
|
5073
|
+
|
|
5074
|
+
export { AppsResource, AudioResource, BillingResource, CREATURE_ASSET_TYPES, CallbackAuth, CatalogsResource, CharactersResource, CollectionsResource, CommunityResource, CompetitorsResource, ContinuationRefusedError, CopilotResource, CreaturesResource, CreditsResource, DeveloperAppsResource, EditResource, ExecutionsResource, ForbiddenError, GalleryResource, InsufficientCreditsError, JobAbortedError, JobBlockedError, JobFailedError, JobHeldError, JobTimeoutError, JobsResource, LibraryResource, LlmResource, LocationsResource, MOODBOARD_DIRECTION_PREFIX, MediaResource, ModelsResource, MoodboardsResource, NodaroClient, NodaroError, NodesResource, NotFoundError, OAuthResource, ObjectsResource, OrganizationsResource, PALETTE_DIRECTION_PREFIX, PalettesResource, PickerCatalogsResource, PipelinesResource, PresetsResource, ProjectsResource, PromptHelperResource, RateLimitedError, RecastResource, ReduceResource, SCENE3D_DELIVERY_ASSET_USAGE, SavedPostsResource, Scene3DResource, SessionsResource, ShotsResource, StaticTokenAuth, StorageExceededError, StudioOpError, StudioPreviewAppliedError, StudioPreviewUnavailable, StudioProductionsResource, StudioResource, TemplatesResource, TutorialsResource, UnauthorizedError, UploadsResource, VideoProResource, VoicesResource, WebhookSignatureError, WebhooksResource, WorkflowConflictError, WorkflowsResource, WorkspacesResource, buildPersonSeedPrompt, createClient, isStudioGenerateEstimate, moodboardDirectionId, paletteDirectionId, supabaseAuth, throwFromResponse, verifyWebhookSignature };
|
|
4512
5075
|
//# sourceMappingURL=index.js.map
|
|
4513
5076
|
//# sourceMappingURL=index.js.map
|