@ursalock/client 0.3.1 → 0.4.0
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/dist/index.d.ts +169 -1
- package/dist/index.js +372 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -554,4 +554,172 @@ declare function useCredential(client: VaultClient): ZKCredential | null;
|
|
|
554
554
|
*/
|
|
555
555
|
declare function usePasskeySupport(client: VaultClient): boolean;
|
|
556
556
|
|
|
557
|
-
|
|
557
|
+
/**
|
|
558
|
+
* Document types for UrsaLock v2
|
|
559
|
+
* E2E encrypted document storage
|
|
560
|
+
*/
|
|
561
|
+
/** A decrypted document with typed content */
|
|
562
|
+
interface Document<T> {
|
|
563
|
+
uid: string;
|
|
564
|
+
collection: string;
|
|
565
|
+
content: T;
|
|
566
|
+
version: number;
|
|
567
|
+
createdAt: number;
|
|
568
|
+
updatedAt: number;
|
|
569
|
+
deletedAt?: number;
|
|
570
|
+
}
|
|
571
|
+
/** Server response shape (encrypted) */
|
|
572
|
+
interface DocumentResponse {
|
|
573
|
+
uid: string;
|
|
574
|
+
collection: string;
|
|
575
|
+
data: string;
|
|
576
|
+
hmac: string | null;
|
|
577
|
+
version: number;
|
|
578
|
+
createdAt: number;
|
|
579
|
+
updatedAt: number;
|
|
580
|
+
deletedAt: number | null;
|
|
581
|
+
}
|
|
582
|
+
/** Options for listing documents */
|
|
583
|
+
interface ListOptions {
|
|
584
|
+
collection?: string;
|
|
585
|
+
since?: number;
|
|
586
|
+
includeDeleted?: boolean;
|
|
587
|
+
limit?: number;
|
|
588
|
+
offset?: number;
|
|
589
|
+
}
|
|
590
|
+
/** Sync result */
|
|
591
|
+
interface SyncResult<T> {
|
|
592
|
+
documents: Document<T>[];
|
|
593
|
+
syncedAt: number;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Collection - Typed E2E encrypted document storage
|
|
598
|
+
*
|
|
599
|
+
* Encrypts documents client-side using AES-GCM and optionally HMAC-SHA256
|
|
600
|
+
* for integrity verification in transit/storage.
|
|
601
|
+
*/
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Collection provides typed, encrypted document storage
|
|
605
|
+
*
|
|
606
|
+
* All documents are encrypted client-side before being sent to the server.
|
|
607
|
+
* The server never sees plaintext content, only encrypted blobs.
|
|
608
|
+
*/
|
|
609
|
+
declare class Collection<T> {
|
|
610
|
+
private serverUrl;
|
|
611
|
+
private vaultUid;
|
|
612
|
+
private collectionName;
|
|
613
|
+
private encryptionKey;
|
|
614
|
+
private hmacKey;
|
|
615
|
+
private getAuthHeader;
|
|
616
|
+
private httpClient?;
|
|
617
|
+
constructor(serverUrl: string, vaultUid: string, collectionName: string, encryptionKey: Uint8Array, hmacKey: Uint8Array | undefined, getAuthHeader: () => Record<string, string>, httpClient?: IHttpClient | undefined);
|
|
618
|
+
/**
|
|
619
|
+
* Create a new document
|
|
620
|
+
* @param content Document content (will be encrypted)
|
|
621
|
+
* @returns Created document with server-generated uid
|
|
622
|
+
*/
|
|
623
|
+
create(content: T): Promise<Document<T>>;
|
|
624
|
+
/**
|
|
625
|
+
* Get a document by uid
|
|
626
|
+
* @param uid Document uid
|
|
627
|
+
* @returns Decrypted document
|
|
628
|
+
*/
|
|
629
|
+
get(uid: string): Promise<Document<T>>;
|
|
630
|
+
/**
|
|
631
|
+
* List documents in this collection
|
|
632
|
+
* @param options Filter options
|
|
633
|
+
* @returns Array of decrypted documents
|
|
634
|
+
*/
|
|
635
|
+
list(options?: Omit<ListOptions, 'collection'>): Promise<Document<T>[]>;
|
|
636
|
+
/**
|
|
637
|
+
* Update a document
|
|
638
|
+
* @param uid Document uid
|
|
639
|
+
* @param content Partial content to merge (or full replacement)
|
|
640
|
+
* @returns Updated document
|
|
641
|
+
*/
|
|
642
|
+
update(uid: string, content: Partial<T>): Promise<Document<T>>;
|
|
643
|
+
/**
|
|
644
|
+
* Delete a document (soft delete)
|
|
645
|
+
* @param uid Document uid
|
|
646
|
+
*/
|
|
647
|
+
delete(uid: string): Promise<void>;
|
|
648
|
+
/**
|
|
649
|
+
* Sync documents since a timestamp
|
|
650
|
+
* @param since Timestamp (ms) to sync from
|
|
651
|
+
* @returns Sync result with documents and sync timestamp
|
|
652
|
+
*/
|
|
653
|
+
sync(since?: number): Promise<SyncResult<T>>;
|
|
654
|
+
/**
|
|
655
|
+
* Encrypt content to base64 string with optional HMAC
|
|
656
|
+
*/
|
|
657
|
+
private encryptContent;
|
|
658
|
+
/**
|
|
659
|
+
* Decrypt a server response document
|
|
660
|
+
*/
|
|
661
|
+
private decryptDocument;
|
|
662
|
+
/**
|
|
663
|
+
* Make authenticated HTTP request
|
|
664
|
+
*/
|
|
665
|
+
private request;
|
|
666
|
+
/**
|
|
667
|
+
* Convert Uint8Array to base64 string
|
|
668
|
+
*/
|
|
669
|
+
private bytesToBase64;
|
|
670
|
+
/**
|
|
671
|
+
* Convert base64 string to Uint8Array
|
|
672
|
+
*/
|
|
673
|
+
private base64ToBytes;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* DocumentClient - Factory for creating typed Collections
|
|
678
|
+
*
|
|
679
|
+
* Provides a simple interface to create Collection instances
|
|
680
|
+
* with shared configuration (server URL, vault UID, encryption keys).
|
|
681
|
+
*/
|
|
682
|
+
|
|
683
|
+
interface DocumentClientOptions {
|
|
684
|
+
/** Server URL */
|
|
685
|
+
serverUrl: string;
|
|
686
|
+
/** Vault UID */
|
|
687
|
+
vaultUid: string;
|
|
688
|
+
/** 256-bit encryption key */
|
|
689
|
+
encryptionKey: Uint8Array;
|
|
690
|
+
/** Optional HMAC key for integrity verification */
|
|
691
|
+
hmacKey?: Uint8Array;
|
|
692
|
+
/** Function to get current auth header */
|
|
693
|
+
getAuthHeader: () => Record<string, string>;
|
|
694
|
+
/** Optional HTTP client (defaults to fetch) */
|
|
695
|
+
httpClient?: IHttpClient;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* DocumentClient creates typed Collections with shared configuration
|
|
699
|
+
*
|
|
700
|
+
* @example
|
|
701
|
+
* ```ts
|
|
702
|
+
* const client = new DocumentClient({
|
|
703
|
+
* serverUrl: 'https://api.ursalock.com',
|
|
704
|
+
* vaultUid: 'vault-123',
|
|
705
|
+
* encryptionKey: key,
|
|
706
|
+
* hmacKey: hmacKey,
|
|
707
|
+
* getAuthHeader: () => ({ Authorization: `Bearer ${token}` }),
|
|
708
|
+
* });
|
|
709
|
+
*
|
|
710
|
+
* const notes = client.collection<Note>('notes');
|
|
711
|
+
* await notes.create({ title: 'Secret note', content: 'Hello' });
|
|
712
|
+
* ```
|
|
713
|
+
*/
|
|
714
|
+
declare class DocumentClient {
|
|
715
|
+
private options;
|
|
716
|
+
constructor(options: DocumentClientOptions);
|
|
717
|
+
/**
|
|
718
|
+
* Get a typed collection
|
|
719
|
+
* @param name Collection name
|
|
720
|
+
* @returns Collection instance
|
|
721
|
+
*/
|
|
722
|
+
collection<T>(name: string): Collection<T>;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
export { type AuthProvider, type AuthResult, type AuthState, Collection, type Document, DocumentClient, type DocumentClientOptions, type DocumentResponse, EmailAuth, type EmailAuthOptions, type EmailCredentials, type EmailSignInOptions, type EmailSignUpOptions, FetchHttpClient, type IAuthProvider, type IHttpClient, type ListOptions, PasskeyAuth, type PasskeyAuthOptions, type PasskeySignUpOptions, type SyncResult, type Token, TokenManager, type TokenManagerOptions, type User, VaultClient, type VaultClientOptions, type ZKAuthResult, useAuth, useCredential, usePasskeySupport, useSignIn, useSignOut, useSignUp, useUser };
|
package/dist/index.js
CHANGED
|
@@ -802,4 +802,375 @@ function usePasskeySupport(client) {
|
|
|
802
802
|
return client.supportsPasskey();
|
|
803
803
|
}
|
|
804
804
|
|
|
805
|
-
|
|
805
|
+
// ../crypto/dist/index.js
|
|
806
|
+
function randomBytes(length) {
|
|
807
|
+
const bytes = new Uint8Array(length);
|
|
808
|
+
crypto.getRandomValues(bytes);
|
|
809
|
+
return bytes;
|
|
810
|
+
}
|
|
811
|
+
function concatBytes(...arrays) {
|
|
812
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
813
|
+
const result = new Uint8Array(totalLength);
|
|
814
|
+
let offset = 0;
|
|
815
|
+
for (const arr of arrays) {
|
|
816
|
+
result.set(arr, offset);
|
|
817
|
+
offset += arr.length;
|
|
818
|
+
}
|
|
819
|
+
return result;
|
|
820
|
+
}
|
|
821
|
+
var IV_LENGTH = 12;
|
|
822
|
+
var WebCryptoProvider = class {
|
|
823
|
+
async encrypt(plaintext, key) {
|
|
824
|
+
if (key.length !== 32) {
|
|
825
|
+
throw new Error(`Invalid key length: expected 32 bytes, got ${key.length}`);
|
|
826
|
+
}
|
|
827
|
+
const actualIv = randomBytes(IV_LENGTH);
|
|
828
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
829
|
+
"raw",
|
|
830
|
+
key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength),
|
|
831
|
+
{ name: "AES-GCM" },
|
|
832
|
+
false,
|
|
833
|
+
["encrypt"]
|
|
834
|
+
);
|
|
835
|
+
const ciphertext = new Uint8Array(
|
|
836
|
+
await crypto.subtle.encrypt(
|
|
837
|
+
{ name: "AES-GCM", iv: actualIv },
|
|
838
|
+
cryptoKey,
|
|
839
|
+
plaintext.buffer.slice(plaintext.byteOffset, plaintext.byteOffset + plaintext.byteLength)
|
|
840
|
+
)
|
|
841
|
+
);
|
|
842
|
+
const combined = concatBytes(actualIv, ciphertext);
|
|
843
|
+
return { iv: actualIv, ciphertext, combined };
|
|
844
|
+
}
|
|
845
|
+
async decrypt(encrypted, key) {
|
|
846
|
+
if (key.length !== 32) {
|
|
847
|
+
throw new Error(`Invalid key length: expected 32 bytes, got ${key.length}`);
|
|
848
|
+
}
|
|
849
|
+
let iv;
|
|
850
|
+
let ciphertext;
|
|
851
|
+
if (encrypted instanceof Uint8Array) {
|
|
852
|
+
if (encrypted.length < IV_LENGTH + 16) {
|
|
853
|
+
throw new Error("Invalid encrypted data: too short");
|
|
854
|
+
}
|
|
855
|
+
iv = encrypted.slice(0, IV_LENGTH);
|
|
856
|
+
ciphertext = encrypted.slice(IV_LENGTH);
|
|
857
|
+
} else {
|
|
858
|
+
iv = encrypted.iv;
|
|
859
|
+
ciphertext = encrypted.ciphertext;
|
|
860
|
+
}
|
|
861
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
862
|
+
"raw",
|
|
863
|
+
key.buffer.slice(key.byteOffset, key.byteOffset + key.byteLength),
|
|
864
|
+
{ name: "AES-GCM" },
|
|
865
|
+
false,
|
|
866
|
+
["decrypt"]
|
|
867
|
+
);
|
|
868
|
+
try {
|
|
869
|
+
const plaintext = await crypto.subtle.decrypt(
|
|
870
|
+
{ name: "AES-GCM", iv },
|
|
871
|
+
cryptoKey,
|
|
872
|
+
ciphertext.buffer.slice(ciphertext.byteOffset, ciphertext.byteOffset + ciphertext.byteLength)
|
|
873
|
+
);
|
|
874
|
+
return new Uint8Array(plaintext);
|
|
875
|
+
} catch (error) {
|
|
876
|
+
throw new Error("Decryption failed: invalid key or corrupted data");
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
var defaultProvider = new WebCryptoProvider();
|
|
881
|
+
async function encrypt(plaintext, key, provider = defaultProvider) {
|
|
882
|
+
return provider.encrypt(plaintext, key);
|
|
883
|
+
}
|
|
884
|
+
async function decrypt(encrypted, key, provider = defaultProvider) {
|
|
885
|
+
return provider.decrypt(encrypted, key);
|
|
886
|
+
}
|
|
887
|
+
async function computeHmac(data, key) {
|
|
888
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
889
|
+
"raw",
|
|
890
|
+
key,
|
|
891
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
892
|
+
false,
|
|
893
|
+
["sign"]
|
|
894
|
+
);
|
|
895
|
+
const signature = new Uint8Array(
|
|
896
|
+
await crypto.subtle.sign("HMAC", cryptoKey, data)
|
|
897
|
+
);
|
|
898
|
+
return bytesToHex(signature);
|
|
899
|
+
}
|
|
900
|
+
async function verifyHmac(data, key, expectedHmac) {
|
|
901
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
902
|
+
"raw",
|
|
903
|
+
key,
|
|
904
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
905
|
+
false,
|
|
906
|
+
["verify"]
|
|
907
|
+
);
|
|
908
|
+
const expectedBytes = hexToBytes(expectedHmac);
|
|
909
|
+
return crypto.subtle.verify(
|
|
910
|
+
"HMAC",
|
|
911
|
+
cryptoKey,
|
|
912
|
+
expectedBytes,
|
|
913
|
+
data
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
function bytesToHex(bytes) {
|
|
917
|
+
let hex = "";
|
|
918
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
919
|
+
hex += bytes[i].toString(16).padStart(2, "0");
|
|
920
|
+
}
|
|
921
|
+
return hex;
|
|
922
|
+
}
|
|
923
|
+
function hexToBytes(hex) {
|
|
924
|
+
const len = hex.length >>> 1;
|
|
925
|
+
const bytes = new Uint8Array(len);
|
|
926
|
+
for (let i = 0; i < len; i++) {
|
|
927
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
928
|
+
}
|
|
929
|
+
return bytes;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/collection.ts
|
|
933
|
+
var Collection = class {
|
|
934
|
+
constructor(serverUrl, vaultUid, collectionName, encryptionKey, hmacKey, getAuthHeader, httpClient) {
|
|
935
|
+
this.serverUrl = serverUrl;
|
|
936
|
+
this.vaultUid = vaultUid;
|
|
937
|
+
this.collectionName = collectionName;
|
|
938
|
+
this.encryptionKey = encryptionKey;
|
|
939
|
+
this.hmacKey = hmacKey;
|
|
940
|
+
this.getAuthHeader = getAuthHeader;
|
|
941
|
+
this.httpClient = httpClient;
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Create a new document
|
|
945
|
+
* @param content Document content (will be encrypted)
|
|
946
|
+
* @returns Created document with server-generated uid
|
|
947
|
+
*/
|
|
948
|
+
async create(content) {
|
|
949
|
+
const { data, hmac } = await this.encryptContent(content);
|
|
950
|
+
const body = {
|
|
951
|
+
collection: this.collectionName,
|
|
952
|
+
data
|
|
953
|
+
};
|
|
954
|
+
if (hmac) {
|
|
955
|
+
body.hmac = hmac;
|
|
956
|
+
}
|
|
957
|
+
const response = await this.request(`/vault/${this.vaultUid}/documents`, {
|
|
958
|
+
method: "POST",
|
|
959
|
+
headers: {
|
|
960
|
+
"Content-Type": "application/json"
|
|
961
|
+
},
|
|
962
|
+
body: JSON.stringify(body)
|
|
963
|
+
});
|
|
964
|
+
return this.decryptDocument(response);
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Get a document by uid
|
|
968
|
+
* @param uid Document uid
|
|
969
|
+
* @returns Decrypted document
|
|
970
|
+
*/
|
|
971
|
+
async get(uid) {
|
|
972
|
+
const response = await this.request(`/vault/${this.vaultUid}/documents/${uid}`);
|
|
973
|
+
return this.decryptDocument(response);
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* List documents in this collection
|
|
977
|
+
* @param options Filter options
|
|
978
|
+
* @returns Array of decrypted documents
|
|
979
|
+
*/
|
|
980
|
+
async list(options) {
|
|
981
|
+
const params = new URLSearchParams();
|
|
982
|
+
params.set("collection", this.collectionName);
|
|
983
|
+
if (options?.since !== void 0) {
|
|
984
|
+
params.set("since", String(options.since));
|
|
985
|
+
}
|
|
986
|
+
if (options?.includeDeleted !== void 0) {
|
|
987
|
+
params.set("includeDeleted", String(options.includeDeleted));
|
|
988
|
+
}
|
|
989
|
+
if (options?.limit !== void 0) {
|
|
990
|
+
params.set("limit", String(options.limit));
|
|
991
|
+
}
|
|
992
|
+
if (options?.offset !== void 0) {
|
|
993
|
+
params.set("offset", String(options.offset));
|
|
994
|
+
}
|
|
995
|
+
const response = await this.request(
|
|
996
|
+
`/vault/${this.vaultUid}/documents?${params}`
|
|
997
|
+
);
|
|
998
|
+
return Promise.all(response.documents.map((doc) => this.decryptDocument(doc)));
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Update a document
|
|
1002
|
+
* @param uid Document uid
|
|
1003
|
+
* @param content Partial content to merge (or full replacement)
|
|
1004
|
+
* @returns Updated document
|
|
1005
|
+
*/
|
|
1006
|
+
async update(uid, content) {
|
|
1007
|
+
const current = await this.get(uid);
|
|
1008
|
+
const merged = { ...current.content, ...content };
|
|
1009
|
+
const { data, hmac } = await this.encryptContent(merged);
|
|
1010
|
+
const body = {
|
|
1011
|
+
data,
|
|
1012
|
+
version: current.version
|
|
1013
|
+
};
|
|
1014
|
+
if (hmac) {
|
|
1015
|
+
body.hmac = hmac;
|
|
1016
|
+
}
|
|
1017
|
+
const response = await this.request(`/vault/${this.vaultUid}/documents/${uid}`, {
|
|
1018
|
+
method: "PUT",
|
|
1019
|
+
headers: {
|
|
1020
|
+
"Content-Type": "application/json"
|
|
1021
|
+
},
|
|
1022
|
+
body: JSON.stringify(body)
|
|
1023
|
+
});
|
|
1024
|
+
return this.decryptDocument(response);
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Delete a document (soft delete)
|
|
1028
|
+
* @param uid Document uid
|
|
1029
|
+
*/
|
|
1030
|
+
async delete(uid) {
|
|
1031
|
+
await this.request(`/vault/${this.vaultUid}/documents/${uid}`, {
|
|
1032
|
+
method: "DELETE"
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Sync documents since a timestamp
|
|
1037
|
+
* @param since Timestamp (ms) to sync from
|
|
1038
|
+
* @returns Sync result with documents and sync timestamp
|
|
1039
|
+
*/
|
|
1040
|
+
async sync(since) {
|
|
1041
|
+
const params = new URLSearchParams();
|
|
1042
|
+
if (since !== void 0) {
|
|
1043
|
+
params.set("since", String(since));
|
|
1044
|
+
}
|
|
1045
|
+
const response = await this.request(
|
|
1046
|
+
`/vault/${this.vaultUid}/documents/sync?${params}`
|
|
1047
|
+
);
|
|
1048
|
+
const documents = await Promise.all(
|
|
1049
|
+
response.documents.map((doc) => this.decryptDocument(doc))
|
|
1050
|
+
);
|
|
1051
|
+
return {
|
|
1052
|
+
documents,
|
|
1053
|
+
syncedAt: response.syncedAt
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
// ==================
|
|
1057
|
+
// Private Helpers
|
|
1058
|
+
// ==================
|
|
1059
|
+
/**
|
|
1060
|
+
* Encrypt content to base64 string with optional HMAC
|
|
1061
|
+
*/
|
|
1062
|
+
async encryptContent(content) {
|
|
1063
|
+
const json = JSON.stringify(content);
|
|
1064
|
+
const plaintext = new TextEncoder().encode(json);
|
|
1065
|
+
const encrypted = await encrypt(plaintext, this.encryptionKey);
|
|
1066
|
+
const data = this.bytesToBase64(encrypted.combined);
|
|
1067
|
+
let hmac;
|
|
1068
|
+
if (this.hmacKey) {
|
|
1069
|
+
const dataBytes = new TextEncoder().encode(data);
|
|
1070
|
+
hmac = await computeHmac(dataBytes, this.hmacKey);
|
|
1071
|
+
}
|
|
1072
|
+
return { data, hmac };
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Decrypt a server response document
|
|
1076
|
+
*/
|
|
1077
|
+
async decryptDocument(response) {
|
|
1078
|
+
if (this.hmacKey && response.hmac) {
|
|
1079
|
+
const dataBytes = new TextEncoder().encode(response.data);
|
|
1080
|
+
const valid = await verifyHmac(dataBytes, this.hmacKey, response.hmac);
|
|
1081
|
+
if (!valid) {
|
|
1082
|
+
throw new Error("HMAC verification failed: invalid signature");
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
const encryptedBytes = this.base64ToBytes(response.data);
|
|
1086
|
+
const plaintext = await decrypt(encryptedBytes, this.encryptionKey);
|
|
1087
|
+
const json = new TextDecoder().decode(plaintext);
|
|
1088
|
+
const content = JSON.parse(json);
|
|
1089
|
+
return {
|
|
1090
|
+
uid: response.uid,
|
|
1091
|
+
collection: response.collection,
|
|
1092
|
+
content,
|
|
1093
|
+
version: response.version,
|
|
1094
|
+
createdAt: response.createdAt,
|
|
1095
|
+
updatedAt: response.updatedAt,
|
|
1096
|
+
deletedAt: response.deletedAt ?? void 0
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Make authenticated HTTP request
|
|
1101
|
+
*/
|
|
1102
|
+
async request(path, options = {}) {
|
|
1103
|
+
const url = path.startsWith("http") ? path : `${this.serverUrl}${path}`;
|
|
1104
|
+
const headers = {
|
|
1105
|
+
...this.getAuthHeader(),
|
|
1106
|
+
...options.headers
|
|
1107
|
+
};
|
|
1108
|
+
const fetchFn = this.httpClient ? (url2, opts) => this.httpClient.fetch(url2, opts) : fetch;
|
|
1109
|
+
const response = await fetchFn(url, { ...options, headers });
|
|
1110
|
+
if (!response.ok) {
|
|
1111
|
+
let errorMessage;
|
|
1112
|
+
try {
|
|
1113
|
+
const errorData = await response.json();
|
|
1114
|
+
errorMessage = errorData.message || errorData.error || response.statusText;
|
|
1115
|
+
} catch {
|
|
1116
|
+
errorMessage = response.statusText;
|
|
1117
|
+
}
|
|
1118
|
+
if (response.status === 404) {
|
|
1119
|
+
throw new Error(`Document not found: ${errorMessage}`);
|
|
1120
|
+
} else if (response.status === 409) {
|
|
1121
|
+
throw new Error(`Conflict: ${errorMessage}`);
|
|
1122
|
+
} else if (response.status === 401) {
|
|
1123
|
+
throw new Error(`Unauthorized: ${errorMessage}`);
|
|
1124
|
+
} else {
|
|
1125
|
+
throw new Error(`HTTP ${response.status}: ${errorMessage}`);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return response.json();
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Convert Uint8Array to base64 string
|
|
1132
|
+
*/
|
|
1133
|
+
bytesToBase64(bytes) {
|
|
1134
|
+
let binary = "";
|
|
1135
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1136
|
+
binary += String.fromCharCode(bytes[i]);
|
|
1137
|
+
}
|
|
1138
|
+
return btoa(binary);
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Convert base64 string to Uint8Array
|
|
1142
|
+
*/
|
|
1143
|
+
base64ToBytes(base64) {
|
|
1144
|
+
const binary = atob(base64);
|
|
1145
|
+
const bytes = new Uint8Array(binary.length);
|
|
1146
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1147
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1148
|
+
}
|
|
1149
|
+
return bytes;
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
// src/document-client.ts
|
|
1154
|
+
var DocumentClient = class {
|
|
1155
|
+
constructor(options) {
|
|
1156
|
+
this.options = options;
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Get a typed collection
|
|
1160
|
+
* @param name Collection name
|
|
1161
|
+
* @returns Collection instance
|
|
1162
|
+
*/
|
|
1163
|
+
collection(name) {
|
|
1164
|
+
return new Collection(
|
|
1165
|
+
this.options.serverUrl,
|
|
1166
|
+
this.options.vaultUid,
|
|
1167
|
+
name,
|
|
1168
|
+
this.options.encryptionKey,
|
|
1169
|
+
this.options.hmacKey,
|
|
1170
|
+
this.options.getAuthHeader,
|
|
1171
|
+
this.options.httpClient
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
export { Collection, DocumentClient, EmailAuth, FetchHttpClient, PasskeyAuth, TokenManager, VaultClient, useAuth, useCredential, usePasskeySupport, useSignIn, useSignOut, useSignUp, useUser };
|