@nitida/sdk 0.36.0 → 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 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 aquienpz with the real key.
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 → aquienpz)
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 aquienpz. Same pattern Vercel Blob uses for
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 aquienpz URL —
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: process.env.EXPO_PUBLIC_AQUIENPZ_API_KEY!, // amk_rt_*
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
- // In production, get apiKey from a /session route instead of bundling it.
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). See
1063
- [the rotation runbook](https://github.com/espaciofuturoio/aquienpz/blob/main/docs/RUNBOOKS/signing-key-rotation.md).
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);