@sparkvault/sdk-mobile 7.1.11 → 7.1.13

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.
Files changed (52) hide show
  1. package/README.md +2 -1
  2. package/dist/account.d.ts +0 -5
  3. package/dist/account.js +0 -9
  4. package/dist/account.js.map +1 -1
  5. package/dist/account.test-d.d.ts +26 -0
  6. package/dist/account.test-d.js +17 -0
  7. package/dist/account.test-d.js.map +1 -0
  8. package/dist/errors.d.ts +0 -3
  9. package/dist/errors.js +0 -7
  10. package/dist/errors.js.map +1 -1
  11. package/dist/http.d.ts +7 -13
  12. package/dist/http.js +7 -41
  13. package/dist/http.js.map +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/ingots.d.ts +17 -8
  18. package/dist/ingots.js +48 -26
  19. package/dist/ingots.js.map +1 -1
  20. package/dist/ingots.test-d.d.ts +11 -6
  21. package/dist/ingots.test-d.js +7 -4
  22. package/dist/ingots.test-d.js.map +1 -1
  23. package/dist/products/identity.d.ts +8 -5
  24. package/dist/products/identity.js +12 -29
  25. package/dist/products/identity.js.map +1 -1
  26. package/dist/sparks.d.ts +1 -2
  27. package/dist/sparks.js +0 -1
  28. package/dist/sparks.js.map +1 -1
  29. package/dist/tus.d.ts +0 -1
  30. package/dist/tus.js +1 -5
  31. package/dist/tus.js.map +1 -1
  32. package/dist/types.d.ts +27 -52
  33. package/dist/vaults.d.ts +1 -4
  34. package/dist/vaults.js +3 -3
  35. package/dist/vaults.js.map +1 -1
  36. package/dist/vaults.test-d.d.ts +7 -4
  37. package/dist/vaults.test-d.js +4 -2
  38. package/dist/vaults.test-d.js.map +1 -1
  39. package/package.json +1 -1
  40. package/src/account.test-d.ts +39 -0
  41. package/src/account.ts +0 -14
  42. package/src/errors.ts +0 -8
  43. package/src/http.ts +8 -50
  44. package/src/index.ts +0 -1
  45. package/src/ingots.test-d.ts +28 -5
  46. package/src/ingots.ts +56 -42
  47. package/src/products/identity.ts +16 -46
  48. package/src/sparks.ts +1 -3
  49. package/src/tus.ts +1 -6
  50. package/src/types.ts +27 -56
  51. package/src/vaults.test-d.ts +6 -3
  52. package/src/vaults.ts +4 -8
package/src/ingots.ts CHANGED
@@ -121,8 +121,12 @@ export interface UploadFromUriOptions extends UploadOptions {
121
121
  debug?: DebugLogger;
122
122
  }
123
123
 
