@keyneom/sync-kit 0.2.0-rc.6 → 0.2.0-rc.7

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.
@@ -0,0 +1,36 @@
1
+ import type { Authorization } from "../core/types.js";
2
+ import { GoogleDriveAppDataStore } from "../stores/google-drive/index.js";
3
+ import { type ProtectedSharingIdentityStore, type ProtectedSharingIdentityV1 } from "./web-passkey.js";
4
+ export type DriveAppDataProtectedSharingIdentityStoreOptions = {
5
+ /**
6
+ * Supplies a Drive authorization scoped to `drive.appdata`. The same Google
7
+ * account on any device returns the same private app-data folder, which is
8
+ * how a single sharing identity follows the user across devices.
9
+ */
10
+ authorization(): Promise<Authorization>;
11
+ /** Overrides the app-data filename derived from the app id. */
12
+ filename?(appId: string): string;
13
+ drive?: GoogleDriveAppDataStore;
14
+ };
15
+ /**
16
+ * Hosts the passkey-wrapped sharing identity in the signed-in Google account's
17
+ * private `drive.appdata` folder instead of device-local storage. Because the
18
+ * record is already AES-GCM-encrypted with the passkey PRF secret, app-data is
19
+ * a plaintext-safe transport: Drive only ever sees an opaque JSON blob whose
20
+ * `encryptedPrivateKeys` field it cannot read.
21
+ *
22
+ * The single-user, multi-device continuity this restores is deliberately
23
+ * distinct from the sharing (multi-user) protocol: the identity here is one
24
+ * account's own key, replicated to its own devices, never granted to another
25
+ * participant.
26
+ */
27
+ export declare class DriveAppDataProtectedSharingIdentityStore implements ProtectedSharingIdentityStore {
28
+ private readonly options;
29
+ private readonly drive;
30
+ constructor(options: DriveAppDataProtectedSharingIdentityStoreOptions);
31
+ load(appId: string): Promise<ProtectedSharingIdentityV1 | null>;
32
+ save(record: ProtectedSharingIdentityV1): Promise<void>;
33
+ delete(appId: string): Promise<void>;
34
+ private filename;
35
+ }
36
+ //# sourceMappingURL=appdata-identity-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appdata-identity-store.d.ts","sourceRoot":"","sources":["../../src/sharing/appdata-identity-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAEL,KAAK,6BAA6B,EAClC,KAAK,0BAA0B,EAChC,MAAM,kBAAkB,CAAC;AAE1B,MAAM,MAAM,gDAAgD,GAAG;IAC7D;;;;OAIG;IACH,aAAa,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IACxC,+DAA+D;IAC/D,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,KAAK,CAAC,EAAE,uBAAuB,CAAC;CACjC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,qBAAa,yCACX,YAAW,6BAA6B;IAKtC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAH1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0B;gBAG7B,OAAO,EAAE,gDAAgD;IAKtE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC;IAQ/D,IAAI,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM1C,OAAO,CAAC,QAAQ;CAKjB"}
@@ -0,0 +1,49 @@
1
+ import { GoogleDriveAppDataStore } from "../stores/google-drive/index.js";
2
+ import { parseProtectedSharingIdentityV1, } from "./web-passkey.js";
3
+ /**
4
+ * Hosts the passkey-wrapped sharing identity in the signed-in Google account's
5
+ * private `drive.appdata` folder instead of device-local storage. Because the
6
+ * record is already AES-GCM-encrypted with the passkey PRF secret, app-data is
7
+ * a plaintext-safe transport: Drive only ever sees an opaque JSON blob whose
8
+ * `encryptedPrivateKeys` field it cannot read.
9
+ *
10
+ * The single-user, multi-device continuity this restores is deliberately
11
+ * distinct from the sharing (multi-user) protocol: the identity here is one
12
+ * account's own key, replicated to its own devices, never granted to another
13
+ * participant.
14
+ */
15
+ export class DriveAppDataProtectedSharingIdentityStore {
16
+ options;
17
+ drive;
18
+ constructor(options) {
19
+ this.options = options;
20
+ this.drive = options.drive ?? new GoogleDriveAppDataStore();
21
+ }
22
+ async load(appId) {
23
+ const authorization = await this.options.authorization();
24
+ const found = await this.drive.find(this.filename(appId), authorization);
25
+ if (!found)
26
+ return null;
27
+ const text = await this.drive.readText(found.fileId, authorization);
28
+ return parseProtectedSharingIdentityV1(text);
29
+ }
30
+ async save(record) {
31
+ const authorization = await this.options.authorization();
32
+ const name = this.filename(record.appId);
33
+ const found = await this.drive.find(name, authorization);
34
+ await this.drive.write(name, JSON.stringify(record), authorization, {
35
+ ...(found ? { existingId: found.fileId } : {}),
36
+ contentType: "application/json",
37
+ });
38
+ }
39
+ async delete(appId) {
40
+ const authorization = await this.options.authorization();
41
+ const found = await this.drive.find(this.filename(appId), authorization);
42
+ if (found)
43
+ await this.drive.delete(found.fileId, authorization);
44
+ }
45
+ filename(appId) {
46
+ return (this.options.filename?.(appId) ?? `sync-kit-sharing-identity-${appId}.json`);
47
+ }
48
+ }
49
+ //# sourceMappingURL=appdata-identity-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appdata-identity-store.js","sourceRoot":"","sources":["../../src/sharing/appdata-identity-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EACL,+BAA+B,GAGhC,MAAM,kBAAkB,CAAC;AAc1B;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,yCAAyC;IAMjC;IAHF,KAAK,CAA0B;IAEhD,YACmB,OAAyD;QAAzD,YAAO,GAAP,OAAO,CAAkD;QAE1E,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,uBAAuB,EAAE,CAAC;IAC9D,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAa;QACtB,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,CAAC;QACzE,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QACpE,OAAO,+BAA+B,CAAC,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAkC;QAC3C,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;QACzD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QACzD,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE;YAClE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9C,WAAW,EAAE,kBAAkB;SAChC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAa;QACxB,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,CAAC;QACzE,IAAI,KAAK;YAAE,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAClE,CAAC;IAEO,QAAQ,CAAC,KAAa;QAC5B,OAAO,CACL,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,IAAI,6BAA6B,KAAK,OAAO,CAC5E,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,29 @@
1
+ import { type ProtectedSharingIdentityStore, type ProtectedSharingIdentityV1 } from "./web-passkey.js";
2
+ export type MigratingProtectedSharingIdentityStoreOptions = {
3
+ /** Authoritative store (e.g. `drive.appdata`), read first and always written. */
4
+ primary: ProtectedSharingIdentityStore;
5
+ /**
6
+ * Pre-existing device-local store (e.g. IndexedDB / EncryptedSharedPreferences).
7
+ * Read only as a fallback; its blob is promoted to `primary` on first hit.
8
+ */
9
+ legacy: ProtectedSharingIdentityStore;
10
+ };
11
+ /**
12
+ * Bridges a device that already holds a passkey-wrapped identity in local
13
+ * storage to the app-data-hosted substrate without regenerating it. On the
14
+ * first load after upgrade the legacy blob is promoted to the primary store,
15
+ * after which the primary store is authoritative and local storage is only a
16
+ * cache the consumer may keep for a hot path.
17
+ *
18
+ * Promotion is best-effort: if the primary write fails (offline, revoked
19
+ * scope) the legacy identity is still returned, so a returning user never
20
+ * silently generates a fresh identity and loses access to their dataset.
21
+ */
22
+ export declare class MigratingProtectedSharingIdentityStore implements ProtectedSharingIdentityStore {
23
+ private readonly options;
24
+ constructor(options: MigratingProtectedSharingIdentityStoreOptions);
25
+ load(appId: string): Promise<ProtectedSharingIdentityV1 | null>;
26
+ save(record: ProtectedSharingIdentityV1): Promise<void>;
27
+ delete(appId: string): Promise<void>;
28
+ }
29
+ //# sourceMappingURL=migrating-identity-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrating-identity-store.d.ts","sourceRoot":"","sources":["../../src/sharing/migrating-identity-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,6BAA6B,EAClC,KAAK,0BAA0B,EAChC,MAAM,kBAAkB,CAAC;AAE1B,MAAM,MAAM,6CAA6C,GAAG;IAC1D,iFAAiF;IACjF,OAAO,EAAE,6BAA6B,CAAC;IACvC;;;OAGG;IACH,MAAM,EAAE,6BAA6B,CAAC;CACvC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,sCACX,YAAW,6BAA6B;IAGtC,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,6CAA6C;IAGnE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC;IAU/D,IAAI,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAI3C"}
@@ -0,0 +1,37 @@
1
+ import { parseProtectedSharingIdentityV1, } from "./web-passkey.js";
2
+ /**
3
+ * Bridges a device that already holds a passkey-wrapped identity in local
4
+ * storage to the app-data-hosted substrate without regenerating it. On the
5
+ * first load after upgrade the legacy blob is promoted to the primary store,
6
+ * after which the primary store is authoritative and local storage is only a
7
+ * cache the consumer may keep for a hot path.
8
+ *
9
+ * Promotion is best-effort: if the primary write fails (offline, revoked
10
+ * scope) the legacy identity is still returned, so a returning user never
11
+ * silently generates a fresh identity and loses access to their dataset.
12
+ */
13
+ export class MigratingProtectedSharingIdentityStore {
14
+ options;
15
+ constructor(options) {
16
+ this.options = options;
17
+ }
18
+ async load(appId) {
19
+ const primary = await this.options.primary.load(appId);
20
+ if (primary)
21
+ return parseProtectedSharingIdentityV1(primary);
22
+ const legacy = await this.options.legacy.load(appId);
23
+ if (!legacy)
24
+ return null;
25
+ const record = parseProtectedSharingIdentityV1(legacy);
26
+ await this.options.primary.save(record).catch(() => undefined);
27
+ return record;
28
+ }
29
+ async save(record) {
30
+ await this.options.primary.save(record);
31
+ }
32
+ async delete(appId) {
33
+ await this.options.primary.delete(appId);
34
+ await this.options.legacy.delete(appId).catch(() => undefined);
35
+ }
36
+ }
37
+ //# sourceMappingURL=migrating-identity-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrating-identity-store.js","sourceRoot":"","sources":["../../src/sharing/migrating-identity-store.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,+BAA+B,GAGhC,MAAM,kBAAkB,CAAC;AAY1B;;;;;;;;;;GAUG;AACH,MAAM,OAAO,sCAAsC;IAI9B;IADnB,YACmB,OAAsD;QAAtD,YAAO,GAAP,OAAO,CAA+C;IACtE,CAAC;IAEJ,KAAK,CAAC,IAAI,CAAC,KAAa;QACtB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvD,IAAI,OAAO;YAAE,OAAO,+BAA+B,CAAC,OAAO,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrD,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACzB,MAAM,MAAM,GAAG,+BAA+B,CAAC,MAAM,CAAC,CAAC;QACvD,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC/D,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAkC;QAC3C,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,KAAa;QACxB,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACjE,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keyneom/sync-kit",
3
- "version": "0.2.0-rc.6",
3
+ "version": "0.2.0-rc.7",
4
4
  "private": false,
