@sparkvault/sdk-mobile 6.0.2 → 7.0.1
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 +5 -5
- package/dist/account.d.ts +4 -3
- package/dist/account.js +16 -11
- package/dist/account.js.map +1 -1
- package/dist/app-sdk/email-address.d.ts +37 -0
- package/dist/app-sdk/email-address.js +91 -0
- package/dist/app-sdk/email-address.js.map +1 -0
- package/dist/app-sdk/encoding.d.ts +64 -0
- package/dist/app-sdk/encoding.js +245 -0
- package/dist/app-sdk/encoding.js.map +1 -0
- package/dist/app-sdk/entropy-contract.d.ts +33 -0
- package/dist/app-sdk/entropy-contract.js +51 -0
- package/dist/app-sdk/entropy-contract.js.map +1 -0
- package/dist/app-sdk/filename.d.ts +93 -0
- package/dist/app-sdk/filename.js +161 -0
- package/dist/app-sdk/filename.js.map +1 -0
- package/dist/app-sdk/forge-url.d.ts +107 -0
- package/dist/app-sdk/forge-url.js +222 -0
- package/dist/app-sdk/forge-url.js.map +1 -0
- package/dist/app-sdk/ids.d.ts +48 -0
- package/dist/app-sdk/ids.js +65 -0
- package/dist/app-sdk/ids.js.map +1 -0
- package/dist/app-sdk/ingot-access-log.d.ts +29 -0
- package/dist/app-sdk/ingot-access-log.js +49 -0
- package/dist/app-sdk/ingot-access-log.js.map +1 -0
- package/dist/app-sdk/phone.d.ts +37 -0
- package/dist/app-sdk/phone.js +134 -0
- package/dist/app-sdk/phone.js.map +1 -0
- package/dist/app-sdk/platform-domains.d.ts +41 -0
- package/dist/app-sdk/platform-domains.js +82 -0
- package/dist/app-sdk/platform-domains.js.map +1 -0
- package/dist/app-sdk/request-path.d.ts +49 -0
- package/dist/app-sdk/request-path.js +81 -0
- package/dist/app-sdk/request-path.js.map +1 -0
- package/dist/app-sdk/transfer-retry.d.ts +298 -0
- package/dist/app-sdk/transfer-retry.js +535 -0
- package/dist/app-sdk/transfer-retry.js.map +1 -0
- package/dist/app-sdk/transient.d.ts +23 -0
- package/dist/app-sdk/transient.js +29 -0
- package/dist/app-sdk/transient.js.map +1 -0
- package/dist/app-sdk/upload-publication.d.ts +81 -0
- package/dist/app-sdk/upload-publication.js +93 -0
- package/dist/app-sdk/upload-publication.js.map +1 -0
- package/dist/app-sdk/vat-refusal.d.ts +66 -0
- package/dist/app-sdk/vat-refusal.js +69 -0
- package/dist/app-sdk/vat-refusal.js.map +1 -0
- package/dist/billing.d.ts +9 -17
- package/dist/billing.js +14 -17
- package/dist/billing.js.map +1 -1
- package/dist/config.d.ts +33 -1
- package/dist/config.js +23 -17
- package/dist/config.js.map +1 -1
- package/dist/encoding.d.ts +30 -4
- package/dist/encoding.js +89 -83
- package/dist/encoding.js.map +1 -1
- package/dist/entropy.d.ts +25 -4
- package/dist/entropy.js +62 -15
- package/dist/entropy.js.map +1 -1
- package/dist/entropy.test-d.d.ts +40 -0
- package/dist/entropy.test-d.js +12 -0
- package/dist/entropy.test-d.js.map +1 -0
- package/dist/errors.d.ts +105 -21
- package/dist/errors.js +74 -28
- package/dist/errors.js.map +1 -1
- package/dist/folders.js +8 -7
- package/dist/folders.js.map +1 -1
- package/dist/http.d.ts +57 -15
- package/dist/http.js +125 -138
- package/dist/http.js.map +1 -1
- package/dist/identity-dialog.js +4 -30
- package/dist/identity-dialog.js.map +1 -1
- package/dist/identity-input.d.ts +39 -0
- package/dist/identity-input.js +59 -0
- package/dist/identity-input.js.map +1 -0
- package/dist/index.d.ts +8 -7
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/ingots.d.ts +77 -32
- package/dist/ingots.js +152 -136
- package/dist/ingots.js.map +1 -1
- package/dist/mutex.d.ts +1 -2
- package/dist/mutex.js +0 -6
- package/dist/mutex.js.map +1 -1
- package/dist/notify.d.ts +7 -2
- package/dist/notify.js +12 -15
- package/dist/notify.js.map +1 -1
- package/dist/notify.test-d.d.ts +3 -2
- package/dist/notify.test-d.js +1 -1
- package/dist/products/identity.d.ts +5 -0
- package/dist/products/identity.js +40 -35
- package/dist/products/identity.js.map +1 -1
- package/dist/request-path.d.ts +32 -0
- package/dist/request-path.js +35 -0
- package/dist/request-path.js.map +1 -0
- package/dist/sparks.js +16 -15
- package/dist/sparks.js.map +1 -1
- package/dist/tus.d.ts +198 -16
- package/dist/tus.js +816 -327
- package/dist/tus.js.map +1 -1
- package/dist/types.d.ts +183 -23
- package/dist/users.d.ts +12 -5
- package/dist/users.js +27 -19
- package/dist/users.js.map +1 -1
- package/dist/validation.d.ts +20 -5
- package/dist/validation.js +29 -27
- package/dist/validation.js.map +1 -1
- package/dist/vaults.d.ts +34 -83
- package/dist/vaults.js +64 -44
- package/dist/vaults.js.map +1 -1
- package/dist/vaults.test-d.d.ts +22 -0
- package/dist/vaults.test-d.js +14 -0
- package/dist/vaults.test-d.js.map +1 -0
- package/package.json +4 -2
- package/src/account.ts +16 -11
- package/src/app-sdk/email-address.js +94 -0
- package/src/app-sdk/encoding.js +250 -0
- package/src/app-sdk/entropy-contract.js +56 -0
- package/src/app-sdk/filename.js +174 -0
- package/src/app-sdk/forge-url.js +224 -0
- package/src/app-sdk/ids.js +71 -0
- package/src/app-sdk/ingot-access-log.js +51 -0
- package/src/app-sdk/phone.js +139 -0
- package/src/app-sdk/platform-domains.js +99 -0
- package/src/app-sdk/request-path.js +81 -0
- package/src/app-sdk/transfer-retry.js +563 -0
- package/src/app-sdk/transient.js +30 -0
- package/src/app-sdk/upload-publication.js +102 -0
- package/src/app-sdk/vat-refusal.js +74 -0
- package/src/billing.ts +16 -25
- package/src/config.ts +56 -23
- package/src/encoding.ts +91 -103
- package/src/entropy.test-d.ts +47 -0
- package/src/entropy.ts +86 -20
- package/src/errors.ts +135 -38
- package/src/folders.ts +8 -7
- package/src/http.ts +145 -150
- package/src/identity-dialog.tsx +4 -37
- package/src/identity-input.ts +70 -0
- package/src/index.ts +13 -3
- package/src/ingots.ts +225 -180
- package/src/mutex.ts +1 -8
- package/src/notify.test-d.ts +3 -1
- package/src/notify.ts +19 -17
- package/src/products/identity.ts +45 -39
- package/src/request-path.ts +45 -0
- package/src/sparks.ts +16 -13
- package/src/tus.ts +1054 -408
- package/src/types.ts +181 -23
- package/src/users.ts +27 -17
- package/src/validation.ts +30 -24
- package/src/vaults.test-d.ts +29 -0
- package/src/vaults.ts +87 -153
- package/dist/platform-domains.generated.d.ts +0 -2
- package/dist/platform-domains.generated.js +0 -16
- package/dist/platform-domains.generated.js.map +0 -1
- package/src/platform-domains.generated.ts +0 -17
package/src/types.ts
CHANGED
|
@@ -5,7 +5,9 @@ export type PoolState = 'ok' | 'notice' | 'warning' | 'exhausted';
|
|
|
5
5
|
export interface PoolStatus {
|
|
6
6
|
used_gb: number;
|
|
7
7
|
limit_gb: number | null;
|
|
8
|
+
/** Used fraction of the pool: 0.85 is 85% full, 1 is full (above 1 when over). */
|
|
8
9
|
pct: number;
|
|
10
|
+
/** The server's severity for the pool, from the canonical thresholds; display keys off it. */
|
|
9
11
|
state: PoolState;
|
|
10
12
|
}
|
|
11
13
|
|
|
@@ -137,6 +139,12 @@ export interface RemoveMemberResponse {
|
|
|
137
139
|
user_id: string;
|
|
138
140
|
removed_at: number;
|
|
139
141
|
dvaks_revoked: number;
|
|
142
|
+
/**
|
|
143
|
+
* True when this removal ends the subscription: the member renewed the last
|
|
144
|
+
* license it renews, and it was not already set to end. It ends at the
|
|
145
|
+
* period end.
|
|
146
|
+
*/
|
|
147
|
+
wind_down: boolean;
|
|
140
148
|
}
|
|
141
149
|
|
|
142
150
|
export interface ResendInviteResponse {
|
|
@@ -177,7 +185,7 @@ export interface SparkVaultSubscription {
|
|
|
177
185
|
renewal_date: number | null;
|
|
178
186
|
renewal_amount_usd: string;
|
|
179
187
|
seats: {
|
|
180
|
-
/**
|
|
188
|
+
/** Licenses the subscription renews with — what the next renewal bills. */
|
|
181
189
|
full: number;
|
|
182
190
|
viewer: number;
|
|
183
191
|
licensed: number;
|
|
@@ -187,6 +195,18 @@ export interface SparkVaultSubscription {
|
|
|
187
195
|
assigned_viewer: number;
|
|
188
196
|
/** Roster members with no license (no access until licensed; free). */
|
|
189
197
|
unlicensed_members: number;
|
|
198
|
+
/** Licenses paid for the current billing period (a removed license stays
|
|
199
|
+
* paid until the renewal). */
|
|
200
|
+
paid: { full: number; viewer: number };
|
|
201
|
+
/** Paid licenses nobody holds — assignable at no charge until the renewal. */
|
|
202
|
+
available: { full: number; viewer: number };
|
|
203
|
+
/**
|
|
204
|
+
* Licenses are prepaid per period on a web subscription: a removed one
|
|
205
|
+
* stays paid until the renewal, free to give to someone else until then.
|
|
206
|
+
* False on a store subscription (its one license lands on a member by
|
|
207
|
+
* itself) and on an account with no web subscription.
|
|
208
|
+
*/
|
|
209
|
+
prepaid: boolean;
|
|
190
210
|
};
|
|
191
211
|
blocks: {
|
|
192
212
|
/** Effective capacity blocks (this subscription plus any add-on rail). */
|
|
@@ -204,6 +224,21 @@ export interface SparkVaultSubscription {
|
|
|
204
224
|
/** Native in-app purchase store channel for receipt validation. */
|
|
205
225
|
export type IapPlatform = 'ios' | 'android';
|
|
206
226
|
|
|
227
|
+
/** The store rail a validated purchase is billed on. */
|
|
228
|
+
export type IapChannel = 'appstore' | 'play';
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* POST /v1/billing/iap/validate — whether Core provisioned the purchase: a
|
|
232
|
+
* provisioning status, not a subscription (read the subscription again with
|
|
233
|
+
* `billing.getSubscription()` to show what changed). A purchase the store
|
|
234
|
+
* reports as not entitling (expired, refunded, revoked or not yet paid) is
|
|
235
|
+
* answered, not refused, and provisions nothing; every other refusal is an
|
|
236
|
+
* error. `addon` marks a capacity add-on stacked on the license.
|
|
237
|
+
*/
|
|
238
|
+
export type IapValidation =
|
|
239
|
+
| { provisioned: true; channel: IapChannel; addon?: true }
|
|
240
|
+
| { provisioned: false; reason: 'not_entitling' };
|
|
241
|
+
|
|
207
242
|
export type IdentityType = 'email' | 'phone';
|
|
208
243
|
export type VerifiedIdentityType = IdentityType | 'social';
|
|
209
244
|
|
|
@@ -542,9 +577,104 @@ export interface UnsealVaultResponse {
|
|
|
542
577
|
ttl_seconds: number;
|
|
543
578
|
}
|
|
544
579
|
|
|
580
|
+
/** One page of GET /v1/vaults. */
|
|
545
581
|
export interface ListVaultsResponse {
|
|
546
582
|
vaults: Vault[];
|
|
547
|
-
|
|
583
|
+
count: number;
|
|
584
|
+
has_more: boolean;
|
|
585
|
+
next_cursor?: string;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/** A vault's public sharing policy (GET /v1/vaults/{id}/sharing). */
|
|
589
|
+
export interface VaultSharingConfig {
|
|
590
|
+
vault_id: string;
|
|
591
|
+
public_sharing_enabled: boolean;
|
|
592
|
+
all_ingots_public: boolean;
|
|
593
|
+
session_length_seconds: number | null;
|
|
594
|
+
enabled_at: number | null;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/** The sharing policy a caller sets when enabling or updating vault sharing. */
|
|
598
|
+
export interface VaultSharingOptions {
|
|
599
|
+
all_ingots_public?: boolean;
|
|
600
|
+
session_length_seconds?: number;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export interface EnableVaultSharingResponse {
|
|
604
|
+
vault_id: string;
|
|
605
|
+
public_sharing_enabled: boolean;
|
|
606
|
+
all_ingots_public: boolean;
|
|
607
|
+
session_length_seconds: number;
|
|
608
|
+
enabled_at: number;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export interface DisableVaultSharingResponse {
|
|
612
|
+
vault_id: string;
|
|
613
|
+
public_sharing_enabled: false;
|
|
614
|
+
disabled_at: number;
|
|
615
|
+
shares_revoked: number;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export interface UpdateVaultSharingResponse {
|
|
619
|
+
vault_id: string;
|
|
620
|
+
public_sharing_enabled: boolean;
|
|
621
|
+
all_ingots_public: boolean;
|
|
622
|
+
session_length_seconds: number;
|
|
623
|
+
updated_at: number;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** How long a vault keeps its access log (GET /v1/vaults/{id}/access-log-retention). */
|
|
627
|
+
export interface AccessLogRetention {
|
|
628
|
+
vault_id: string;
|
|
629
|
+
access_log_retention_seconds: number;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export interface UpdateAccessLogRetentionResponse extends AccessLogRetention {
|
|
633
|
+
updated_at: number;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/** A vault's upload portal and widget settings (GET /v1/vaults/{id}/upload). */
|
|
637
|
+
export interface UploadConfig {
|
|
638
|
+
vault_id: string;
|
|
639
|
+
upload_portal_enabled: boolean;
|
|
640
|
+
upload_portal_enabled_at: number | null;
|
|
641
|
+
upload_widget_enabled: boolean;
|
|
642
|
+
upload_widget_enabled_at: number | null;
|
|
643
|
+
max_size_bytes: number | null;
|
|
644
|
+
notification_email: string | null;
|
|
645
|
+
portal_url?: string;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export interface UploadPortalEnabled {
|
|
649
|
+
vault_id: string;
|
|
650
|
+
upload_portal_enabled: true;
|
|
651
|
+
enabled_at: number;
|
|
652
|
+
portal_url: string;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
export interface UploadWidgetEnabled {
|
|
656
|
+
vault_id: string;
|
|
657
|
+
upload_widget_enabled: true;
|
|
658
|
+
enabled_at: number;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
export interface UploadPortalDisabled {
|
|
662
|
+
vault_id: string;
|
|
663
|
+
upload_portal_enabled: false;
|
|
664
|
+
disabled_at: number;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
export interface UploadWidgetDisabled {
|
|
668
|
+
vault_id: string;
|
|
669
|
+
upload_widget_enabled: false;
|
|
670
|
+
disabled_at: number;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
export interface UploadConfigUpdate {
|
|
674
|
+
vault_id: string;
|
|
675
|
+
max_size_bytes: number;
|
|
676
|
+
notification_email: string | null;
|
|
677
|
+
updated_at: number;
|
|
548
678
|
}
|
|
549
679
|
|
|
550
680
|
export interface Ingot {
|
|
@@ -553,7 +683,8 @@ export interface Ingot {
|
|
|
553
683
|
content_type: string;
|
|
554
684
|
size_bytes: number;
|
|
555
685
|
storage_location: 'dynamodb' | 's3';
|
|
556
|
-
|
|
686
|
+
/** `uploading` until Forge activates the upload, then `active`. */
|
|
687
|
+
status?: 'uploading' | 'active';
|
|
557
688
|
folder_id?: string | null;
|
|
558
689
|
created_at: number;
|
|
559
690
|
accessed_at?: number;
|
|
@@ -586,24 +717,36 @@ export interface ListIngotsResponse {
|
|
|
586
717
|
next_cursor?: string;
|
|
587
718
|
}
|
|
588
719
|
|
|
720
|
+
/**
|
|
721
|
+
* GET /vaults/:id/contents: one folder whole, in one response — the folder
|
|
722
|
+
* (null at the vault root), the trail from the root to it, its direct
|
|
723
|
+
* subfolders (pinned first) and every ingot directly in it.
|
|
724
|
+
*/
|
|
725
|
+
export interface FolderContentsResponse {
|
|
726
|
+
folder: { folder_id: string; name: string; parent_id: string | null } | null;
|
|
727
|
+
breadcrumb: Array<{ folder_id: string | null; name: string }>;
|
|
728
|
+
folders: Folder[];
|
|
729
|
+
ingots: Ingot[];
|
|
730
|
+
}
|
|
731
|
+
|
|
589
732
|
export interface UploadResult {
|
|
590
733
|
ingot_id: string;
|
|
591
734
|
name: string;
|
|
592
735
|
size_bytes: number;
|
|
593
736
|
storage_location: 's3';
|
|
594
737
|
/**
|
|
595
|
-
* Whether this upload minted the ingot (true) or
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
*
|
|
738
|
+
* Whether this upload minted the ingot (true) or went into an existing row
|
|
739
|
+
* (false): the one holding the name, as the create's name-conflict policy
|
|
740
|
+
* allowed, a replace, or a resumed session. A caller that discards its
|
|
741
|
+
* ingot on a failure path needs this to tell "mine to discard" from
|
|
742
|
+
* "someone else's live row".
|
|
599
743
|
*/
|
|
600
744
|
created?: boolean;
|
|
601
745
|
/**
|
|
602
|
-
* The ingot status the upload
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
* faith (resume the session later; Forge re-confirms finalization).
|
|
746
|
+
* The ingot status the upload confirmed: `active`, the server's read of
|
|
747
|
+
* the published ingot. An upload whose activation the poll budget could
|
|
748
|
+
* not confirm never resolves: it rejects with a `TusUploadError` whose
|
|
749
|
+
* `retryLater` is set.
|
|
607
750
|
*/
|
|
608
751
|
status?: Ingot['status'];
|
|
609
752
|
}
|
|
@@ -623,9 +766,19 @@ export interface IngotInvite {
|
|
|
623
766
|
created_at: number;
|
|
624
767
|
}
|
|
625
768
|
|
|
769
|
+
/**
|
|
770
|
+
* An ingot access-log entry: a download that delivered the file, a SparkLink
|
|
771
|
+
* open of it included. The server records an access as the download that
|
|
772
|
+
* delivered it, so `download` is the one action.
|
|
773
|
+
*/
|
|
626
774
|
export interface IngotAccessLog {
|
|
775
|
+
/**
|
|
776
|
+
* The entry's time (Unix seconds) as a string. The server issues no entry id,
|
|
777
|
+
* so entries in the same second share it.
|
|
778
|
+
*/
|
|
627
779
|
log_id: string;
|
|
628
|
-
action: 'download'
|
|
780
|
+
action: 'download';
|
|
781
|
+
/** Who accessed: a member's email, an API key's name, or a SparkLink visitor's identity */
|
|
629
782
|
identity: string;
|
|
630
783
|
ip_address: string;
|
|
631
784
|
user_agent: string;
|
|
@@ -638,18 +791,14 @@ export interface IngotAccessStats {
|
|
|
638
791
|
last_accessed_at?: number;
|
|
639
792
|
}
|
|
640
793
|
|
|
794
|
+
/** A folder as every folder route answers it (Core's FolderRepository.toPublicFormat). */
|
|
641
795
|
export interface Folder {
|
|
642
796
|
folder_id: string;
|
|
643
|
-
vault_id: string;
|
|
644
797
|
name: string;
|
|
645
798
|
parent_id: string | null;
|
|
646
|
-
is_system: boolean;
|
|
647
799
|
pinned: boolean;
|
|
648
800
|
created_at: number;
|
|
649
801
|
updated_at?: number;
|
|
650
|
-
path?: string;
|
|
651
|
-
depth?: number;
|
|
652
|
-
ingot_count?: number;
|
|
653
802
|
}
|
|
654
803
|
|
|
655
804
|
export interface CreateFolderRequest {
|
|
@@ -663,9 +812,9 @@ export interface UpdateFolderRequest {
|
|
|
663
812
|
pinned?: boolean;
|
|
664
813
|
}
|
|
665
814
|
|
|
815
|
+
/** GET /v1/vaults/{id}/folders: the vault's whole folder list. */
|
|
666
816
|
export interface ListFoldersResponse {
|
|
667
817
|
folders: Folder[];
|
|
668
|
-
total: number;
|
|
669
818
|
}
|
|
670
819
|
|
|
671
820
|
export interface FolderBreadcrumbItem {
|
|
@@ -716,14 +865,23 @@ export interface MobileFileReader {
|
|
|
716
865
|
|
|
717
866
|
/**
|
|
718
867
|
* A tus upload session as Forge created it: the session URL, the per-upload
|
|
719
|
-
* token every request on it must carry,
|
|
720
|
-
*
|
|
721
|
-
* relaunch (`ingots.resumeUpload`) instead of re-sending bytes
|
|
868
|
+
* token every request on it must carry, the chunk geometry Forge fixed for
|
|
869
|
+
* it, and when it lapses. Persist it to resume the upload after a failed
|
|
870
|
+
* attempt or a relaunch (`ingots.resumeUpload`) instead of re-sending bytes
|
|
871
|
+
* Forge holds.
|
|
722
872
|
*/
|
|
723
873
|
export interface TusSessionInfo {
|
|
724
874
|
uploadUrl: string;
|
|
725
875
|
istk: string;
|
|
726
876
|
chunkSize: number;
|
|
877
|
+
/**
|
|
878
|
+
* When the session lapses (epoch ms, Forge's `Upload-Expires`), or null
|
|
879
|
+
* when Forge named no expiry. Forge slides it forward on every committed
|
|
880
|
+
* chunk, so a caller that persists the session keeps this current through
|
|
881
|
+
* the upload's `onSessionExpiry`; past it, Forge refuses every request to
|
|
882
|
+
* the session (`isSessionExpired`).
|
|
883
|
+
*/
|
|
884
|
+
expiresAt: number | null;
|
|
727
885
|
}
|
|
728
886
|
|
|
729
887
|
/**
|
|
@@ -919,7 +1077,7 @@ export type SparkShareStatus =
|
|
|
919
1077
|
link_code: string;
|
|
920
1078
|
visibility: string;
|
|
921
1079
|
invites: string[];
|
|
922
|
-
/** Single-use lifecycle: 'active' → 'consumed' (opened once)
|
|
1080
|
+
/** Single-use lifecycle: 'active' → 'consumed' (opened once). */
|
|
923
1081
|
status: string;
|
|
924
1082
|
expires_at?: number;
|
|
925
1083
|
created_at?: number;
|
package/src/users.ts
CHANGED
|
@@ -7,15 +7,17 @@ import type {
|
|
|
7
7
|
RemoveMemberResponse,
|
|
8
8
|
ResendInviteResponse,
|
|
9
9
|
} from './types.js';
|
|
10
|
+
import { apiPath } from './request-path.js';
|
|
11
|
+
import { validateResourceId } from './validation.js';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Organization team client (member admins only — the API gates these on the
|
|
13
15
|
* admin role and returns 403 otherwise).
|
|
14
16
|
*
|
|
15
|
-
* Licensing is a billing action (
|
|
16
|
-
*
|
|
17
|
-
* operation exposed here is `invite`, which defaults new members
|
|
18
|
-
* `unlicensed` and never charges. Assigning or changing a paid license is
|
|
17
|
+
* Licensing is a billing action (licenses are prepaid: assigning or upgrading
|
|
18
|
+
* one charges the rest of the current billing period at once), so the only
|
|
19
|
+
* seat-affecting operation exposed here is `invite`, which defaults new members
|
|
20
|
+
* to `unlicensed` and never charges. Assigning or changing a paid license is
|
|
19
21
|
* intentionally absent — clients route that to the web dashboard so native
|
|
20
22
|
* apps never initiate an out-of-band purchase.
|
|
21
23
|
*/
|
|
@@ -28,33 +30,41 @@ export class MobileUsersClient {
|
|
|
28
30
|
|
|
29
31
|
/** List organization members (active and invited), with optional search/filter. */
|
|
30
32
|
async list(options: ListMembersOptions = {}): Promise<ListMembersResponse> {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
);
|
|
33
|
+
const response = await this.http.get<ListMembersResponse>(apiPath`/users`, {
|
|
34
|
+
query: {
|
|
35
|
+
search: options.search,
|
|
36
|
+
status: options.status,
|
|
37
|
+
limit: typeof options.limit === 'number' ? options.limit : undefined,
|
|
38
|
+
cursor: options.cursor,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
40
41
|
return response.data;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/** Invite a coworker by email. Defaults to an unlicensed seat (no charge). */
|
|
44
45
|
async invite(request: InviteMemberRequest): Promise<InviteMemberResponse> {
|
|
45
|
-
const response = await this.http.post<InviteMemberResponse>(
|
|
46
|
+
const response = await this.http.post<InviteMemberResponse>(apiPath`/users/invite`, request);
|
|
46
47
|
return response.data;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Remove a member and revoke their DVAKs. Access ends immediately. A license
|
|
52
|
+
* they held stays paid until the end of the billing period — nothing is
|
|
53
|
+
* refunded — and is not renewed; until then it can be given to someone else
|
|
54
|
+
* at no charge. `wind_down` is true when this removal ends the
|
|
55
|
+
* subscription — theirs was the last license it renews, and it was not
|
|
56
|
+
* already set to end: it ends at the period end.
|
|
57
|
+
*/
|
|
50
58
|
async remove(userId: string): Promise<RemoveMemberResponse> {
|
|
51
|
-
|
|
59
|
+
validateResourceId(userId, 'user ID', 'userId');
|
|
60
|
+
const response = await this.http.delete<RemoveMemberResponse>(apiPath`/users/${userId}`);
|
|
52
61
|
return response.data;
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
/** Resend a pending invitation email (resets the 7-day expiry). */
|
|
56
65
|
async resendInvite(userId: string): Promise<ResendInviteResponse> {
|
|
57
|
-
|
|
66
|
+
validateResourceId(userId, 'user ID', 'userId');
|
|
67
|
+
const response = await this.http.post<ResendInviteResponse>(apiPath`/users/${userId}/resend-invite`);
|
|
58
68
|
return response.data;
|
|
59
69
|
}
|
|
60
70
|
}
|
package/src/validation.ts
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
|
+
import { isResourceId } from './app-sdk/ids.js';
|
|
1
2
|
import { SparkVaultValidationError } from './errors.js';
|
|
3
|
+
import { SAVED_FILENAME_FALLBACK, toStorableFilename } from './app-sdk/filename.js';
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/** Whether a value is a well-formed SparkVault resource id (the platform's one rule). */
|
|
6
|
+
export { isResourceId as isSafeId };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Refuse a value that is not a SparkVault resource id before it names a route.
|
|
10
|
+
*
|
|
11
|
+
* @param label - How the message names the id, e.g. 'spark ID'.
|
|
12
|
+
* @param field - The argument's name, reported in the error details.
|
|
13
|
+
*/
|
|
14
|
+
export function validateResourceId(value: unknown, label: string, field: string): void {
|
|
15
|
+
if (!isResourceId(value)) {
|
|
16
|
+
throw new SparkVaultValidationError(`Invalid ${label} format`, { [field]: value });
|
|
17
|
+
}
|
|
8
18
|
}
|
|
9
19
|
|
|
10
20
|
export function validateVaultId(vaultId: string): void {
|
|
11
|
-
|
|
12
|
-
throw new SparkVaultValidationError('Invalid vault ID format', { vaultId });
|
|
13
|
-
}
|
|
21
|
+
validateResourceId(vaultId, 'vault ID', 'vaultId');
|
|
14
22
|
}
|
|
15
23
|
|
|
16
24
|
export function validateIngotId(ingotId: string): void {
|
|
17
|
-
|
|
18
|
-
throw new SparkVaultValidationError('Invalid ingot ID format', { ingotId });
|
|
19
|
-
}
|
|
25
|
+
validateResourceId(ingotId, 'ingot ID', 'ingotId');
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
export function validateFolderId(folderId: string): void {
|
|
23
|
-
|
|
24
|
-
throw new SparkVaultValidationError('Invalid folder ID format', { folderId });
|
|
25
|
-
}
|
|
29
|
+
validateResourceId(folderId, 'folder ID', 'folderId');
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
/** Windows reserved filenames that cannot be used. */
|
|
@@ -33,17 +37,19 @@ const WINDOWS_RESERVED_NAMES = new Set([
|
|
|
33
37
|
]);
|
|
34
38
|
|
|
35
39
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
+
* The file name to store or save text under, by the platform's one file-name
|
|
41
|
+
* rule (@sparkvault/app-sdk/filename, mirrored into ./app-sdk/filename.js):
|
|
42
|
+
* path separators, control characters and the bidi controls that disguise an
|
|
43
|
+
* extension (`invoice<U+202E>fdp.exe` shows as `invoiceexe.pdf`) become `_`,
|
|
44
|
+
* and so do the characters SparkVault refuses in a name (: * ? " < > |), so a
|
|
45
|
+
* name derived from a note's title uploads. With every separator gone no name
|
|
46
|
+
* can climb a directory, so a `..` inside a name is kept. On top of the rule,
|
|
47
|
+
* for the device's file system: leading dots are stripped (a hidden file) and
|
|
48
|
+
* Windows reserved names are prefixed. Falls back to the platform's one
|
|
49
|
+
* fallback name (SAVED_FILENAME_FALLBACK) when nothing remains.
|
|
40
50
|
*/
|
|
41
51
|
export function sanitizeFilename(filename: string): string {
|
|
42
|
-
let sanitized = filename
|
|
43
|
-
.replace(/\0/g, '')
|
|
44
|
-
.replace(/[\x00-\x1f\x7f]/g, '')
|
|
45
|
-
.replace(/\.\./g, '')
|
|
46
|
-
.replace(/[\/\\]/g, '_')
|
|
52
|
+
let sanitized = toStorableFilename(filename)
|
|
47
53
|
.replace(/^\.+/, '')
|
|
48
54
|
.trim();
|
|
49
55
|
|
|
@@ -52,5 +58,5 @@ export function sanitizeFilename(filename: string): string {
|
|
|
52
58
|
sanitized = `_${sanitized}`;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
return sanitized ||
|
|
61
|
+
return sanitized || SAVED_FILENAME_FALLBACK;
|
|
56
62
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault and folder wire-contract assertions — COMPILE-TIME ONLY.
|
|
3
|
+
*
|
|
4
|
+
* These types have no runtime, so a unit test can never catch them drifting from
|
|
5
|
+
* the server: the transpiler strips them and every assertion passes. `tsc
|
|
6
|
+
* --noEmit` (`pnpm typecheck`) is the gate instead. It is deliberately NOT a
|
|
7
|
+
* `*.test.ts`, which tsconfig excludes.
|
|
8
|
+
*
|
|
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? }`).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ListFoldersResponse, ListVaultsResponse } from './types.js';
|
|
15
|
+
|
|
16
|
+
/** Fails to compile unless `T` resolves to exactly `true`. */
|
|
17
|
+
type Assert<T extends true> = T;
|
|
18
|
+
|
|
19
|
+
/** Invariant type equality: the SAME type, not merely mutually assignable. */
|
|
20
|
+
type Equals<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
|
|
21
|
+
? true
|
|
22
|
+
: false;
|
|
23
|
+
|
|
24
|
+
export type VaultsWireContract = [
|
|
25
|
+
// A folder listing carries the folders and nothing else: a `total` the server
|
|
26
|
+
// never sends would read as undefined wherever a caller trusted it.
|
|
27
|
+
Assert<Equals<keyof ListFoldersResponse, 'folders'>>,
|
|
28
|
+
Assert<Equals<keyof ListVaultsResponse, 'vaults' | 'count' | 'has_more' | 'next_cursor'>>,
|
|
29
|
+
];
|