@keyneom/sync-kit 0.1.0 → 0.2.0-rc.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.
Files changed (77) hide show
  1. package/README.md +137 -11
  2. package/dist/auth/google-web/identity.d.ts +49 -0
  3. package/dist/auth/google-web/identity.d.ts.map +1 -0
  4. package/dist/auth/google-web/identity.js +102 -0
  5. package/dist/auth/google-web/identity.js.map +1 -0
  6. package/dist/auth/google-web/index.d.ts +1 -0
  7. package/dist/auth/google-web/index.d.ts.map +1 -1
  8. package/dist/auth/google-web/index.js +1 -0
  9. package/dist/auth/google-web/index.js.map +1 -1
  10. package/dist/core/errors.d.ts +1 -1
  11. package/dist/core/errors.d.ts.map +1 -1
  12. package/dist/core/errors.js +22 -1
  13. package/dist/core/errors.js.map +1 -1
  14. package/dist/crypto/canonical.d.ts +5 -0
  15. package/dist/crypto/canonical.d.ts.map +1 -1
  16. package/dist/crypto/canonical.js +8 -1
  17. package/dist/crypto/canonical.js.map +1 -1
  18. package/dist/crypto/envelope.d.ts.map +1 -1
  19. package/dist/crypto/envelope.js +1 -0
  20. package/dist/crypto/envelope.js.map +1 -1
  21. package/dist/crypto/profiles.d.ts +6 -46
  22. package/dist/crypto/profiles.d.ts.map +1 -1
  23. package/dist/crypto/profiles.js +33 -46
  24. package/dist/crypto/profiles.js.map +1 -1
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/keys/web-passkey/index.d.ts +8 -2
  30. package/dist/keys/web-passkey/index.d.ts.map +1 -1
  31. package/dist/keys/web-passkey/index.js +32 -10
  32. package/dist/keys/web-passkey/index.js.map +1 -1
  33. package/dist/sharing/account-binding.d.ts +57 -0
  34. package/dist/sharing/account-binding.d.ts.map +1 -0
  35. package/dist/sharing/account-binding.js +305 -0
  36. package/dist/sharing/account-binding.js.map +1 -0
  37. package/dist/sharing/controller.d.ts +139 -0
  38. package/dist/sharing/controller.d.ts.map +1 -0
  39. package/dist/sharing/controller.js +549 -0
  40. package/dist/sharing/controller.js.map +1 -0
  41. package/dist/sharing/index.d.ts +119 -0
  42. package/dist/sharing/index.d.ts.map +1 -0
  43. package/dist/sharing/index.js +360 -0
  44. package/dist/sharing/index.js.map +1 -0
  45. package/dist/sharing/transport.d.ts +65 -0
  46. package/dist/sharing/transport.d.ts.map +1 -0
  47. package/dist/sharing/transport.js +2 -0
  48. package/dist/sharing/transport.js.map +1 -0
  49. package/dist/sharing/web-crypto.d.ts +68 -0
  50. package/dist/sharing/web-crypto.d.ts.map +1 -0
  51. package/dist/sharing/web-crypto.js +645 -0
  52. package/dist/sharing/web-crypto.js.map +1 -0
  53. package/dist/sharing/web-passkey.d.ts +68 -0
  54. package/dist/sharing/web-passkey.d.ts.map +1 -0
  55. package/dist/sharing/web-passkey.js +303 -0
  56. package/dist/sharing/web-passkey.js.map +1 -0
  57. package/dist/snapshot/controller.d.ts.map +1 -1
  58. package/dist/snapshot/controller.js +4 -8
  59. package/dist/snapshot/controller.js.map +1 -1
  60. package/dist/stores/google-drive/index.d.ts +147 -1
  61. package/dist/stores/google-drive/index.d.ts.map +1 -1
  62. package/dist/stores/google-drive/index.js +397 -0
  63. package/dist/stores/google-drive/index.js.map +1 -1
  64. package/dist/stores/google-drive/picker.d.ts +89 -0
  65. package/dist/stores/google-drive/picker.d.ts.map +1 -0
  66. package/dist/stores/google-drive/picker.js +179 -0
  67. package/dist/stores/google-drive/picker.js.map +1 -0
  68. package/dist/stores/google-drive/sharing.d.ts +55 -0
  69. package/dist/stores/google-drive/sharing.d.ts.map +1 -0
  70. package/dist/stores/google-drive/sharing.js +295 -0
  71. package/dist/stores/google-drive/sharing.js.map +1 -0
  72. package/package.json +67 -12
  73. package/fixtures/v1/easybc-web-android-gzip.json +0 -41
  74. package/fixtures/v1/easybc-web-uncompressed.json +0 -35
  75. package/fixtures/v1/failures.json +0 -42
  76. package/fixtures/v1/family-chores-web-uncompressed.json +0 -73
  77. package/fixtures/v1/profiles.json +0 -51
