@sparkvault/sdk-mobile 7.1.10 → 7.1.12
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 -1
- package/dist/account.d.ts +0 -5
- package/dist/account.js +0 -9
- package/dist/account.js.map +1 -1
- package/dist/account.test-d.d.ts +26 -0
- package/dist/account.test-d.js +17 -0
- package/dist/account.test-d.js.map +1 -0
- package/dist/errors.d.ts +0 -3
- package/dist/errors.js +0 -7
- package/dist/errors.js.map +1 -1
- package/dist/http.d.ts +7 -13
- package/dist/http.js +7 -41
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ingots.d.ts +6 -8
- package/dist/ingots.js +3 -15
- package/dist/ingots.js.map +1 -1
- package/dist/ingots.test-d.d.ts +11 -6
- package/dist/ingots.test-d.js +7 -4
- package/dist/ingots.test-d.js.map +1 -1
- package/dist/products/identity.d.ts +8 -5
- package/dist/products/identity.js +12 -29
- package/dist/products/identity.js.map +1 -1
- package/dist/sparks.d.ts +5 -2
- package/dist/sparks.js +0 -1
- package/dist/sparks.js.map +1 -1
- package/dist/tus.d.ts +0 -1
- package/dist/tus.js +1 -5
- package/dist/tus.js.map +1 -1
- package/dist/types.d.ts +27 -52
- package/dist/vaults.d.ts +1 -4
- package/dist/vaults.js +3 -3
- package/dist/vaults.js.map +1 -1
- package/dist/vaults.test-d.d.ts +7 -4
- package/dist/vaults.test-d.js +4 -2
- package/dist/vaults.test-d.js.map +1 -1
- package/package.json +1 -1
- package/src/account.test-d.ts +39 -0
- package/src/account.ts +0 -14
- package/src/errors.ts +0 -8
- package/src/http.ts +8 -50
- package/src/index.ts +0 -1
- package/src/ingots.test-d.ts +28 -5
- package/src/ingots.ts +9 -31
- package/src/products/identity.ts +16 -46
- package/src/sparks.ts +5 -3
- package/src/tus.ts +1 -6
- package/src/types.ts +27 -56
- package/src/vaults.test-d.ts +6 -3
- package/src/vaults.ts +4 -8
package/src/http.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { isTransientHttpStatus } from './app-sdk/transient.js';
|
|
11
11
|
import { createDedupMutex } from './mutex.js';
|
|
12
12
|
import { queryString, type ApiPath, type QueryParams } from './request-path.js';
|
|
13
|
-
import type { ApiErrorBody, ApiMeta, ApiResponse
|
|
13
|
+
import type { ApiErrorBody, ApiMeta, ApiResponse } from './types.js';
|
|
14
14
|
|
|
15
15
|
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
16
16
|
|
|
@@ -21,7 +21,6 @@ export interface RequestOptions {
|
|
|
21
21
|
query?: QueryParams;
|
|
22
22
|
skipAuth?: boolean;
|
|
23
23
|
vat?: string;
|
|
24
|
-
timeoutMs?: number;
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
export type AuthEvent = 'logout' | 'token_refreshed';
|
|
@@ -72,24 +71,11 @@ export class MobileHttpClient {
|
|
|
72
71
|
private readonly authListeners = new Set<AuthEventListener>();
|
|
73
72
|
private readonly refreshMutex = createDedupMutex<RefreshOutcome>();
|
|
74
73
|
private lastMeta: ApiMeta | null = null;
|
|
75
|
-
private rateLimitInfo: RateLimitInfo | null = null;
|
|
76
74
|
|
|
77
75
|
constructor(config: ResolvedMobileConfig) {
|
|
78
76
|
this.config = config;
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
get baseUrl(): string {
|
|
82
|
-
return this.config.apiBaseUrl;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
get meta(): ApiMeta | null {
|
|
86
|
-
return this.lastMeta;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
get rateLimit(): RateLimitInfo | null {
|
|
90
|
-
return this.rateLimitInfo;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
79
|
onAuthEvent(listener: AuthEventListener): () => void {
|
|
94
80
|
this.authListeners.add(listener);
|
|
95
81
|
return () => this.authListeners.delete(listener);
|
|
@@ -133,32 +119,6 @@ export class MobileHttpClient {
|
|
|
133
119
|
return headers;
|
|
134
120
|
}
|
|
135
121
|
|
|
136
|
-
private parseRateLimitHeaders(headers: Headers): void {
|
|
137
|
-
const limit = headers.get('X-RateLimit-Limit');
|
|
138
|
-
const remaining = headers.get('X-RateLimit-Remaining');
|
|
139
|
-
const reset = headers.get('X-RateLimit-Reset');
|
|
140
|
-
|
|
141
|
-
if (!limit || !remaining || !reset) return;
|
|
142
|
-
|
|
143
|
-
const parsedLimit = parseInt(limit, 10);
|
|
144
|
-
const parsedRemaining = parseInt(remaining, 10);
|
|
145
|
-
const parsedReset = parseInt(reset, 10);
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
Number.isFinite(parsedLimit) &&
|
|
149
|
-
Number.isFinite(parsedRemaining) &&
|
|
150
|
-
Number.isFinite(parsedReset)
|
|
151
|
-
) {
|
|
152
|
-
this.rateLimitInfo = {
|
|
153
|
-
limit: parsedLimit,
|
|
154
|
-
remaining: parsedRemaining,
|
|
155
|
-
reset: parsedReset,
|
|
156
|
-
};
|
|
157
|
-
} else {
|
|
158
|
-
this.config.logger.warn('Malformed rate limit headers', { limit, remaining, reset });
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
122
|
/** The SDK error for a request the fetch adapter rejected (no HTTP response). */
|
|
163
123
|
private transportError(err: unknown): SparkVaultMobileError {
|
|
164
124
|
if (err instanceof SparkVaultMobileError) {
|
|
@@ -181,12 +141,6 @@ export class MobileHttpClient {
|
|
|
181
141
|
return new SparkVaultMobileError(err instanceof Error ? err.message : 'Unknown request error');
|
|
182
142
|
}
|
|
183
143
|
|
|
184
|
-
/**
|
|
185
|
-
* Single mapping for non-OK responses: 402s become typed gate errors
|
|
186
|
-
* (PlanRequiredError / QuotaExceededError) so the subscribe / add-on UX keys
|
|
187
|
-
* off one set of types; everything else becomes a SparkVaultMobileError
|
|
188
|
-
* carrying the server code, status, details, and meta.
|
|
189
|
-
*/
|
|
190
144
|
/** An error response's JSON body, or an empty one when it has none. */
|
|
191
145
|
private async readErrorBody(response: Response): Promise<ApiErrorBody> {
|
|
192
146
|
try {
|
|
@@ -209,6 +163,12 @@ export class MobileHttpClient {
|
|
|
209
163
|
}
|
|
210
164
|
}
|
|
211
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Single mapping for non-OK responses: 402s become typed gate errors
|
|
168
|
+
* (PlanRequiredError / QuotaExceededError) so the subscribe / add-on UX keys
|
|
169
|
+
* off one set of types; everything else becomes a SparkVaultMobileError
|
|
170
|
+
* carrying the server code, status, details, and meta.
|
|
171
|
+
*/
|
|
212
172
|
private httpError(status: number, errorBody: ApiErrorBody): SparkVaultMobileError {
|
|
213
173
|
if (errorBody.meta) {
|
|
214
174
|
this.lastMeta = errorBody.meta;
|
|
@@ -358,7 +318,7 @@ export class MobileHttpClient {
|
|
|
358
318
|
|
|
359
319
|
async request<T>(method: HttpMethod, path: ApiPath, options: RequestOptions = {}): Promise<ApiResponse<T>> {
|
|
360
320
|
const url = this.makeUrl(path, options.query);
|
|
361
|
-
const timeoutMs =
|
|
321
|
+
const timeoutMs = this.config.timeoutMs;
|
|
362
322
|
const body = method === 'GET' ? undefined : this.serializeBody(options.body);
|
|
363
323
|
let headers = await this.getHeaders({ ...options, body: options.body });
|
|
364
324
|
|
|
@@ -368,7 +328,6 @@ export class MobileHttpClient {
|
|
|
368
328
|
} catch (err) {
|
|
369
329
|
throw this.transportError(err);
|
|
370
330
|
}
|
|
371
|
-
this.parseRateLimitHeaders(response.headers);
|
|
372
331
|
|
|
373
332
|
if (response.status === 401 && !options.skipAuth) {
|
|
374
333
|
await this.throwIfVatRefused(response);
|
|
@@ -379,7 +338,6 @@ export class MobileHttpClient {
|
|
|
379
338
|
} catch (err) {
|
|
380
339
|
throw this.transportError(err);
|
|
381
340
|
}
|
|
382
|
-
this.parseRateLimitHeaders(response.headers);
|
|
383
341
|
}
|
|
384
342
|
|
|
385
343
|
if (response.status === 204) {
|
package/src/index.ts
CHANGED
package/src/ingots.test-d.ts
CHANGED
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
* and every assertion would pass. `tsc --noEmit` (`pnpm typecheck`) is the gate
|
|
7
7
|
* instead. It is deliberately NOT a `*.test.ts`, which tsconfig excludes.
|
|
8
8
|
*
|
|
9
|
-
* Mirrored server
|
|
10
|
-
* the one shape GET and PUT /vaults/:id/ingots/:ingot_id/sharing answer)
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* Mirrored server sources: apps/api/src/ingots/sharing.js (`formatSharingConfig`,
|
|
10
|
+
* the one shape GET and PUT /vaults/:id/ingots/:ingot_id/sharing answer),
|
|
11
|
+
* apps/api/src/repositories/IngotRepository.js (`toPublicFormat`, the one
|
|
12
|
+
* projection every ingot answer uses) and apps/api/src/lib/pagination.js
|
|
13
|
+
* (`buildPaginatedResponse`: one page, `{ ingots, count, has_more,
|
|
14
|
+
* next_cursor? }`). A field the server never sends reads as undefined wherever
|
|
15
|
+
* an app trusts it: a link section keyed on it never renders.
|
|
13
16
|
*/
|
|
14
17
|
|
|
15
|
-
import type { IngotSharingConfig } from './types.js';
|
|
18
|
+
import type { Ingot, IngotSharingConfig, ListIngotsResponse } from './types.js';
|
|
16
19
|
|
|
17
20
|
/** Fails to compile unless `T` resolves to exactly `true`. */
|
|
18
21
|
type Assert<T extends true> = T;
|
|
@@ -43,4 +46,24 @@ export type IngotsWireContract = [
|
|
|
43
46
|
Assert<Equals<IngotSharingConfig['public_url'], string | null>>,
|
|
44
47
|
Assert<Equals<IngotSharingConfig['link_code'], string | null>>,
|
|
45
48
|
Assert<Equals<IngotSharingConfig['visibility'], 'invite_only' | 'authenticated' | 'public' | null>>,
|
|
49
|
+
// A page reports its own length and whether more follow; it carries no total.
|
|
50
|
+
Assert<Equals<keyof ListIngotsResponse, 'ingots' | 'count' | 'has_more' | 'next_cursor'>>,
|
|
51
|
+
// Fields toPublicFormat projects, and none it keeps to the row (storage
|
|
52
|
+
// location, last access, the abandoned-upload expiry).
|
|
53
|
+
Assert<
|
|
54
|
+
Equals<
|
|
55
|
+
keyof Ingot,
|
|
56
|
+
| 'ingot_id'
|
|
57
|
+
| 'name'
|
|
58
|
+
| 'content_type'
|
|
59
|
+
| 'size_bytes'
|
|
60
|
+
| 'status'
|
|
61
|
+
| 'folder_id'
|
|
62
|
+
| 'managed_by'
|
|
63
|
+
| 'created_at'
|
|
64
|
+
| 'access_count'
|
|
65
|
+
| 'content_hmac'
|
|
66
|
+
| 'content_version'
|
|
67
|
+
>
|
|
68
|
+
>,
|
|
46
69
|
];
|
package/src/ingots.ts
CHANGED
|
@@ -121,8 +121,12 @@ export interface UploadFromUriOptions extends UploadOptions {
|
|
|
121
121
|
debug?: DebugLogger;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
/**
|
|
125
|
-
|
|
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;
|
|
@@ -411,7 +410,6 @@ export class MobileIngotsClient {
|
|
|
411
410
|
ingot_id: init.ingotId,
|
|
412
411
|
name,
|
|
413
412
|
size_bytes: init.sizeBytes ?? options.fileSize,
|
|
414
|
-
storage_location: 's3',
|
|
415
413
|
// A server that does not report it minted the row (the upsert is the
|
|
416
414
|
// only other outcome, and it always says so).
|
|
417
415
|
created,
|
|
@@ -464,7 +462,6 @@ export class MobileIngotsClient {
|
|
|
464
462
|
ingot_id: options.ingotId,
|
|
465
463
|
name: options.name,
|
|
466
464
|
size_bytes: options.fileSize,
|
|
467
|
-
storage_location: 's3',
|
|
468
465
|
created: false,
|
|
469
466
|
status: ingot.status,
|
|
470
467
|
...publicationOf(ingot),
|
|
@@ -600,7 +597,6 @@ export class MobileIngotsClient {
|
|
|
600
597
|
ingot_id: response.data.ingot_id,
|
|
601
598
|
name,
|
|
602
599
|
size_bytes: fileSize,
|
|
603
|
-
storage_location: 's3',
|
|
604
600
|
// A replace never mints an ingot: the row is the caller's existing one.
|
|
605
601
|
created: false,
|
|
606
602
|
status: ingot.status,
|
|
@@ -868,18 +864,12 @@ export class MobileIngotsClient {
|
|
|
868
864
|
return response.data;
|
|
869
865
|
}
|
|
870
866
|
|
|
871
|
-
async addInvite(
|
|
872
|
-
vaultId: string,
|
|
873
|
-
ingotId: string,
|
|
874
|
-
vat: string,
|
|
875
|
-
identity: string,
|
|
876
|
-
options: AddInviteOptions = {}
|
|
877
|
-
): Promise<IngotInvite> {
|
|
867
|
+
async addInvite(vaultId: string, ingotId: string, vat: string, identity: string): Promise<IngotInvite> {
|
|
878
868
|
validateVaultId(vaultId);
|
|
879
869
|
validateIngotId(ingotId);
|
|
880
870
|
const response = await this.http.post<IngotInvite>(
|
|
881
871
|
apiPath`/vaults/${vaultId}/ingots/${ingotId}/sharing/invite`,
|
|
882
|
-
{ identity
|
|
872
|
+
{ identity },
|
|
883
873
|
{ vat }
|
|
884
874
|
);
|
|
885
875
|
return response.data;
|
|
@@ -925,19 +915,7 @@ export class MobileIngotsClient {
|
|
|
925
915
|
validateVaultId(vaultId);
|
|
926
916
|
validateIngotId(ingotId);
|
|
927
917
|
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
|
-
);
|
|
918
|
+
return { total_downloads: ingot.access_count ?? 0 };
|
|
941
919
|
}
|
|
942
920
|
|
|
943
921
|
/**
|
package/src/products/identity.ts
CHANGED
|
@@ -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<
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
633
|
-
|
|
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
|
|
|
@@ -31,9 +31,12 @@ export interface SparkResponse {
|
|
|
31
31
|
export interface CreateSparkOptions {
|
|
32
32
|
payload: string;
|
|
33
33
|
contentType?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Time-to-live: a whole number of minutes from 1 to 1440 (24h). Defaults to
|
|
36
|
+
* 1440 server-side. A fraction is refused with a 400 `VALIDATION_ERROR`.
|
|
37
|
+
*/
|
|
34
38
|
ttlMinutes?: number;
|
|
35
39
|
kindling?: string;
|
|
36
|
-
name?: string;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export interface CreateSparkResponse {
|
|
@@ -75,7 +78,6 @@ export class MobileSparksClient {
|
|
|
75
78
|
content_type: options.contentType,
|
|
76
79
|
ttl_minutes: options.ttlMinutes,
|
|
77
80
|
kindling: options.kindling,
|
|
78
|
-
name: options.name,
|
|
79
81
|
});
|
|
80
82
|
return response.data;
|
|
81
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
|
-
|
|
384
|
-
|
|
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
|
-
*
|
|
412
|
-
* the
|
|
413
|
-
* `
|
|
414
|
-
*
|
|
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
|
-
|
|
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
|
}
|