@nitida/sdk 0.36.1 → 0.36.2
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 +19 -9
- package/dist/index.d.ts +3 -0
- package/dist/index.js +16 -13
- package/dist/index.js.map +1 -1
- package/dist/server.js +16 -13
- package/dist/server.js.map +1 -1
- package/dist/web.js +23 -13
- package/dist/web.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +23 -12
- package/src/web/index.ts +10 -0
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ slots and reads assets — your bundle stays a few KB.
|
|
|
69
69
|
|
|
70
70
|
**Never ship the API key to the browser.** Whichever subpath you import,
|
|
71
71
|
the long-lived `amk_rt_*` key lives on the server. Browser flows hit a BFF
|
|
72
|
-
route that proxies to
|
|
72
|
+
route that proxies to the nitida API with the real key.
|
|
73
73
|
|
|
74
74
|
## Which subpath do I import from?
|
|
75
75
|
|
|
@@ -128,11 +128,11 @@ loud — same pattern as `@vercel/blob/client`, `better-auth/client`, AI SDK's
|
|
|
128
128
|
is `Omit<NitidaClientOptions, "apiKey" | "signingKey">`. Passing `apiKey`
|
|
129
129
|
won't compile, period.
|
|
130
130
|
|
|
131
|
-
## BFF-proxy mode (browser → your route →
|
|
131
|
+
## BFF-proxy mode (browser → your route → nitida)
|
|
132
132
|
|
|
133
133
|
Browser code constructs the client against a **relative endpoint** that
|
|
134
134
|
points at your own route handler. The handler attaches the real bearer
|
|
135
|
-
token and forwards to
|
|
135
|
+
token and forwards to the nitida API. Same pattern Vercel Blob uses for
|
|
136
136
|
`@vercel/blob/client.upload`.
|
|
137
137
|
|
|
138
138
|
### Next.js — client component
|
|
@@ -206,7 +206,7 @@ import { NitidaClient } from "@nitida/sdk/web"; // same browser-safe type
|
|
|
206
206
|
|
|
207
207
|
### SSG storefront (build-time)
|
|
208
208
|
|
|
209
|
-
Use `/server` at build time (Node/Bun) with the absolute
|
|
209
|
+
Use `/server` at build time (Node/Bun) with the absolute nitida API URL —
|
|
210
210
|
no proxy needed because keys never reach the browser bundle. Static
|
|
211
211
|
HTML output references the CDN directly.
|
|
212
212
|
|
|
@@ -400,9 +400,16 @@ import {
|
|
|
400
400
|
listResumableSessions,
|
|
401
401
|
} from "@nitida/sdk/expo";
|
|
402
402
|
|
|
403
|
+
// Background upload survival means a token DOES reach the native session — but
|
|
404
|
+
// it must be a short-lived, per-user token your backend mints, NEVER a shared
|
|
405
|
+
// platform amk_rt_* baked into the binary via EXPO_PUBLIC_* (that ships one write
|
|
406
|
+
// key to the whole platform, readable by anyone who unzips the IPA/APK). Fetch it
|
|
407
|
+
// at runtime from your own /session route and hand it to the client.
|
|
408
|
+
const { uploadToken } = await fetch(`${API}/session/upload-token`).then((r) => r.json());
|
|
409
|
+
|
|
403
410
|
const aq = new NitidaClient({
|
|
404
411
|
endpoint: process.env.EXPO_PUBLIC_AQUIENPZ_URL!,
|
|
405
|
-
apiKey:
|
|
412
|
+
apiKey: uploadToken, // short-lived, scoped, from YOUR backend — not EXPO_PUBLIC_*
|
|
406
413
|
tenantCode: "your-tenant",
|
|
407
414
|
tenantId: 42,
|
|
408
415
|
});
|
|
@@ -503,10 +510,13 @@ each hook subscribes to the in-process cache.
|
|
|
503
510
|
import { NitidaClient } from "@nitida/sdk";
|
|
504
511
|
import { NitidaProvider } from "@nitida/sdk/react";
|
|
505
512
|
|
|
506
|
-
//
|
|
513
|
+
// NO apiKey in the browser. Reads and URL building (useSlot, urlFor, srcSetFor)
|
|
514
|
+
// need no credential; writes go through a BFF route that calls aq.upload()
|
|
515
|
+
// server-side with the real amk_rt_* key (see the warning above). Shipping
|
|
516
|
+
// NEXT_PUBLIC_AQUIENPZ_API_KEY inlines a shared platform write key into every
|
|
517
|
+
// bundle — readable by any visitor.
|
|
507
518
|
const client = new NitidaClient({
|
|
508
519
|
endpoint: process.env.NEXT_PUBLIC_AQUIENPZ_URL!,
|
|
509
|
-
apiKey: process.env.NEXT_PUBLIC_AQUIENPZ_API_KEY!,
|
|
510
520
|
tenantCode: "your-tenant",
|
|
511
521
|
tenantId: 42,
|
|
512
522
|
});
|
|
@@ -1059,8 +1069,8 @@ the same signature.
|
|
|
1059
1069
|
`kid` names the key so a rotation does not kill URLs already in flight: the
|
|
1060
1070
|
outgoing key keeps verifying for 14 days
|
|
1061
1071
|
(`POST /admin/projects/:code/rotate-signing-key` returns the new key, its `kid`,
|
|
1062
|
-
and when the previous one retires).
|
|
1063
|
-
|
|
1072
|
+
and when the previous one retires). The rotation runbook lives in the private
|
|
1073
|
+
source repository — ask the platform owner if you need it.
|
|
1064
1074
|
|
|
1065
1075
|
> ⚠️ **A signature does NOT widen the ladder.** Off-ladder widths are 400 at
|
|
1066
1076
|
> the edge whether or not the URL is signed. Until `@nitida/asset-client`
|
package/dist/index.d.ts
CHANGED
|
@@ -145,6 +145,9 @@ type NitidaClientOptions = {
|
|
|
145
145
|
declare class SlotsApi {
|
|
146
146
|
private readonly opts;
|
|
147
147
|
constructor(opts: NitidaClientOptions);
|
|
148
|
+
/** This client's own auth/endpoint scope, threaded into every resolve so it
|
|
149
|
+
* never uses another client's key (audit N15). */
|
|
150
|
+
private resolverConfig;
|
|
148
151
|
/** Resolve one slot — returns `{slot, preset, url}` or `{slot: null, url: null}` when unbound. */
|
|
149
152
|
resolve(slotKey: string, options?: ResolveSlotOptions): Promise<SlotResolution>;
|
|
150
153
|
/** Bulk-resolve N slots in one HTTP round-trip. */
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import {
|
|
3
|
-
configureSlotResolver,
|
|
4
3
|
getAssetSrcSet,
|
|
5
4
|
getAssetUrl,
|
|
6
5
|
getHlsStreamingUrl,
|
|
@@ -119,13 +118,22 @@ var SlotsApi = class {
|
|
|
119
118
|
this.opts = opts;
|
|
120
119
|
}
|
|
121
120
|
opts;
|
|
121
|
+
/** This client's own auth/endpoint scope, threaded into every resolve so it
|
|
122
|
+
* never uses another client's key (audit N15). */
|
|
123
|
+
resolverConfig() {
|
|
124
|
+
return {
|
|
125
|
+
endpoint: this.opts.endpoint,
|
|
126
|
+
apiKey: this.opts.apiKey ?? null,
|
|
127
|
+
tenantCode: this.opts.tenantCode
|
|
128
|
+
};
|
|
129
|
+
}
|
|
122
130
|
/** Resolve one slot — returns `{slot, preset, url}` or `{slot: null, url: null}` when unbound. */
|
|
123
131
|
resolve(slotKey, options = {}) {
|
|
124
|
-
return resolveSlot(slotKey, options);
|
|
132
|
+
return resolveSlot(slotKey, { config: this.resolverConfig(), ...options });
|
|
125
133
|
}
|
|
126
134
|
/** Bulk-resolve N slots in one HTTP round-trip. */
|
|
127
135
|
resolveMany(slotKeys, options = {}) {
|
|
128
|
-
return resolveSlots(slotKeys, options);
|
|
136
|
+
return resolveSlots(slotKeys, { config: this.resolverConfig(), ...options });
|
|
129
137
|
}
|
|
130
138
|
/** List slots for the tenant (admin). Optional prefix filter for tree views. */
|
|
131
139
|
async list(opts = {}) {
|
|
@@ -217,7 +225,7 @@ var AssetsApi = class {
|
|
|
217
225
|
*/
|
|
218
226
|
async byHash(sha256) {
|
|
219
227
|
const r = await fetch(
|
|
220
|
-
endpointHref(this.opts, `/assets/by-hash/${sha256}`),
|
|
228
|
+
endpointHref(this.opts, `/assets/by-hash/${encodeURIComponent(sha256)}`),
|
|
221
229
|
{ headers: this.headers() }
|
|
222
230
|
);
|
|
223
231
|
if (r.status === 404) return null;
|
|
@@ -248,7 +256,7 @@ var AssetsApi = class {
|
|
|
248
256
|
}
|
|
249
257
|
/** Full DTO for an asset (admin view — includes audit-only fields). */
|
|
250
258
|
async get(assetId) {
|
|
251
|
-
const r = await fetch(endpointHref(this.opts, `/assets/${assetId}`), {
|
|
259
|
+
const r = await fetch(endpointHref(this.opts, `/assets/${encodeURIComponent(assetId)}`), {
|
|
252
260
|
headers: this.headers()
|
|
253
261
|
});
|
|
254
262
|
if (!r.ok) throw new Error(`asset get ${r.status}: ${await r.text()}`);
|
|
@@ -260,7 +268,7 @@ var AssetsApi = class {
|
|
|
260
268
|
* resolve to nothing.
|
|
261
269
|
*/
|
|
262
270
|
async bindings(assetId) {
|
|
263
|
-
const r = await fetch(endpointHref(this.opts, `/assets/${assetId}/slots`), {
|
|
271
|
+
const r = await fetch(endpointHref(this.opts, `/assets/${encodeURIComponent(assetId)}/slots`), {
|
|
264
272
|
headers: this.headers()
|
|
265
273
|
});
|
|
266
274
|
if (!r.ok) throw new Error(`asset bindings ${r.status}: ${await r.text()}`);
|
|
@@ -323,7 +331,7 @@ var AssetsApi = class {
|
|
|
323
331
|
*/
|
|
324
332
|
async regenerate(assetId, opts = {}) {
|
|
325
333
|
const r = await fetch(
|
|
326
|
-
endpointHref(this.opts, `/assets/${assetId}/regenerate`),
|
|
334
|
+
endpointHref(this.opts, `/assets/${encodeURIComponent(assetId)}/regenerate`),
|
|
327
335
|
{
|
|
328
336
|
method: "POST",
|
|
329
337
|
headers: { ...this.headers(), "Content-Type": "application/json" },
|
|
@@ -336,7 +344,7 @@ var AssetsApi = class {
|
|
|
336
344
|
}
|
|
337
345
|
/** Merge metadata into an asset (role / slot / description / tags). */
|
|
338
346
|
async patchMetadata(assetId, metadata) {
|
|
339
|
-
const r = await fetch(endpointHref(this.opts, `/assets/${assetId}`), {
|
|
347
|
+
const r = await fetch(endpointHref(this.opts, `/assets/${encodeURIComponent(assetId)}`), {
|
|
340
348
|
method: "PATCH",
|
|
341
349
|
headers: { ...this.headers(), "Content-Type": "application/json" },
|
|
342
350
|
body: JSON.stringify({ metadata })
|
|
@@ -577,11 +585,6 @@ var NitidaClient = class {
|
|
|
577
585
|
const cdn = opts.cdnBase ?? "https://8ok.uk";
|
|
578
586
|
setCdnBase(cdn);
|
|
579
587
|
setTenantId(opts.tenantId);
|
|
580
|
-
configureSlotResolver({
|
|
581
|
-
endpoint: opts.endpoint,
|
|
582
|
-
apiKey: opts.apiKey,
|
|
583
|
-
tenantCode: opts.tenantCode
|
|
584
|
-
});
|
|
585
588
|
this.slots = new SlotsApi(opts);
|
|
586
589
|
this.assets = new AssetsApi(opts);
|
|
587
590
|
this.usage = new UsageApi(opts);
|