@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.
- package/dist/sharing/appdata-identity-store.d.ts +36 -0
- package/dist/sharing/appdata-identity-store.d.ts.map +1 -0
- package/dist/sharing/appdata-identity-store.js +49 -0
- package/dist/sharing/appdata-identity-store.js.map +1 -0
- package/dist/sharing/migrating-identity-store.d.ts +29 -0
- package/dist/sharing/migrating-identity-store.d.ts.map +1 -0
- package/dist/sharing/migrating-identity-store.js +37 -0
- package/dist/sharing/migrating-identity-store.js.map +1 -0
- package/package.json +12 -1
|
@@ -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.
|
|
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",
|