@@ -0,0 +1,89 @@
1
+ import type { Authorization } from "../../core/types.js";
2
+ type PickerDocument = {
3
+ id?: string;
4
+ name?: string;
5
+ mimeType?: string;
6
+ url?: string;
7
+ };
8
+ type PickerResponse = {
9
+ action?: string;
10
+ docs?: PickerDocument[];
11
+ };
12
+ type Picker = {
13
+ setVisible(visible: boolean): void;
14
+ dispose?(): void;
15
+ };
16
+ type DocsView = {
17
+ setIncludeFolders(value: boolean): DocsView;
18
+ setMimeTypes(value: string): DocsView;
19
+ setSelectFolderEnabled(value: boolean): DocsView;
20
+ };
21
+ type PickerBuilder = {
22
+ addView(view: DocsView): PickerBuilder;
23
+ setAppId(appId: string): PickerBuilder;
24
+ setCallback(callback: (response: PickerResponse) => void): PickerBuilder;
25
+ setDeveloperKey(key: string): PickerBuilder;
26
+ setOAuthToken(token: string): PickerBuilder;
27
+ setOrigin(origin: string): PickerBuilder;
28
+ setTitle(title: string): PickerBuilder;
29
+ build(): Picker;
30
+ };
31
+ type GooglePicker = {
32
+ Action: {
33
+ PICKED: string;
34
+ CANCEL: string;
35
+ };
36
+ DocsView: new (viewId: string) => DocsView;
37
+ PickerBuilder: new () => PickerBuilder;
38
+ ViewId: {
39
+ FOLDERS: string;
40
+ };
41
+ };
42
+ type GoogleApiWindow = Window & {
43
+ gapi?: {
44
+ load(name: string, callback: {
45
+ callback(): void;
46
+ onerror(): void;
47
+ }): void;
48
+ };
49
+ google?: {
50
+ picker?: GooglePicker;
51
+ };
52
+ };
53
+ export type GoogleDrivePickedFolder = {
54
+ folderId: string;
55
+ name?: string;
56
+ url?: string;
57
+ };
58
+ export type GoogleDriveFolderPickerOptions = {
59
+ developerKey: string;
60
+ cloudProjectNumber: string;
61
+ origin?: string;
62
+ title?: string;
63
+ window?: GoogleApiWindow;
64
+ document?: Document;
65
+ };
66
+ /**
67
+ * Browser-only folder picker for granting drive.file access to a shared app
68
+ * folder. Loading and UI are explicit; importing this module has no effects.
69
+ */
70
+ export declare class GoogleDriveFolderPicker {
71
+ private readonly options;
72
+ private scriptPromise;
73
+ private pickerPromise;
74
+ constructor(options: GoogleDriveFolderPickerOptions);
75
+ pickFolder(authorization: Authorization): Promise<GoogleDrivePickedFolder | null>;
76
+ private loadPicker;
77
+ private loadScript;
78
+ private browserWindow;
79
+ private browserDocument;
80
+ }
81
+ export type GoogleDriveOpenState = {
82
+ action: "open";
83
+ fileIds: string[];
84
+ resourceKeys: Record<string, string>;
85
+ userId?: string;
86
+ };
87
+ export declare function parseGoogleDriveOpenState(input: string | URLSearchParams): GoogleDriveOpenState | null;
88
+ export {};
89
+ //# sourceMappingURL=picker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../../src/stores/google-drive/picker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,179 @@
1
+ import { SyncKitError } from "../../core/errors.js";
2
+ const PICKER_SCRIPT = "https://apis.google.com/js/api.js";
3
+ const DRIVE_FOLDER_MIME_TYPE = "application/vnd.google-apps.folder";
4
+ /**
5
+ * Browser-only folder picker for granting drive.file access to a shared app
6
+ * folder. Loading and UI are explicit; importing this module has no effects.
7
+ */
8
+ export class GoogleDriveFolderPicker {
9
+ options;
10
+ scriptPromise = null;
11
+ pickerPromise = null;
12
+ constructor(options) {
13
+ this.options = options;
14
+ if (!options.developerKey.trim()) {
15
+ throw new TypeError("developerKey must not be empty.");
16
+ }
17
+ if (!options.cloudProjectNumber.trim()) {
18
+ throw new TypeError("cloudProjectNumber must not be empty.");
19
+ }
20
+ }
21
+ async pickFolder(authorization) {
22
+ await this.loadPicker();
23
+ const pickerApi = this.browserWindow().google?.picker;
24
+ if (!pickerApi) {
25
+ throw new SyncKitError("provider", "Google Picker loaded without its folder API.");
26
+ }
27
+ return new Promise((resolve, reject) => {
28
+ let picker;
29
+ const finish = (value) => {
30
+ picker?.dispose?.();
31
+ resolve(value);
32
+ };
33
+ const view = new pickerApi.DocsView(pickerApi.ViewId.FOLDERS)
34
+ .setIncludeFolders(true)
35
+ .setMimeTypes(DRIVE_FOLDER_MIME_TYPE)
36
+ .setSelectFolderEnabled(true);
37
+ try {
38
+ picker = new pickerApi.PickerBuilder()
39
+ .addView(view)
40
+ .setAppId(this.options.cloudProjectNumber)
41
+ .setDeveloperKey(this.options.developerKey)
42
+ .setOAuthToken(authorization.accessToken)
43
+ .setOrigin(this.options.origin ?? this.browserWindow().location.origin)
44
+ .setTitle(this.options.title ?? "Choose the shared app folder")
45
+ .setCallback((response) => {
46
+ if (response.action === pickerApi.Action.CANCEL) {
47
+ finish(null);
48
+ return;
49
+ }
50
+ if (response.action !== pickerApi.Action.PICKED)
51
+ return;
52
+ const document = response.docs?.[0];
53
+ if (!document?.id ||
54
+ (document.mimeType &&
55
+ document.mimeType !== DRIVE_FOLDER_MIME_TYPE)) {
56
+ reject(new SyncKitError("compatibility", "Google Picker did not return a Drive folder."));
57
+ return;
58
+ }
59
+ finish({
60
+ folderId: document.id,
61
+ ...(document.name ? { name: document.name } : {}),
62
+ ...(document.url ? { url: document.url } : {}),
63
+ });
64
+ })
65
+ .build();
66
+ picker.setVisible(true);
67
+ }
68
+ catch (error) {
69
+ reject(new SyncKitError("provider", "Google Picker could not be opened.", { cause: error }));
70
+ }
71
+ });
72
+ }
73
+ async loadPicker() {
74
+ if (this.browserWindow().google?.picker)
75
+ return;
76
+ await this.loadScript();
77
+ this.pickerPromise ??= new Promise((resolve, reject) => {
78
+ const gapi = this.browserWindow().gapi;
79
+ if (!gapi) {
80
+ reject(new SyncKitError("provider", "The Google API loader is unavailable."));
81
+ return;
82
+ }
83
+ gapi.load("picker", {
84
+ callback: () => resolve(),
85
+ onerror: () => {
86
+ this.pickerPromise = null;
87
+ reject(new SyncKitError("provider", "The Google Picker API could not be loaded."));
88
+ },
89
+ });
90
+ });
91
+ await this.pickerPromise;
92
+ }
93
+ loadScript() {
94
+ if (this.browserWindow().gapi)
95
+ return Promise.resolve();
96
+ if (this.scriptPromise)
97
+ return this.scriptPromise;
98
+ this.scriptPromise = new Promise((resolve, reject) => {
99
+ const script = this.browserDocument().createElement("script");
100
+ script.src = PICKER_SCRIPT;
101
+ script.async = true;
102
+ script.defer = true;
103
+ script.onload = () => resolve();
104
+ script.onerror = () => {
105
+ this.scriptPromise = null;
106
+ reject(new SyncKitError("provider", "The Google API loader could not be loaded."));
107
+ };
108
+ this.browserDocument().head.append(script);
109
+ });
110
+ return this.scriptPromise;
111
+ }
112
+ browserWindow() {
113
+ const value = this.options.window ??
114
+ (typeof window === "undefined"
115
+ ? undefined
116
+ : window);
117
+ if (!value) {
118
+ throw new SyncKitError("configuration", "Google Picker requires a browser window.");
119
+ }
120
+ return value;
121
+ }
122
+ browserDocument() {
123
+ const value = this.options.document ??
124
+ (typeof document === "undefined" ? undefined : document);
125
+ if (!value) {
126
+ throw new SyncKitError("configuration", "Google Picker requires a browser document.");
127
+ }
128
+ return value;
129
+ }
130
+ }
131
+ export function parseGoogleDriveOpenState(input) {
132
+ const params = typeof input === "string"
133
+ ? new URLSearchParams(input.startsWith("?") ? input.slice(1) : input)
134
+ : input;
135
+ const encoded = params.get("state");
136
+ if (!encoded)
137
+ return null;
138
+ let value;
139
+ try {
140
+ value = JSON.parse(encoded);
141
+ }
142
+ catch (error) {
143
+ throw new SyncKitError("compatibility", "The Google Drive Open-with state is not valid JSON.", { cause: error });
144
+ }
145
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
146
+ throw new SyncKitError("compatibility", "The Google Drive Open-with state must be an object.");
147
+ }
148
+ const state = value;
149
+ if (state.action !== "open")
150
+ return null;
151
+ if (!Array.isArray(state.ids) ||
152
+ state.ids.length === 0 ||
153
+ !state.ids.every((id) => typeof id === "string" && id.length > 0)) {
154
+ throw new SyncKitError("compatibility", "The Google Drive Open-with state has no file IDs.");
155
+ }
156
+ const resourceKeys = {};
157
+ if (state.resourceKeys !== undefined) {
158
+ if (!state.resourceKeys ||
159
+ typeof state.resourceKeys !== "object" ||
160
+ Array.isArray(state.resourceKeys)) {
161
+ throw new SyncKitError("compatibility", "The Google Drive resource keys are malformed.");
162
+ }
163
+ for (const [fileId, resourceKey] of Object.entries(state.resourceKeys)) {
164
+ if (typeof resourceKey !== "string" || resourceKey.length === 0) {
165
+ throw new SyncKitError("compatibility", "A Google Drive resource key is malformed.");
166
+ }
167
+ resourceKeys[fileId] = resourceKey;
168
+ }
169
+ }
170
+ return {
171
+ action: "open",
172
+ fileIds: state.ids.map((id) => String(id)),
173
+ resourceKeys,
174
+ ...(typeof state.userId === "string" && state.userId
175
+ ? { userId: state.userId }
176
+ : {}),
177
+ };
178
+ }
179
+ //# sourceMappingURL=picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picker.js","sourceRoot":"","sources":["../../../src/stores/google-drive/picker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,MAAM,aAAa,GAAG,mCAAmC,CAAC;AAC1D,MAAM,sBAAsB,GAAG,oCAAoC,CAAC;AAiEpE;;;GAGG;AACH,MAAM,OAAO,uBAAuB;IAIL;IAHrB,aAAa,GAAyB,IAAI,CAAC;IAC3C,aAAa,GAAyB,IAAI,CAAC;IAEnD,YAA6B,OAAuC;QAAvC,YAAO,GAAP,OAAO,CAAgC;QAClE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC;YACjC,MAAM,IAAI,SAAS,CAAC,iCAAiC,CAAC,CAAC;QACzD,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,IAAI,EAAE,EAAE,CAAC;YACvC,MAAM,IAAI,SAAS,CAAC,uCAAuC,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CACd,aAA4B;QAE5B,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QACxB,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;QACtD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,YAAY,CACpB,UAAU,EACV,8CAA8C,CAC/C,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,MAA0B,CAAC;YAC/B,MAAM,MAAM,GAAG,CAAC,KAAqC,EAAQ,EAAE;gBAC7D,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC;gBACpB,OAAO,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC,CAAC;YACF,MAAM,IAAI,GAAG,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC;iBAC1D,iBAAiB,CAAC,IAAI,CAAC;iBACvB,YAAY,CAAC,sBAAsB,CAAC;iBACpC,sBAAsB,CAAC,IAAI,CAAC,CAAC;YAChC,IAAI,CAAC;gBACH,MAAM,GAAG,IAAI,SAAS,CAAC,aAAa,EAAE;qBACnC,OAAO,CAAC,IAAI,CAAC;qBACb,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC;qBACzC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC;qBAC1C,aAAa,CAAC,aAAa,CAAC,WAAW,CAAC;qBACxC,SAAS,CACR,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC,QAAQ,CAAC,MAAM,CAC5D;qBACA,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,8BAA8B,CAAC;qBAC9D,WAAW,CAAC,CAAC,QAAQ,EAAE,EAAE;oBACxB,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;wBAChD,MAAM,CAAC,IAAI,CAAC,CAAC;wBACb,OAAO;oBACT,CAAC;oBACD,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,CAAC,MAAM;wBAAE,OAAO;oBACxD,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;oBACpC,IACE,CAAC,QAAQ,EAAE,EAAE;wBACb,CAAC,QAAQ,CAAC,QAAQ;4BAChB,QAAQ,CAAC,QAAQ,KAAK,sBAAsB,CAAC,EAC/C,CAAC;wBACD,MAAM,CACJ,IAAI,YAAY,CACd,eAAe,EACf,8CAA8C,CAC/C,CACF,CAAC;wBACF,OAAO;oBACT,CAAC;oBACD,MAAM,CAAC;wBACL,QAAQ,EAAE,QAAQ,CAAC,EAAE;wBACrB,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBACjD,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;qBAC/C,CAAC,CAAC;gBACL,CAAC,CAAC;qBACD,KAAK,EAAE,CAAC;gBACX,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CACJ,IAAI,YAAY,CACd,UAAU,EACV,oCAAoC,EACpC,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CACF,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,UAAU;QACtB,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM;YAAE,OAAO;QAChD,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QACxB,IAAI,CAAC,aAAa,KAAK,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrD,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC;YACvC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,CACJ,IAAI,YAAY,CACd,UAAU,EACV,uCAAuC,CACxC,CACF,CAAC;gBACF,OAAO;YACT,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;gBAClB,QAAQ,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE;gBACzB,OAAO,EAAE,GAAG,EAAE;oBACZ,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;oBAC1B,MAAM,CACJ,IAAI,YAAY,CACd,UAAU,EACV,4CAA4C,CAC7C,CACF,CAAC;gBACJ,CAAC;aACF,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,aAAa,CAAC;IAC3B,CAAC;IAEO,UAAU;QAChB,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QACxD,IAAI,IAAI,CAAC,aAAa;YAAE,OAAO,IAAI,CAAC,aAAa,CAAC;QAClD,IAAI,CAAC,aAAa,GAAG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnD,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC9D,MAAM,CAAC,GAAG,GAAG,aAAa,CAAC;YAC3B,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;YACpB,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC;YACpB,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;YAChC,MAAM,CAAC,OAAO,GAAG,GAAG,EAAE;gBACpB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;gBAC1B,MAAM,CACJ,IAAI,YAAY,CACd,UAAU,EACV,4CAA4C,CAC7C,CACF,CAAC;YACJ,CAAC,CAAC;YACF,IAAI,CAAC,eAAe,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAEO,aAAa;QACnB,MAAM,KAAK,GACT,IAAI,CAAC,OAAO,CAAC,MAAM;YACnB,CAAC,OAAO,MAAM,KAAK,WAAW;gBAC5B,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,0CAA0C,CAC3C,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,eAAe;QACrB,MAAM,KAAK,GACT,IAAI,CAAC,OAAO,CAAC,QAAQ;YACrB,CAAC,OAAO,QAAQ,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC3D,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,4CAA4C,CAC7C,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AASD,MAAM,UAAU,yBAAyB,CACvC,KAA+B;IAE/B,MAAM,MAAM,GACV,OAAO,KAAK,KAAK,QAAQ;QACvB,CAAC,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACrE,CAAC,CAAC,KAAK,CAAC;IACZ,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAY,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,qDAAqD,EACrD,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,qDAAqD,CACtD,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,KAAgC,CAAC;IAC/C,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACzC,IACE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;QACzB,KAAK,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC;QACtB,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,EACjE,CAAC;QACD,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,mDAAmD,CACpD,CAAC;IACJ,CAAC;IACD,MAAM,YAAY,GAA2B,EAAE,CAAC;IAChD,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACrC,IACE,CAAC,KAAK,CAAC,YAAY;YACnB,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ;YACtC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,EACjC,CAAC;YACD,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,+CAA+C,CAChD,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;YACvE,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAChE,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,2CAA2C,CAC5C,CAAC;YACJ,CAAC;YACD,YAAY,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC;QACrC,CAAC;IACH,CAAC;IACD,OAAO;QACL,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAU,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAClD,YAAY;QACZ,GAAG,CAAC,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM;YAClD,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;YAC1B,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,55 @@
1
+ import type { Authorization, AuthorizationProvider } from "../../core/types.js";
2
+ import { type SharedBackupEnvelopeV1, type SharingInvitationV1, type SharingPublicKeyResponseV1, type SharingRole } from "../../sharing/index.js";
3
+ import type { SharedBackupStorage, SharedBackupTransport, SharedDatasetFile, SharedDatasetPermission, SharedExchangeFile, SharedKeyResponseFile, VersionedSharedDataset } from "../../sharing/transport.js";
4
+ import { GoogleDriveFileStore } from "./index.js";
5
+ export type GoogleDriveSharedBackupTransportOptions = {
6
+ appId: string;
7
+ authorizationProvider: AuthorizationProvider<Authorization>;
8
+ folderName?: string;
9
+ parentFolderId?: string;
10
+ selectedAppFolderId?: string;
11
+ drive?: GoogleDriveFileStore;
12
+ };
13
+ /**
14
+ * Backendless Google Drive implementation of the shared-backup transport.
15
+ * Authorization, encryption, and permission changes all happen in the client.
16
+ */
17
+ export declare class GoogleDriveSharedBackupTransport implements SharedBackupTransport {
18
+ private readonly options;
19
+ private readonly drive;
20
+ private storagePromise;
21
+ constructor(options: GoogleDriveSharedBackupTransportOptions);
22
+ ensureStorage(): Promise<SharedBackupStorage>;
23
+ listDatasets(): Promise<SharedDatasetFile[]>;
24
+ readDataset(fileId: string): Promise<VersionedSharedDataset>;
25
+ createDataset(datasetId: string, envelope: SharedBackupEnvelopeV1): Promise<VersionedSharedDataset>;
26
+ writeDataset(current: VersionedSharedDataset, envelope: SharedBackupEnvelopeV1): Promise<VersionedSharedDataset>;
27
+ grantExchangeAccess(emailAddress: string, options?: {
28
+ sendNotificationEmail?: boolean;
29
+ emailMessage?: string;
30
+ }): Promise<{
31
+ drivePermissionId: string;
32
+ appFolderId: string;
33
+ }>;
34
+ createInvitation(invitation: SharingInvitationV1): Promise<string>;
35
+ createKeyResponse(response: SharingPublicKeyResponseV1): Promise<string>;
36
+ listExchanges(options?: {
37
+ exchangeId?: string;
38
+ kind?: SharedExchangeFile["kind"];
39
+ }): Promise<SharedExchangeFile[]>;
40
+ readInvitation(fileId: string): Promise<SharingInvitationV1>;
41
+ readKeyResponse(fileId: string, expectedDrivePermissionId: string): Promise<SharedKeyResponseFile>;
42
+ deleteExchange(fileId: string): Promise<void>;
43
+ setDatasetPermission(fileId: string, emailAddress: string, role: Exclude<SharingRole, "owner">, options?: {
44
+ existingDirectPermissionId?: string;
45
+ hasInheritedReadAccess?: boolean;
46
+ }): Promise<SharedDatasetPermission>;
47
+ removeDatasetPermission(fileId: string, permissionId: string): Promise<void>;
48
+ private ensureStorageNow;
49
+ private listAll;
50
+ private properties;
51
+ private assertManagedFile;
52
+ private assertEnvelopeDataset;
53
+ private authorize;
54
+ }
55
+ //# sourceMappingURL=sharing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharing.d.ts","sourceRoot":"","sources":["../../../src/stores/google-drive/sharing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,295 @@
1
+ import { SyncKitError } from "../../core/errors.js";
2
+ import { parseSharedBackupEnvelopeV1, parseSharingInvitationV1, parseSharingPublicKeyResponseV1, } from "../../sharing/index.js";
3
+ import { assertDriveFileProvenance, ensureGoogleDriveSyncKitFolder, GoogleDriveFileStore, SYNC_KIT_APP_ID_PROPERTY, SYNC_KIT_DATASET_ID_PROPERTY, SYNC_KIT_KIND_PROPERTY, SYNC_KIT_PROTOCOL_PROPERTY, } from "./index.js";
4
+ const SHARING_PROTOCOL = "sharing-v1";
5
+ /**
6
+ * Backendless Google Drive implementation of the shared-backup transport.
7
+ * Authorization, encryption, and permission changes all happen in the client.
8
+ */
9
+ export class GoogleDriveSharedBackupTransport {
10
+ options;
11
+ drive;
12
+ storagePromise = null;
13
+ constructor(options) {
14
+ this.options = options;
15
+ if (!options.appId.trim())
16
+ throw new TypeError("appId must not be empty.");
17
+ this.drive = options.drive ?? new GoogleDriveFileStore();
18
+ }
19
+ async ensureStorage() {
20
+ this.storagePromise ??= this.ensureStorageNow().catch((error) => {
21
+ this.storagePromise = null;
22
+ throw error;
23
+ });
24
+ return this.storagePromise;
25
+ }
26
+ async listDatasets() {
27
+ const authorization = await this.authorize();
28
+ const storage = await this.ensureStorage();
29
+ const files = await this.listAll(authorization, {
30
+ parentId: storage.appFolderId,
31
+ appProperties: this.properties("dataset"),
32
+ });
33
+ return files.map((file) => ({
34
+ datasetId: requiredProperty(file, SYNC_KIT_DATASET_ID_PROPERTY, "dataset"),
35
+ fileId: file.fileId,
36
+ name: file.name,
37
+ ...(file.capabilities?.canEdit === undefined
38
+ ? {}
39
+ : { canEdit: file.capabilities.canEdit }),
40
+ }));
41
+ }
42
+ async readDataset(fileId) {
43
+ const authorization = await this.authorize();
44
+ const metadata = await this.drive.get(fileId, authorization);
45
+ this.assertManagedFile(metadata, "dataset");
46
+ const datasetId = requiredProperty(metadata, SYNC_KIT_DATASET_ID_PROPERTY, "dataset");
47
+ const document = await this.drive.readTextVersioned(fileId, authorization);
48
+ const version = document.etag ?? metadata.etag;
49
+ if (!version) {
50
+ throw new SyncKitError("state", "Google Drive did not expose an ETag for the dataset; a safe conditional write is unavailable.");
51
+ }
52
+ const envelope = parseSharedBackupEnvelopeV1(document.content);
53
+ if (envelope.appId !== this.options.appId ||
54
+ envelope.backupId !== datasetId) {
55
+ throw new SyncKitError("compatibility", "The Drive dataset metadata does not match its encrypted envelope.");
56
+ }
57
+ return {
58
+ datasetId,
59
+ fileId,
60
+ name: metadata.name,
61
+ ...(metadata.capabilities?.canEdit === undefined
62
+ ? {}
63
+ : { canEdit: metadata.capabilities.canEdit }),
64
+ envelope,
65
+ version,
66
+ };
67
+ }
68
+ async createDataset(datasetId, envelope) {
69
+ requireNonEmpty(datasetId, "datasetId");
70
+ this.assertEnvelopeDataset(datasetId, envelope);
71
+ const authorization = await this.authorize();
72
+ const storage = await this.ensureStorage();
73
+ const fileId = await this.drive.create(`${datasetId}.sync-kit.json`, JSON.stringify(envelope), authorization, {
74
+ parentId: storage.appFolderId,
75
+ contentType: "application/json",
76
+ appProperties: {
77
+ ...this.properties("dataset"),
78
+ [SYNC_KIT_DATASET_ID_PROPERTY]: datasetId,
79
+ },
80
+ });
81
+ return this.readDataset(fileId);
82
+ }
83
+ async writeDataset(current, envelope) {
84
+ this.assertEnvelopeDataset(current.datasetId, envelope);
85
+ if (envelope.parentRevisionId !== current.envelope.revisionId) {
86
+ throw new SyncKitError("conflict", "The new dataset revision does not descend from the version being replaced.");
87
+ }
88
+ const authorization = await this.authorize();
89
+ const written = await this.drive.write(current.fileId, JSON.stringify(envelope), authorization, {
90
+ contentType: "application/json",
91
+ ifMatch: current.version,
92
+ });
93
+ if (!written.etag)
94
+ return this.readDataset(current.fileId);
95
+ return {
96
+ ...current,
97
+ envelope,
98
+ version: written.etag,
99
+ };
100
+ }
101
+ async grantExchangeAccess(emailAddress, options = {}) {
102
+ requireNonEmpty(emailAddress, "emailAddress");
103
+ const authorization = await this.authorize();
104
+ const storage = await this.ensureStorage();
105
+ const appPermissionId = await this.drive.share(storage.appFolderId, emailAddress, "reader", authorization, options);
106
+ const exchangePermissionId = await this.drive.share(storage.exchangesFolderId, emailAddress, "writer", authorization, { sendNotificationEmail: false });
107
+ if (exchangePermissionId !== appPermissionId) {
108
+ throw new SyncKitError("provider", "Google Drive returned inconsistent permission IDs for one account.");
109
+ }
110
+ return {
111
+ drivePermissionId: appPermissionId,
112
+ appFolderId: storage.appFolderId,
113
+ };
114
+ }
115
+ async createInvitation(invitation) {
116
+ if (invitation.appId !== this.options.appId) {
117
+ throw new SyncKitError("compatibility", "The invitation belongs to another application.");
118
+ }
119
+ const authorization = await this.authorize();
120
+ const storage = await this.ensureStorage();
121
+ if (invitation.appFolderId !== storage.appFolderId) {
122
+ throw new SyncKitError("compatibility", "The invitation references another app folder.");
123
+ }
124
+ return this.drive.create(`${invitation.exchangeId}-invitation.json`, JSON.stringify(invitation), authorization, {
125
+ parentId: storage.exchangesFolderId,
126
+ contentType: "application/json",
127
+ appProperties: {
128
+ ...this.properties("invitation"),
129
+ "sync-kit-exchange-id": invitation.exchangeId,
130
+ },
131
+ });
132
+ }
133
+ async createKeyResponse(response) {
134
+ if (response.appId !== this.options.appId) {
135
+ throw new SyncKitError("compatibility", "The key response belongs to another application.");
136
+ }
137
+ const authorization = await this.authorize();
138
+ const storage = await this.ensureStorage();
139
+ return this.drive.create(`${response.exchangeId}-${response.keyId}-response.json`, JSON.stringify(response), authorization, {
140
+ parentId: storage.exchangesFolderId,
141
+ contentType: "application/json",
142
+ appProperties: {
143
+ ...this.properties("key-response"),
144
+ "sync-kit-exchange-id": response.exchangeId,
145
+ "sync-kit-key-id": response.keyId,
146
+ },
147
+ });
148
+ }
149
+ async listExchanges(options = {}) {
150
+ const authorization = await this.authorize();
151
+ const storage = await this.ensureStorage();
152
+ const files = await this.listAll(authorization, {
153
+ parentId: storage.exchangesFolderId,
154
+ appProperties: {
155
+ [SYNC_KIT_APP_ID_PROPERTY]: this.options.appId,
156
+ [SYNC_KIT_PROTOCOL_PROPERTY]: SHARING_PROTOCOL,
157
+ ...(options.kind
158
+ ? { [SYNC_KIT_KIND_PROPERTY]: options.kind }
159
+ : {}),
160
+ ...(options.exchangeId
161
+ ? { "sync-kit-exchange-id": options.exchangeId }
162
+ : {}),
163
+ },
164
+ });
165
+ return files.map((file) => {
166
+ const kind = requiredProperty(file, SYNC_KIT_KIND_PROPERTY, "exchange");
167
+ if (kind !== "invitation" && kind !== "key-response") {
168
+ throw new SyncKitError("compatibility", "A managed exchange file has an unsupported kind.");
169
+ }
170
+ const keyId = file.appProperties?.["sync-kit-key-id"];
171
+ return {
172
+ fileId: file.fileId,
173
+ exchangeId: requiredProperty(file, "sync-kit-exchange-id", "exchange"),
174
+ kind,
175
+ ...(keyId ? { keyId } : {}),
176
+ ...(file.createdTime ? { createdTime: file.createdTime } : {}),
177
+ };
178
+ });
179
+ }
180
+ async readInvitation(fileId) {
181
+ const authorization = await this.authorize();
182
+ const metadata = await this.drive.get(fileId, authorization);
183
+ this.assertManagedFile(metadata, "invitation");
184
+ return parseSharingInvitationV1(await this.drive.readText(fileId, authorization));
185
+ }
186
+ async readKeyResponse(fileId, expectedDrivePermissionId) {
187
+ const authorization = await this.authorize();
188
+ const metadata = await this.drive.get(fileId, authorization);
189
+ this.assertManagedFile(metadata, "key-response");
190
+ assertDriveFileProvenance(metadata, {
191
+ permissionId: expectedDrivePermissionId,
192
+ });
193
+ const response = parseSharingPublicKeyResponseV1(await this.drive.readText(fileId, authorization));
194
+ return {
195
+ fileId,
196
+ response,
197
+ ownerPermissionId: expectedDrivePermissionId,
198
+ };
199
+ }
200
+ async deleteExchange(fileId) {
201
+ await this.drive.delete(fileId, await this.authorize());
202
+ }
203
+ async setDatasetPermission(fileId, emailAddress, role, options = {}) {
204
+ const driveRole = role === "viewer" ? "reader" : "writer";
205
+ if (options.existingDirectPermissionId) {
206
+ await this.drive.updatePermission(fileId, options.existingDirectPermissionId, driveRole, await this.authorize());
207
+ return {
208
+ permissionId: options.existingDirectPermissionId,
209
+ role: driveRole,
210
+ };
211
+ }
212
+ if (driveRole === "reader" && options.hasInheritedReadAccess) {
213
+ return { role: "reader" };
214
+ }
215
+ const permissionId = await this.drive.share(fileId, emailAddress, driveRole, await this.authorize(), { sendNotificationEmail: false });
216
+ return { permissionId, role: driveRole };
217
+ }
218
+ async removeDatasetPermission(fileId, permissionId) {
219
+ await this.drive.removePermission(fileId, permissionId, await this.authorize());
220
+ }
221
+ async ensureStorageNow() {
222
+ const authorization = await this.authorize();
223
+ const appFolderId = this.options.selectedAppFolderId ??
224
+ (await ensureGoogleDriveSyncKitFolder(authorization, {
225
+ appId: this.options.appId,
226
+ ...(this.options.folderName
227
+ ? { folderName: this.options.folderName }
228
+ : {}),
229
+ ...(this.options.parentFolderId
230
+ ? { parentFolderId: this.options.parentFolderId }
231
+ : {}),
232
+ drive: this.drive,
233
+ })).appFolderId;
234
+ const exchangeProperties = this.properties("exchange-folder");
235
+ const existing = await this.drive.list(authorization, {
236
+ parentId: appFolderId,
237
+ appProperties: exchangeProperties,
238
+ });
239
+ const exchangesFolderId = existing.files.find((file) => file.name === "exchanges")?.fileId ??
240
+ (await this.drive.createFolder("exchanges", authorization, {
241
+ parentId: appFolderId,
242
+ appProperties: exchangeProperties,
243
+ writersCanShare: false,
244
+ }));
245
+ return { appFolderId, exchangesFolderId };
246
+ }
247
+ async listAll(authorization, options) {
248
+ const files = [];
249
+ let pageToken;
250
+ do {
251
+ const page = await this.drive.list(authorization, {
252
+ ...options,
253
+ ...(pageToken ? { pageToken } : {}),
254
+ });
255
+ files.push(...page.files);
256
+ pageToken = page.nextPageToken;
257
+ } while (pageToken);
258
+ return files;
259
+ }
260
+ properties(kind) {
261
+ return {
262
+ [SYNC_KIT_APP_ID_PROPERTY]: this.options.appId,
263
+ [SYNC_KIT_PROTOCOL_PROPERTY]: SHARING_PROTOCOL,
264
+ [SYNC_KIT_KIND_PROPERTY]: kind,
265
+ };
266
+ }
267
+ assertManagedFile(file, kind) {
268
+ if (file.appProperties?.[SYNC_KIT_APP_ID_PROPERTY] !== this.options.appId ||
269
+ file.appProperties?.[SYNC_KIT_PROTOCOL_PROPERTY] !== SHARING_PROTOCOL ||
270
+ file.appProperties?.[SYNC_KIT_KIND_PROPERTY] !== kind) {
271
+ throw new SyncKitError("compatibility", `The selected Drive file is not a managed ${kind} for this application.`);
272
+ }
273
+ }
274
+ assertEnvelopeDataset(datasetId, envelope) {
275
+ if (envelope.appId !== this.options.appId ||
276
+ envelope.backupId !== datasetId) {
277
+ throw new SyncKitError("compatibility", "The shared-backup envelope belongs to another dataset.");
278
+ }
279
+ }
280
+ authorize() {
281
+ return this.options.authorizationProvider.authorize();
282
+ }
283
+ }
284
+ function requiredProperty(file, name, label) {
285
+ const value = file.appProperties?.[name];
286
+ if (!value) {
287
+ throw new SyncKitError("compatibility", `A managed ${label} file is missing ${name}.`);
288
+ }
289
+ return value;
290
+ }
291
+ function requireNonEmpty(value, name) {
292
+ if (!value.trim())
293
+ throw new TypeError(`${name} must not be empty.`);
294
+ }
295
+ //# sourceMappingURL=sharing.js.map