5
5
  "description": "Provider-neutral encrypted application-data synchronization with opt-in browser adapters.",
6
6
  "keywords": [
@@ -68,6 +68,16 @@
68
68
  "import": "./dist/sharing/account-binding.js",
69
69
  "default": "./dist/sharing/account-binding.js"
70
70
  },
71
+ "./sharing/appdata-identity-store": {
72
+ "types": "./dist/sharing/appdata-identity-store.d.ts",
73
+ "import": "./dist/sharing/appdata-identity-store.js",
74
+ "default": "./dist/sharing/appdata-identity-store.js"
75
+ },
76
+ "./sharing/migrating-identity-store": {
77
+ "types": "./dist/sharing/migrating-identity-store.d.ts",
78
+ "import": "./dist/sharing/migrating-identity-store.js",
79
+ "default": "./dist/sharing/migrating-identity-store.js"
80
+ },
71
81
  "./sharing/lifecycle": {
72
82
  "types": "./dist/sharing/lifecycle.d.ts",
73
83
  "import": "./dist/sharing/lifecycle.js",
@@ -113,6 +123,7 @@
113
123
  "build": "tsc -p tsconfig.build.json",
114
124
  "check": "npm run fixtures:check && npm run native:check && npm run parity:check && npm run parity:sharing:check && npm run lint && npm run typecheck && npm test && npm run build && npm run pack:check",
115
125
  "fixtures": "node scripts/generate-v1-fixtures.mjs",
126
+ "fixtures:identity": "npm run build && node scripts/generate-protected-identity-fixture.mjs",
116
127
  "fixtures:check": "node scripts/check-v1-fixtures.mjs",
117
128
  "native:check": "bash scripts/check-native-sharing-fixture.sh",
118
129
  "parity:check": "bash scripts/check-js-kotlin-parity.sh",