124
- /** A replace addresses its ingot by id: no name is placed, so the create-only hooks do not apply. */
125
- export interface ReplaceFromUriOptions extends Omit<UploadFromUriOptions, 'name' | 'onNameConflict' | 'onIngotCreated'> {
124
+ /**
125
+ * A replace addresses its ingot by id: no name is placed and the ingot stays
126
+ * in its folder, so the create-only hooks and the folder target do not apply.
127
+ */
128
+ export interface ReplaceFromUriOptions
129
+ extends Omit<UploadFromUriOptions, 'name' | 'folder' | 'folderId' | 'onNameConflict' | 'onIngotCreated'> {
126
130
  ingotId: string;
127
131
  /**
128
132
  * A new name for the ingot, stored in the same request as the content.
@@ -195,11 +199,6 @@ export interface DownloadToFileResult {
195
199
  sizeBytes: number;
196
200
  }
197
201
 
198
- export interface AddInviteOptions {
199
- /** Invite lifetime in seconds. Omit to inherit the link's existing expiry. */
200
- expiresInSeconds?: number;
201
- }
202
-
203
202
  export interface CreateUploadOptions {
204
203
  vaultId: string;
205
204
  vat: string;
@@ -330,6 +329,22 @@ function publicationOf(ingot: Ingot): Pick<UploadResult, 'content_version'> {
330
329
  return { content_version: ingot.content_version ?? null };
331
330
  }
332
331
 
332
+ /** A response header's value by name, whatever case the adapter spelled it in; undefined when absent. */
333
+ function headerOf(headers: Record<string, string> | undefined, name: string): string | undefined {
334
+ return Object.entries(headers ?? {}).find(([header]) => header.toLowerCase() === name)?.[1];
335
+ }
336
+
337
+ /**
338
+ * The body length a response declared (`Content-Length`), or undefined when
339
+ * it declared none, or something that is not a non-negative whole number.
340
+ */
341
+ function declaredLengthOf(headers: Record<string, string> | undefined): number | undefined {
342
+ const declared = headerOf(headers, 'content-length');
343
+ if (declared === undefined || !/^\d+$/.test(declared.trim())) return undefined;
344
+ const length = Number(declared);
345
+ return Number.isSafeInteger(length) ? length : undefined;
346
+ }
347
+
333
348
  /**
334
349
  * Forge's id for an upload: the last segment of its session URL, where
335
350
  * resolveForgeSessionUrl placed the session Forge's create answered with.
@@ -411,7 +426,6 @@ export class MobileIngotsClient {
411
426
  ingot_id: init.ingotId,
412
427
  name,
413
428
  size_bytes: init.sizeBytes ?? options.fileSize,
414
- storage_location: 's3',
415
429
  // A server that does not report it minted the row (the upsert is the
416
430
  // only other outcome, and it always says so).
417
431
  created,
@@ -464,7 +478,6 @@ export class MobileIngotsClient {
464
478
  ingot_id: options.ingotId,
465
479
  name: options.name,
466
480
  size_bytes: options.fileSize,
467
- storage_location: 's3',
468
481
  created: false,
469
482
  status: ingot.status,
470
483
  ...publicationOf(ingot),
@@ -600,7 +613,6 @@ export class MobileIngotsClient {
600
613
  ingot_id: response.data.ingot_id,
601
614
  name,
602
615
  size_bytes: fileSize,
603
- storage_location: 's3',
604
616
  // A replace never mints an ingot: the row is the caller's existing one.
605
617
  created: false,
606
618
  status: ingot.status,
@@ -799,11 +811,11 @@ export class MobileIngotsClient {
799
811
  result.uri,
800
812
  fileUri,
801
813
  result.sizeBytes,
802
- expectedSizeBytes
814
+ expectedSizeBytes,
815
+ declaredLengthOf(result.headers)
803
816
  );
804
817
  // Bind cached metadata to the actual stream, never the earlier URL response.
805
- const contentVersion = Object.entries(result.headers ?? {})
806
- .find(([name]) => name.toLowerCase() === 'x-content-version')?.[1] || null;
818
+ const contentVersion = headerOf(result.headers, 'x-content-version') || null;
807
819
  if (options.expectedContentVersion && contentVersion && options.expectedContentVersion !== contentVersion) {
808
820
  await this.deleteFileIfPresent(fileDownloader, verified.filePath);
809
821
  throw new SparkVaultMobileError('File changed while downloading. Please try again.', {
@@ -868,18 +880,12 @@ export class MobileIngotsClient {
868
880
  return response.data;
869
881
  }
870
882
 
871
- async addInvite(
872
- vaultId: string,
873
- ingotId: string,
874
- vat: string,
875
- identity: string,
876
- options: AddInviteOptions = {}
877
- ): Promise<IngotInvite> {
883
+ async addInvite(vaultId: string, ingotId: string, vat: string, identity: string): Promise<IngotInvite> {
878
884
  validateVaultId(vaultId);
879
885
  validateIngotId(ingotId);
880
886
  const response = await this.http.post<IngotInvite>(
881
887
  apiPath`/vaults/${vaultId}/ingots/${ingotId}/sharing/invite`,
882
- { identity, expires_in_seconds: options.expiresInSeconds },
888
+ { identity },
883
889
  { vat }
884
890
  );
885
891
  return response.data;
@@ -925,19 +931,7 @@ export class MobileIngotsClient {
925
931
  validateVaultId(vaultId);
926
932
  validateIngotId(ingotId);
927
933
  const ingot = await this.get(vaultId, ingotId, vat);
928
- return {
929
- total_downloads: ingot.access_count ?? 0,
930
- last_accessed_at: ingot.last_accessed_at ?? ingot.accessed_at,
931
- };
932
- }
933
-
934
- async purgeAccessLogs(vaultId: string, ingotId: string, vat: string): Promise<void> {
935
- validateVaultId(vaultId);
936
- validateIngotId(ingotId);
937
- void vat;
938
- throw new SparkVaultValidationError(
939
- 'Ingot audit logs are read-only. Configure vault access-log retention to expire old entries.'
940
- );
934
+ return { total_downloads: ingot.access_count ?? 0 };
941
935
  }
942
936
 
943
937
  /**
@@ -992,12 +986,24 @@ export class MobileIngotsClient {
992
986
  return ingot;
993
987
  }
994
988
 
989
+ /**
990
+ * The staged file is the download only when its length is the file's. The
991
+ * caller's `expectedSizeBytes` (the ingot's `size_bytes`) is the length the
992
+ * vault lists, so a file of any other length is the wrong content for good
993
+ * (SparkVaultValidationError, never retried). Without it, the response's own
994
+ * `Content-Length` stands in: a body that ended cleanly short of it is a
995
+ * transfer some hop cut and reframed, not the file, so the file goes and the
996
+ * attempt fails as a transport failure does (`download_truncated`, retried
997
+ * on a fresh link). A response that declares no length can only be checked
998
+ * for being non-empty.
999
+ */
995
1000
  private async verifyDownloadedFile(
996
1001
  fileDownloader: MobileFileDownloader,
997
1002
  preferredUri: string,
998
1003
  expectedUri: string,
999
1004
  knownSizeBytes?: number,
1000
- expectedSizeBytes?: number
1005
+ expectedSizeBytes?: number,
1006
+ declaredSizeBytes?: number
1001
1007
  ): Promise<DownloadToFileResult> {
1002
1008
  const filePath = preferredUri || expectedUri;
1003
1009
  const info = await fileDownloader.getInfo(filePath);
@@ -1011,14 +1017,22 @@ export class MobileIngotsClient {
1011
1017
  throw new SparkVaultMobileError('Download failed - file is empty');
1012
1018
  }
1013
1019
 
1014
- if (expectedSizeBytes !== undefined && expectedSizeBytes > 0 && sizeBytes !== expectedSizeBytes) {
1020
+ if (expectedSizeBytes !== undefined && expectedSizeBytes > 0) {
1021
+ if (sizeBytes !== expectedSizeBytes) {
1022
+ await this.deleteFileIfPresent(fileDownloader, filePath);
1023
+ // Deterministic server-content mismatch — retrying the same ingot will
1024
+ // yield the same bytes, so this must surface as a non-retryable validation
1025
+ // failure rather than a transient transport error.
1026
+ throw new SparkVaultValidationError('Download failed - file size did not match expected ingot size', {
1027
+ expectedSizeBytes,
1028
+ actualSizeBytes: sizeBytes,
1029
+ });
1030
+ }
1031
+ } else if (declaredSizeBytes !== undefined && sizeBytes !== declaredSizeBytes) {
1015
1032
  await this.deleteFileIfPresent(fileDownloader, filePath);
1016
- // Deterministic server-content mismatch — retrying the same ingot will
1017
- // yield the same bytes, so this must surface as a non-retryable validation
1018
- // failure rather than a transient transport error.
1019
- throw new SparkVaultValidationError('Download failed - file size did not match expected ingot size', {
1020
- expectedSizeBytes,
1021
- actualSizeBytes: sizeBytes,
1033
+ throw new SparkVaultMobileError('Download failed - the file is not the length the server declared', {
1034
+ code: 'download_truncated',
1035
+ details: { declaredSizeBytes, actualSizeBytes: sizeBytes },
1022
1036
  });
1023
1037
  }
1024
1038
 
@@ -20,9 +20,8 @@ import type {
20
20
  PasskeyRegisterChallenge,
21
21
  PasskeySession,
22
22
  PasskeyStatus,
23
- RequestPinRequest,
24
- RequestPinResponse,
25
23
  SendOtpRequest,
24
+ SendOtpResponse,
26
25
  SparkVaultUser,
27
26
  SubmitSecondFactorRequest,
28
27
  VerifyOtpRequest,
@@ -127,10 +126,6 @@ export class MobileIdentityClient {
127
126
  }
128
127
  }
129
128
 
130
- isConfigPreloaded(): boolean {
131
- return this.configCache !== null;
132
- }
133
-
134
129
  async getJwks(options: { forceRefresh?: boolean } = {}): Promise<IdentityJwks> {
135
130
  const now = Date.now();
136
131
  const expired = now - this.jwksCachedAt > JWKS_CACHE_TTL_MS;
@@ -247,31 +242,14 @@ export class MobileIdentityClient {
247
242
  return key;
248
243
  }
249
244
 
250
- async sendOtp(request: SendOtpRequest): Promise<RequestPinResponse> {
245
+ async sendOtp(request: SendOtpRequest): Promise<SendOtpResponse> {
251
246
  const identity = this.normalizeIdentity(request.identity, request.identityType);
252
- const result = await this.postIdentity<{
253
- kindling: string;
254
- expires_at?: number;
255
- retry_after?: number;
256
- }>(apiPath`/otp/send`, {
247
+ const result = await this.postIdentity<SendOtpResponse>(apiPath`/otp/send`, {
257
248
  recipient: identity,
258
249
  method: request.method,
259
250
  });
260
251
 
261
- return {
262
- kindling: result.kindling,
263
- expires_at: result.expires_at ?? Math.floor(Date.now() / 1000) + 900,
264
- retry_after: result.retry_after,
265
- is_signup: false,
266
- };
267
- }
268
-
269
- async requestPin(request: RequestPinRequest): Promise<RequestPinResponse> {
270
- return this.sendOtp({
271
- identity: request.email,
272
- identityType: 'email',
273
- method: 'email',
274
- });
252
+ return { kindling: result.kindling, expires_at: result.expires_at };
275
253
  }
276
254
 
277
255
  async verifyOtp(request: VerifyOtpRequest): Promise<IdentityVerifyResult> {
@@ -371,7 +349,12 @@ export class MobileIdentityClient {
371
349
  }
372
350
  }
373
351
 
374
- async getPasskeyAuthOptions(_identity?: string, _identityType?: IdentityType): Promise<PasskeyAuthChallenge> {
352
+ /**
353
+ * Start a discoverable passkey ceremony: the request names no identity, so
354
+ * the authenticator offers every passkey it holds for the Identity relying
355
+ * party, and the one chosen names its person (its user handle is the SVID).
356
+ */
357
+ async getPasskeyAuthOptions(): Promise<PasskeyAuthChallenge> {
375
358
  return this.postIdentity<PasskeyAuthChallenge>(apiPath`/passkey/verify`, {});
376
359
  }
377
360
 
@@ -507,9 +490,7 @@ export class MobileIdentityClient {
507
490
  recipient: this.normalizeIdentity(request.recipient, inferIdentityType(request.recipient)),
508
491
  });
509
492
  } catch (err) {
510
- const apiError = err as SparkVaultMobileError & {
511
- data?: { kindling?: string; expires_at?: number };
512
- };
493
+ const apiError = err as SparkVaultMobileError;
513
494
  const message = apiError.message || 'Verification failed';
514
495
  const code = apiError.statusCode ?? apiError.code;
515
496
 
@@ -599,15 +580,10 @@ export class MobileIdentityClient {
599
580
  return (data.data ?? data) as T;
600
581
  }
601
582
 
602
- private async postIdentity<T>(
603
- path: ApiPath,
604
- body: Record<string, unknown>,
605
- options: { bearerToken?: string } = {}
606
- ): Promise<T> {
607
- const headers = this.identityHeaders(options.bearerToken);
583
+ private async postIdentity<T>(path: ApiPath, body: Record<string, unknown>): Promise<T> {
608
584
  const response = await this.config.fetch(identityUrl(this.config, path), {
609
585
  method: 'POST',
610
- headers,
586
+ headers: this.identityHeaders(),
611
587
  body: JSON.stringify(body),
612
588
  timeoutMs: this.config.timeoutMs,
613
589
  });
@@ -615,30 +591,24 @@ export class MobileIdentityClient {
615
591
  if (!response.ok) {
616
592
  const errorBody = await response.json().catch(() => ({} as ApiErrorBody));
617
593
  const message = errorBody.error?.message ?? errorBody.message ?? 'Identity verification failed';
618
- const error = new SparkVaultMobileError(message, {
594
+ throw new SparkVaultMobileError(message, {
619
595
  code: errorBody.error?.code ?? response.status,
620
596
  statusCode: response.status,
621
597
  details: errorBody.error?.details,
622
598
  meta: errorBody.meta,
623
599
  });
624
- (error as SparkVaultMobileError & { data?: unknown }).data = errorBody.data ?? errorBody;
625
- throw error;
626
600
  }
627
601
 
628
602
  const data = await response.json();
629
603
  return (data.data ?? data) as T;
630
604
  }
631
605
 
632
- private identityHeaders(bearerToken?: string): Record<string, string> {
633
- const headers: Record<string, string> = {
606
+ private identityHeaders(): Record<string, string> {
607
+ return {
634
608
  'Content-Type': 'application/json',
635
609
  Accept: 'application/json',
636
610
  'User-Agent': this.config.userAgent,
637
611
  };
638
- if (bearerToken) {
639
- headers.Authorization = `Bearer ${bearerToken}`;
640
- }
641
- return headers;
642
612
  }
643
613
 
644
614
  private identityLookupBody(identity: string, identityType?: IdentityType): Record<string, string> {
package/src/sparks.ts CHANGED
@@ -3,10 +3,10 @@ import type { ShareSparkResponse, SparkShareStatus, UnshareSparkResponse } from
3
3
  import { apiPath } from './request-path.js';
4
4
  import { validateResourceId } from './validation.js';
5
5
 
6
+ /** A Spark as GET /sparks lists it: never its payload, which only `get` reads. */
6
7
  export interface Spark {
7
8
  spark_id: string;
8
9
  status: string;
9
- payload?: string;
10
10
  content_type?: string;
11
11
  }
12
12
 
@@ -37,7 +37,6 @@ export interface CreateSparkOptions {
37
37
  */
38
38
  ttlMinutes?: number;
39
39
  kindling?: string;
40
- name?: string;
41
40
  }
42
41
 
43
42
  export interface CreateSparkResponse {
@@ -79,7 +78,6 @@ export class MobileSparksClient {
79
78
  content_type: options.contentType,
80
79
  ttl_minutes: options.ttlMinutes,
81
80
  kindling: options.kindling,
82
- name: options.name,
83
81
  });
84
82
  return response.data;
85
83
  }
package/src/tus.ts CHANGED
@@ -37,7 +37,6 @@ import type {
37
37
  } from './types.js';
38
38
 
39
39
  export interface ParsedForgeUrl {
40
- baseUrl: string;
41
40
  tusEndpoint: string;
42
41
  istk: string;
43
42
  }
@@ -169,11 +168,7 @@ export function parseForgeUrl(
169
168
  reason: read.reason,
170
169
  });
171
170
  }
172
- return {
173
- baseUrl: read.origin,
174
- tusEndpoint: read.endpoint,
175
- istk: read.istk,
176
- };
171
+ return { tusEndpoint: read.endpoint, istk: read.istk };
177
172
  }
178
173
 
179
174
  /**
package/src/types.ts CHANGED
@@ -48,16 +48,9 @@ export interface ApiErrorBody {
48
48
  details?: Record<string, unknown>;
49
49
  };
50
50
  message?: string;
51
- data?: unknown;
52
51
  meta?: ApiMeta;
53
52
  }
54
53
 
55
- export interface RateLimitInfo {
56
- limit: number;
57
- remaining: number;
58
- reset: number;
59
- }
60
-
61
54
  /** Account-scoped role. Owners and admins manage the organization and team. */
62
55
  export type OrgRole = 'owner' | 'admin' | 'user';
63
56
 
@@ -282,7 +275,6 @@ export interface IdentityBranding {
282
275
  companyName: string | null;
283
276
  logoLight: string | null;
284
277
  logoDark: string | null;
285
- backdrop_blur?: boolean;
286
278
  themeMode?: 'light' | 'dark';
287
279
  }
288
280
 
@@ -380,52 +372,37 @@ export interface VerifyOtpRequest {
380
372
  recipient: string;
381
373
  }
382
374
 
383
- export interface RequestPinRequest {
384
- email: string;
385
- organization_name?: string;
386
- }
387
-
388
- export interface RequestPinResponse {
375
+ /** A code sent: the kindling its verify names, and when the code expires (Unix seconds). */
376
+ export interface SendOtpResponse {
389
377
  kindling: string;
390
378
  expires_at: number;
391
- retry_after?: number;
392
- is_signup: boolean;
393
379
  }
394
380
 
395
381
  export interface PinVerifyLoginResponse {
396
382
  access_token: string;
397
383
  refresh_token: string;
398
- token_type: 'Bearer';
399
- expires_in: number;
400
384
  user: SparkVaultUser;
401
385
  }
402
386
 
387
+ /**
388
+ * A verified identity with no SparkVault user yet: `signup_token` (an
389
+ * identity-only access token) authorizes `account.completeSignup`.
390
+ */
403
391
  export interface PinVerifySignupPendingResponse {
404
392
  verified: true;
405
393
  needs_signup_info: true;
406
394
  signup_token: string;
407
- expires_at: number;
408
395
  }
409
396
 
410
397
  /**
411
- * Variant of the Core exchange result indicating the account has 2FA enabled:
412
- * the caller must collect a second factor via
413
- * `products.identity.submitSecondFactor({ ticket, code })` and re-exchange the
414
- * resulting Identity token via `account.exchangeIdentityToken`.
398
+ * The Core exchange of a verified Identity token. A second factor is collected
399
+ * at the Identity Product before its token exists (`products.identity.verifyOtp`
400
+ * throws for it, and `submitSecondFactor` finishes it), so the exchange is a
401
+ * login or a pending signup, never a second-factor challenge.
415
402
  */
416
- export interface PinVerifySecondFactorPendingResponse {
417
- verified: false;
418
- second_factor_required: true;
419
- /** Opaque pending-login handle to submit the second factor against. */
420
- ticket: string;
421
- /** Methods the user may use, e.g. ['authenticator','recovery_code']. */
422
- methods: string[];
423
- }
424
-
425
403
  export type PinVerifyResponse =
426
404
  | PinVerifyLoginResponse
427
- | PinVerifySignupPendingResponse
428
- | PinVerifySecondFactorPendingResponse;
405
+ | PinVerifySignupPendingResponse;
429
406
 
430
407
  export interface SubmitSecondFactorRequest {
431
408
  /** Pending-login ticket from a second_factor_required response. */
@@ -442,8 +419,6 @@ export interface CompleteSignupRequest {
442
419
  export interface CompleteSignupResponse {
443
420
  access_token: string;
444
421
  refresh_token: string;
445
- token_type?: 'Bearer';
446
- expires_in?: number;
447
422
  user: SparkVaultUser;
448
423
  account: SparkVaultAccount;
449
424
  }
@@ -454,10 +429,14 @@ export interface PasskeyAuthOptions {
454
429
  challenge: string;
455
430
  timeout: number;
456
431
  rpId: string;
432
+ /**
433
+ * The person's credentials when the ceremony is scoped to an identifier:
434
+ * ids only, since a transport list would hand an unauthenticated probe a
435
+ * device fingerprint.
436
+ */
457
437
  allowCredentials?: Array<{
458
438
  id: string;
459
439
  type: 'public-key';
460
- transports?: string[];
461
440
  }>;
462
441
  userVerification: 'required' | 'preferred' | 'discouraged';
463
442
  }
@@ -523,15 +502,15 @@ export interface PasskeyCredential {
523
502
 
524
503
  export interface PasskeyStatus {
525
504
  has_passkey: boolean;
526
- registered_at?: number;
527
- last_used_at?: number;
528
505
  }
529
506
 
507
+ /**
508
+ * POST /auth/identity/verify: a session for the verified identity's member, or,
509
+ * with `user` and `account` null, an identity-only token for a signup.
510
+ */
530
511
  export interface IdentitySessionResponse {
531
512
  access_token: string;
532
513
  refresh_token: string;
533
- token_type?: 'Bearer';
534
- expires_in?: number;
535
514
  user: SparkVaultUser | null;
536
515
  account: SparkVaultAccount | null;
537
516
  }
@@ -549,18 +528,16 @@ export interface Vault {
549
528
  unseal_count?: number;
550
529
  }
551
530
 
531
+ /** A vault is created with its name alone; a description is set with `update`. */
552
532
  export interface CreateVaultRequest {
553
533
  name: string;
554
- description?: string;
555
534
  }
556
535
 
557
536
  export interface CreateVaultResponse {
558
537
  vault_id: string;
559
538
  name: string;
560
- description?: string;
561
539
  vmk: string;
562
540
  vmk_warning: string;
563
- status: 'sealed';
564
541
  created_at: number;
565
542
  }
566
543
 
@@ -700,7 +677,6 @@ export interface Ingot {
700
677
  name: string;
701
678
  content_type: string;
702
679
  size_bytes: number;
703
- storage_location: 'dynamodb' | 's3';
704
680
  /** `uploading` until Forge activates the upload, then `active`. */
705
681
  status?: 'uploading' | 'active';
706
682
  folder_id?: string | null;
@@ -712,10 +688,7 @@ export interface Ingot {
712
688
  */
713
689
  managed_by?: string | null;
714
690
  created_at: number;
715
- accessed_at?: number;
716
- last_accessed_at?: number;
717
691
  access_count?: number;
718
- expires_at?: number;
719
692
  /**
720
693
  * Client-computed, vault-keyed HMAC of the plaintext (43-char base64url),
721
694
  * or null when the uploader did not supply one. Used for upload dedup and
@@ -736,9 +709,14 @@ export interface HashCheckResponse {
736
709
  found: HashCheckMatch[];
737
710
  }
738
711
 
712
+ /**
713
+ * One page of GET /vaults/:id/ingots or /ingots/search. `count` is this
714
+ * page's length: the server reports no total.
715
+ */
739
716
  export interface ListIngotsResponse {
740
717
  ingots: Ingot[];
741
- total: number;
718
+ count: number;
719
+ has_more: boolean;
742
720
  next_cursor?: string;
743
721
  }
744
722
 
@@ -762,7 +740,6 @@ export interface UploadResult {
762
740
  */
763
741
  name: string;
764
742
  size_bytes: number;
765
- storage_location: 's3';
766
743
  /**
767
744
  * Whether this upload minted the ingot (true) or went into an existing row
768
745
  * (false): the one holding the name, as the create's name-conflict policy
@@ -842,8 +819,6 @@ export interface IngotAccessLog {
842
819
 
843
820
  export interface IngotAccessStats {
844
821
  total_downloads: number;
845
- unique_visitors?: number;
846
- last_accessed_at?: number;
847
822
  }
848
823
 
849
824
  /** A folder as every folder route answers it (Core's FolderRepository.toPublicFormat). */
@@ -886,16 +861,13 @@ export interface TokenStorageAdapter {
886
861
  clearTokens(): Promise<void>;
887
862
  getUser?<T = SparkVaultUser>(): Promise<T | null>;
888
863
  setUser?<T = SparkVaultUser>(user: T): Promise<void>;
889
- getAccount?<T = SparkVaultAccount>(): Promise<T | null>;
890
864
  setAccount?<T = SparkVaultAccount>(account: T): Promise<void>;
891
- clearAll?(): Promise<void>;
892
865
  }
893
866
 
894
867
  export type FetchLike = (
895
868
  url: string,
896
869
  options?: RequestInit & {
897
870
  timeoutMs?: number;
898
- responseType?: XMLHttpRequestResponseType;
899
871
  }
900
872
  ) => Promise<Response>;
901
873
 
@@ -1106,7 +1078,6 @@ export interface MobileFileDownloader {
1106
1078
 
1107
1079
  export interface DebugLogger {
1108
1080
  log(message: string): void;
1109
- warn(message: string): void;
1110
1081
  error(message: string): void;
1111
1082
  success(message: string): void;
1112
1083
  }
@@ -7,11 +7,13 @@
7
7
  * `*.test.ts`, which tsconfig excludes.
8
8
  *
9
9
  * Mirrored server sources: apps/api/src/folders/list.js (listFolders answers
10
- * `{ folders }`, the whole list and nothing else) and apps/api/src/vaults/list.js
11
- * (one page: `{ vaults, count, has_more, next_cursor? }`).
10
+ * `{ folders }`, the whole list and nothing else), apps/api/src/vaults/list.js
11
+ * (one page: `{ vaults, count, has_more, next_cursor? }`) and
12
+ * apps/api/src/vaults/create.js (createVault answers `{ vault_id, name, vmk,
13
+ * vmk_warning, created_at }`).
12
14
  */
13
15
 
14
- import type { ListFoldersResponse, ListVaultsResponse } from './types.js';
16
+ import type { CreateVaultResponse, ListFoldersResponse, ListVaultsResponse } from './types.js';
15
17
 
16
18
  /** Fails to compile unless `T` resolves to exactly `true`. */
17
19
  type Assert<T extends true> = T;
@@ -26,4 +28,5 @@ export type VaultsWireContract = [
26
28
  // never sends would read as undefined wherever a caller trusted it.
27
29
  Assert<Equals<keyof ListFoldersResponse, 'folders'>>,
28
30
  Assert<Equals<keyof ListVaultsResponse, 'vaults' | 'count' | 'has_more' | 'next_cursor'>>,
31
+ Assert<Equals<keyof CreateVaultResponse, 'vault_id' | 'name' | 'vmk' | 'vmk_warning' | 'created_at'>>,
29
32
  ];
package/src/vaults.ts CHANGED
@@ -41,13 +41,9 @@ export class MobileVaultsClient {
41
41
  return response.data;
42
42
  }
43
43
 
44
- async list(options?: {
45
- status?: 'sealed' | 'unsealed';
46
- limit?: number;
47
- cursor?: string;
48
- }): Promise<ListVaultsResponse> {
44
+ async list(options?: { limit?: number; cursor?: string }): Promise<ListVaultsResponse> {
49
45
  const response = await this.http.get<ListVaultsResponse>(apiPath`/vaults`, {
50
- query: { status: options?.status, limit: options?.limit || undefined, cursor: options?.cursor },
46
+ query: { limit: options?.limit || undefined, cursor: options?.cursor },
51
47
  });
52
48
  return response.data;
53
49
  }
@@ -58,11 +54,11 @@ export class MobileVaultsClient {
58
54
  * filtered down to nothing still carries a cursor. A failed page fails the
59
55
  * listing: a vault picker must never show a silently partial account.
60
56
  */
61
- async listAll(options?: { status?: 'sealed' | 'unsealed' }): Promise<Vault[]> {
57
+ async listAll(): Promise<Vault[]> {
62
58
  const vaults: Vault[] = [];
63
59
  let cursor: string | undefined;
64
60
  do {
65
- const page = await this.list({ ...options, limit: VAULT_LIST_PAGE_LIMIT, cursor });
61
+ const page = await this.list({ limit: VAULT_LIST_PAGE_LIMIT, cursor });
66
62
  vaults.push(...page.vaults);
67
63
  cursor = page.has_more ? page.next_cursor : undefined;
68
64
  } while (cursor);