@interocitor/core 0.0.0-beta.2
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/LICENSE +21 -0
- package/README.md +178 -0
- package/dist/adapters/cloudflare.d.ts +72 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -0
- package/dist/adapters/cloudflare.js +227 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/google-drive.d.ts +64 -0
- package/dist/adapters/google-drive.d.ts.map +1 -0
- package/dist/adapters/google-drive.js +340 -0
- package/dist/adapters/google-drive.js.map +1 -0
- package/dist/adapters/memory.d.ts +45 -0
- package/dist/adapters/memory.d.ts.map +1 -0
- package/dist/adapters/memory.js +129 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/webdav.d.ts +59 -0
- package/dist/adapters/webdav.d.ts.map +1 -0
- package/dist/adapters/webdav.js +247 -0
- package/dist/adapters/webdav.js.map +1 -0
- package/dist/core/codec.d.ts +20 -0
- package/dist/core/codec.d.ts.map +1 -0
- package/dist/core/codec.js +66 -0
- package/dist/core/codec.js.map +1 -0
- package/dist/core/compaction.d.ts +37 -0
- package/dist/core/compaction.d.ts.map +1 -0
- package/dist/core/compaction.js +134 -0
- package/dist/core/compaction.js.map +1 -0
- package/dist/core/crdt.d.ts +33 -0
- package/dist/core/crdt.d.ts.map +1 -0
- package/dist/core/crdt.js +188 -0
- package/dist/core/crdt.js.map +1 -0
- package/dist/core/flush.d.ts +9 -0
- package/dist/core/flush.d.ts.map +1 -0
- package/dist/core/flush.js +41 -0
- package/dist/core/flush.js.map +1 -0
- package/dist/core/hlc.d.ts +25 -0
- package/dist/core/hlc.d.ts.map +1 -0
- package/dist/core/hlc.js +76 -0
- package/dist/core/hlc.js.map +1 -0
- package/dist/core/internals.d.ts +25 -0
- package/dist/core/internals.d.ts.map +1 -0
- package/dist/core/internals.js +54 -0
- package/dist/core/internals.js.map +1 -0
- package/dist/core/manifest.d.ts +31 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +111 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/pull.d.ts +26 -0
- package/dist/core/pull.d.ts.map +1 -0
- package/dist/core/pull.js +98 -0
- package/dist/core/pull.js.map +1 -0
- package/dist/core/row-id.d.ts +12 -0
- package/dist/core/row-id.d.ts.map +1 -0
- package/dist/core/row-id.js +12 -0
- package/dist/core/row-id.js.map +1 -0
- package/dist/core/schema-types.d.ts +13 -0
- package/dist/core/schema-types.d.ts.map +1 -0
- package/dist/core/schema-types.js +18 -0
- package/dist/core/schema-types.js.map +1 -0
- package/dist/core/schema-types.type-test.d.ts +2 -0
- package/dist/core/schema-types.type-test.d.ts.map +1 -0
- package/dist/core/schema-types.type-test.js +149 -0
- package/dist/core/schema-types.type-test.js.map +1 -0
- package/dist/core/sync-engine.d.ts +158 -0
- package/dist/core/sync-engine.d.ts.map +1 -0
- package/dist/core/sync-engine.js +714 -0
- package/dist/core/sync-engine.js.map +1 -0
- package/dist/core/table.d.ts +60 -0
- package/dist/core/table.d.ts.map +1 -0
- package/dist/core/table.js +106 -0
- package/dist/core/table.js.map +1 -0
- package/dist/core/types.d.ts +478 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +7 -0
- package/dist/core/types.js.map +1 -0
- package/dist/crypto/encryption.d.ts +57 -0
- package/dist/crypto/encryption.d.ts.map +1 -0
- package/dist/crypto/encryption.js +195 -0
- package/dist/crypto/encryption.js.map +1 -0
- package/dist/crypto/keys.d.ts +48 -0
- package/dist/crypto/keys.d.ts.map +1 -0
- package/dist/crypto/keys.js +55 -0
- package/dist/crypto/keys.js.map +1 -0
- package/dist/handshake/channel.d.ts +117 -0
- package/dist/handshake/channel.d.ts.map +1 -0
- package/dist/handshake/channel.js +246 -0
- package/dist/handshake/channel.js.map +1 -0
- package/dist/handshake/index.d.ts +213 -0
- package/dist/handshake/index.d.ts.map +1 -0
- package/dist/handshake/index.js +182 -0
- package/dist/handshake/index.js.map +1 -0
- package/dist/handshake/qr.d.ts +100 -0
- package/dist/handshake/qr.d.ts.map +1 -0
- package/dist/handshake/qr.js +103 -0
- package/dist/handshake/qr.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/storage/credential-store.d.ts +99 -0
- package/dist/storage/credential-store.d.ts.map +1 -0
- package/dist/storage/credential-store.js +309 -0
- package/dist/storage/credential-store.js.map +1 -0
- package/dist/storage/local-store.d.ts +56 -0
- package/dist/storage/local-store.d.ts.map +1 -0
- package/dist/storage/local-store.js +411 -0
- package/dist/storage/local-store.js.map +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The UI Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://github.com/TheUiTeam/interocitor">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/TheUiTeam/interocitor/main/docs/assets/hero.svg" alt="interocitor" width="560"/>
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>The JavaScript mailbox that can't read your mail.</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
# interocitor
|
|
12
|
+
|
|
13
|
+
Encrypted local-first CRDT sync for browser apps.
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
Interocitor is your app's __personal keychain__.
|
|
18
|
+
Your devices hold the key. The cloud is only a mailbox. It carries encrypted sync artifacts and cannot read your mail.
|
|
19
|
+
|
|
20
|
+
What this means:
|
|
21
|
+
- encryption on by default
|
|
22
|
+
- reads and writes are local-first
|
|
23
|
+
- sync uses a remote mailbox, not a trusted database
|
|
24
|
+
- restore is explicit app UI, not automatic magic
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { SyncEngine, createRowId } from 'interocitor';
|
|
30
|
+
import { WebDAVAdapter } from 'interocitor/adapters/webdav';
|
|
31
|
+
|
|
32
|
+
const adapter = new WebDAVAdapter({
|
|
33
|
+
baseUrl: 'https://your-webdav-server.example.com',
|
|
34
|
+
auth: { username: 'user', password: 'pass' },
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const engine = new SyncEngine(adapter, {
|
|
38
|
+
remotePath: '/MyApp',
|
|
39
|
+
dbName: 'my-app',
|
|
40
|
+
appName: 'My App',
|
|
41
|
+
// encrypted by default; set encrypted: false to opt out
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await engine.init();
|
|
45
|
+
await engine.connect();
|
|
46
|
+
|
|
47
|
+
const id = createRowId({ prefix: 'todo' });
|
|
48
|
+
await engine.put('todos', id, {
|
|
49
|
+
text: 'Ship privacy-first sync',
|
|
50
|
+
done: false,
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## How sync works
|
|
55
|
+
|
|
56
|
+
```mermaid
|
|
57
|
+
flowchart LR
|
|
58
|
+
A[Browser app] --> B[Interocitor engine]
|
|
59
|
+
B --> C[Local store]
|
|
60
|
+
B --> D[Encrypt changes locally]
|
|
61
|
+
D --> E[Adapter]
|
|
62
|
+
E --> F[Remote mailbox\n(ciphertext only)]
|
|
63
|
+
F --> E
|
|
64
|
+
E --> G[Download ciphertext]
|
|
65
|
+
G --> H[Decrypt locally]
|
|
66
|
+
H --> B
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Core API at a glance
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
await engine.init();
|
|
73
|
+
await engine.connect();
|
|
74
|
+
await engine.put(table, id, data);
|
|
75
|
+
await engine.get(table, id);
|
|
76
|
+
await engine.query(table);
|
|
77
|
+
await engine.sync();
|
|
78
|
+
|
|
79
|
+
await engine.secureWithBiometrics(); // optional, explicit keychain enrollment
|
|
80
|
+
await engine.restoreWithBiometrics(); // explicit recovery flow
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Row IDs
|
|
84
|
+
|
|
85
|
+
Use stable string IDs for synced rows. Do not use auto-increment IDs.
|
|
86
|
+
|
|
87
|
+
Good default:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
import { createRowId } from 'interocitor';
|
|
91
|
+
|
|
92
|
+
const id = createRowId({ prefix: 'task' });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`createRowId()` uses platform crypto. No extra dependency needed.
|
|
96
|
+
|
|
97
|
+
## Offline guarantee
|
|
98
|
+
|
|
99
|
+
Every read and write hits the local store. No network required.
|
|
100
|
+
`sync()` is the only call that touches the remote mailbox.
|
|
101
|
+
|
|
102
|
+
| Operation | Network? |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| `init()` | No |
|
|
105
|
+
| `put()` | No |
|
|
106
|
+
| `delete()` | No |
|
|
107
|
+
| `get()` / `query()` | No |
|
|
108
|
+
| `sync()` | Yes, if adapter configured |
|
|
109
|
+
|
|
110
|
+
## Adapters
|
|
111
|
+
|
|
112
|
+
### Google Drive
|
|
113
|
+
For zero new backend infrastructure where Google Drive is acceptable as the encrypted mailbox.
|
|
114
|
+
|
|
115
|
+
### WebDAV
|
|
116
|
+
For self-hosted sync, local demos, and inspectable remote artifacts.
|
|
117
|
+
|
|
118
|
+
### Cloudflare (experimental)
|
|
119
|
+
For Interocitor-native transport flows with invalidation fanout and maintenance endpoints while keeping decryption client-side.
|
|
120
|
+
|
|
121
|
+
### Memory
|
|
122
|
+
For tests and adapter-contract validation.
|
|
123
|
+
|
|
124
|
+
## Local store
|
|
125
|
+
|
|
126
|
+
Interocitor is a sync engine, not a database. The local store is a pluggable abstraction (`LocalStoreAdapter`). The browser default uses IndexedDB. The Swift package uses SQLite. You don't need to care which — reads, writes, and queries go through the engine API.
|
|
127
|
+
|
|
128
|
+
## CRDT strategy
|
|
129
|
+
|
|
130
|
+
Interocitor uses per-column CRDTs with hybrid logical clocks (HLC). All merge happens on the client. Remote storage is just a byte pipe.
|
|
131
|
+
|
|
132
|
+
Conflict default: `'remote-wins'`.
|
|
133
|
+
Override at database, table, or field level.
|
|
134
|
+
|
|
135
|
+
## Events
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
engine.on('change', (event) => {
|
|
139
|
+
console.log(event.type, event.table, event.id);
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Local TODO demo (WebDAV)
|
|
144
|
+
|
|
145
|
+
From the monorepo root:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
yarn demo:todo
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## What this is not
|
|
152
|
+
|
|
153
|
+
- not Firebase
|
|
154
|
+
- not Fireproof
|
|
155
|
+
- not PowerSync
|
|
156
|
+
- not Replicache
|
|
157
|
+
- not a hosted backend
|
|
158
|
+
- not a query engine over the cloud
|
|
159
|
+
- not a server-trusted merge layer
|
|
160
|
+
|
|
161
|
+
## Tests
|
|
162
|
+
|
|
163
|
+
From the monorepo root:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
yarn workspace interocitor test
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Package context
|
|
170
|
+
|
|
171
|
+
This package is the main JavaScript/TypeScript runtime in the monorepo. See also:
|
|
172
|
+
- root `README.md` — monorepo overview
|
|
173
|
+
- `packages/interocitor-swift` — Swift client
|
|
174
|
+
- `examples/todo-webdav` — local demo app
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Worker + D1 Storage Adapter (Interocitor-native protocol)
|
|
3
|
+
*
|
|
4
|
+
* This adapter is a perfect-fit backend for Interocitor only.
|
|
5
|
+
* It does NOT parse XML and does NOT depend on WebDAV compatibility.
|
|
6
|
+
*
|
|
7
|
+
* Base URL shape:
|
|
8
|
+
* https://<worker>/io/<prefix>
|
|
9
|
+
*
|
|
10
|
+
* The adapter derives:
|
|
11
|
+
* wss://<worker>/notify/<prefix> (WebSocket invalidations via InterocitorRelay DO)
|
|
12
|
+
*/
|
|
13
|
+
import type { StorageAdapter, FileEntry } from '../core/types.ts';
|
|
14
|
+
export interface CloudflareAdapterConfig {
|
|
15
|
+
/** Worker IO base URL that includes prefix, e.g. https://worker/io/team-a */
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
/** Optional bearer for server/cost protection (INTEROCITOR_ACCESS_TOKEN). */
|
|
18
|
+
token?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Interocitor-native Cloudflare adapter for Worker + D1 based deployments.
|
|
22
|
+
*
|
|
23
|
+
* Use this when you want a purpose-fit backend with optional WebSocket-driven
|
|
24
|
+
* invalidation instead of a generic file protocol like WebDAV.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const adapter = new CloudflareAdapter({
|
|
29
|
+
* baseUrl: 'https://example.com/io/team-a',
|
|
30
|
+
* token: 'optional-bearer-token',
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
/** Config shape embedded in QR payloads for CloudflareAdapter. Credentials excluded. */
|
|
35
|
+
export interface CloudflareHandshakeConfig {
|
|
36
|
+
/** Worker IO base URL including the /io/<prefix> path segment. */
|
|
37
|
+
baseUrl: string;
|
|
38
|
+
}
|
|
39
|
+
export declare class CloudflareAdapter implements StorageAdapter {
|
|
40
|
+
readonly name = "cloudflare";
|
|
41
|
+
private readonly config;
|
|
42
|
+
private authenticated;
|
|
43
|
+
constructor(config: CloudflareAdapterConfig);
|
|
44
|
+
private headers;
|
|
45
|
+
private get ioBaseUrl();
|
|
46
|
+
private get notifyUrl();
|
|
47
|
+
private ioUrl;
|
|
48
|
+
private fileUrl;
|
|
49
|
+
authenticate(): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Returns the worker base URL (without credentials) for embedding in a QR payload.
|
|
52
|
+
* The scanner uses this to point their CloudflareAdapter at the same worker shard.
|
|
53
|
+
*/
|
|
54
|
+
getHandshakeConfig(): string;
|
|
55
|
+
isAuthenticated(): boolean;
|
|
56
|
+
subscribeToInvalidations(onInvalidate: (payload: {
|
|
57
|
+
type: string;
|
|
58
|
+
path: string;
|
|
59
|
+
ts: number;
|
|
60
|
+
}) => void, hooks?: {
|
|
61
|
+
onReady?: () => void;
|
|
62
|
+
onError?: () => void;
|
|
63
|
+
}): () => void;
|
|
64
|
+
ensureFolder(path: string): Promise<void>;
|
|
65
|
+
listFiles(path: string): Promise<FileEntry[]>;
|
|
66
|
+
listFolders(path: string): Promise<string[]>;
|
|
67
|
+
readFile(path: string): Promise<Uint8Array>;
|
|
68
|
+
writeFile(path: string, data: Uint8Array | string): Promise<void>;
|
|
69
|
+
deleteFile(path: string): Promise<void>;
|
|
70
|
+
getFileMetadata(path: string): Promise<FileEntry | null>;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=cloudflare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElE,MAAM,WAAW,uBAAuB;IACtC,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAUD;;;;;;;;;;;;;GAaG;AACH,wFAAwF;AACxF,MAAM,WAAW,yBAAyB;IACxC,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,iBAAkB,YAAW,cAAc;IACtD,QAAQ,CAAC,IAAI,gBAAgB;IAE7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,aAAa,CAAS;gBAElB,MAAM,EAAE,uBAAuB;IAI3C,OAAO,CAAC,OAAO;IAQf,OAAO,KAAK,SAAS,GAMpB;IAED,OAAO,KAAK,SAAS,GAWpB;IAED,OAAO,CAAC,KAAK;IAKb,OAAO,CAAC,OAAO;IAMT,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBnC;;;OAGG;IACH,kBAAkB,IAAI,MAAM;IAK5B,eAAe,IAAI,OAAO;IAI1B,wBAAwB,CACtB,YAAY,EAAE,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,EAC3E,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE,GACrD,MAAM,IAAI;IAwDP,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYzC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAqB7C,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAe5C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAa3C,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcjE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;CAsB/D"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Worker + D1 Storage Adapter (Interocitor-native protocol)
|
|
3
|
+
*
|
|
4
|
+
* This adapter is a perfect-fit backend for Interocitor only.
|
|
5
|
+
* It does NOT parse XML and does NOT depend on WebDAV compatibility.
|
|
6
|
+
*
|
|
7
|
+
* Base URL shape:
|
|
8
|
+
* https://<worker>/io/<prefix>
|
|
9
|
+
*
|
|
10
|
+
* The adapter derives:
|
|
11
|
+
* wss://<worker>/notify/<prefix> (WebSocket invalidations via InterocitorRelay DO)
|
|
12
|
+
*/
|
|
13
|
+
export class CloudflareAdapter {
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.name = 'cloudflare';
|
|
16
|
+
this.authenticated = false;
|
|
17
|
+
this.config = { ...config, baseUrl: config.baseUrl.replace(/\/$/, '') };
|
|
18
|
+
}
|
|
19
|
+
headers(extra) {
|
|
20
|
+
const base = {};
|
|
21
|
+
if (this.config.token) {
|
|
22
|
+
base.Authorization = `Bearer ${this.config.token}`;
|
|
23
|
+
}
|
|
24
|
+
return { ...base, ...extra };
|
|
25
|
+
}
|
|
26
|
+
get ioBaseUrl() {
|
|
27
|
+
const u = new URL(this.config.baseUrl);
|
|
28
|
+
if (!u.pathname.includes('/io/')) {
|
|
29
|
+
throw new Error('CloudflareAdapter baseUrl must include /io/<prefix>');
|
|
30
|
+
}
|
|
31
|
+
return u.toString().replace(/\/$/, '');
|
|
32
|
+
}
|
|
33
|
+
get notifyUrl() {
|
|
34
|
+
const u = new URL(this.config.baseUrl);
|
|
35
|
+
if (!u.pathname.includes('/io/')) {
|
|
36
|
+
throw new Error('CloudflareAdapter baseUrl must include /io/<prefix>');
|
|
37
|
+
}
|
|
38
|
+
u.pathname = u.pathname.replace('/io/', '/notify/');
|
|
39
|
+
u.protocol = u.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
40
|
+
if (this.config.token) {
|
|
41
|
+
u.searchParams.set('access_token', this.config.token);
|
|
42
|
+
}
|
|
43
|
+
return u.toString().replace(/\/$/, '');
|
|
44
|
+
}
|
|
45
|
+
ioUrl(pathname) {
|
|
46
|
+
const clean = pathname.startsWith('/') ? pathname : `/${pathname}`;
|
|
47
|
+
return `${this.ioBaseUrl}${clean}`;
|
|
48
|
+
}
|
|
49
|
+
fileUrl(path) {
|
|
50
|
+
const u = new URL(this.ioUrl('/file'));
|
|
51
|
+
u.searchParams.set('path', path);
|
|
52
|
+
return u.toString();
|
|
53
|
+
}
|
|
54
|
+
async authenticate() {
|
|
55
|
+
const res = await fetch(this.ioUrl('/health'), {
|
|
56
|
+
method: 'GET',
|
|
57
|
+
headers: this.headers(),
|
|
58
|
+
});
|
|
59
|
+
if (res.ok) {
|
|
60
|
+
this.authenticated = true;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (res.status === 401 || res.status === 403) {
|
|
64
|
+
throw new Error('Cloudflare Worker auth failed — check your access token');
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`Cloudflare Worker unreachable: HTTP ${res.status}`);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Returns the worker base URL (without credentials) for embedding in a QR payload.
|
|
70
|
+
* The scanner uses this to point their CloudflareAdapter at the same worker shard.
|
|
71
|
+
*/
|
|
72
|
+
getHandshakeConfig() {
|
|
73
|
+
const cfg = { baseUrl: this.config.baseUrl };
|
|
74
|
+
return JSON.stringify(cfg);
|
|
75
|
+
}
|
|
76
|
+
isAuthenticated() {
|
|
77
|
+
return this.authenticated;
|
|
78
|
+
}
|
|
79
|
+
subscribeToInvalidations(onInvalidate, hooks) {
|
|
80
|
+
let ws = null;
|
|
81
|
+
let cancelled = false;
|
|
82
|
+
let backoffMs = 1000;
|
|
83
|
+
const MAX_BACKOFF_MS = 30000;
|
|
84
|
+
const connect = () => {
|
|
85
|
+
if (cancelled)
|
|
86
|
+
return;
|
|
87
|
+
try {
|
|
88
|
+
ws = new WebSocket(this.notifyUrl);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
hooks?.onError?.();
|
|
92
|
+
scheduleReconnect();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
ws.onopen = () => {
|
|
96
|
+
backoffMs = 1000;
|
|
97
|
+
hooks?.onReady?.();
|
|
98
|
+
};
|
|
99
|
+
ws.onmessage = (e) => {
|
|
100
|
+
try {
|
|
101
|
+
const msg = JSON.parse(e.data);
|
|
102
|
+
if (msg.type === 'invalidation' || msg.type === 'invalidate' || msg.type === 'compact') {
|
|
103
|
+
onInvalidate(msg);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
onInvalidate({ type: 'unknown', path: '/', ts: Date.now() });
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
ws.onerror = () => {
|
|
111
|
+
hooks?.onError?.();
|
|
112
|
+
};
|
|
113
|
+
ws.onclose = () => {
|
|
114
|
+
if (!cancelled)
|
|
115
|
+
scheduleReconnect();
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
const scheduleReconnect = () => {
|
|
119
|
+
if (cancelled)
|
|
120
|
+
return;
|
|
121
|
+
setTimeout(() => connect(), backoffMs);
|
|
122
|
+
backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS);
|
|
123
|
+
};
|
|
124
|
+
connect();
|
|
125
|
+
return () => {
|
|
126
|
+
cancelled = true;
|
|
127
|
+
try {
|
|
128
|
+
ws?.close(1000, 'unsubscribed');
|
|
129
|
+
}
|
|
130
|
+
catch { }
|
|
131
|
+
ws = null;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async ensureFolder(path) {
|
|
135
|
+
const res = await fetch(this.ioUrl('/ensure-folder'), {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
headers: this.headers({ 'Content-Type': 'application/json; charset=utf-8' }),
|
|
138
|
+
body: JSON.stringify({ path }),
|
|
139
|
+
});
|
|
140
|
+
if (!res.ok && res.status !== 405) {
|
|
141
|
+
throw new Error(`Failed to ensure folder ${path}: HTTP ${res.status}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async listFiles(path) {
|
|
145
|
+
const res = await fetch(this.ioUrl('/list-files'), {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
headers: this.headers({ 'Content-Type': 'application/json; charset=utf-8' }),
|
|
148
|
+
body: JSON.stringify({ path }),
|
|
149
|
+
});
|
|
150
|
+
if (!res.ok) {
|
|
151
|
+
throw new Error(`Failed to list files for ${path}: HTTP ${res.status}`);
|
|
152
|
+
}
|
|
153
|
+
const payload = await res.json();
|
|
154
|
+
return (payload.files ?? []).map((f) => ({
|
|
155
|
+
name: f.name,
|
|
156
|
+
path: f.path,
|
|
157
|
+
size: f.size,
|
|
158
|
+
modifiedTime: f.modifiedTime,
|
|
159
|
+
etag: f.etag,
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
async listFolders(path) {
|
|
163
|
+
const res = await fetch(this.ioUrl('/list-folders'), {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
headers: this.headers({ 'Content-Type': 'application/json; charset=utf-8' }),
|
|
166
|
+
body: JSON.stringify({ path }),
|
|
167
|
+
});
|
|
168
|
+
if (!res.ok) {
|
|
169
|
+
throw new Error(`Failed to list folders for ${path}: HTTP ${res.status}`);
|
|
170
|
+
}
|
|
171
|
+
const payload = await res.json();
|
|
172
|
+
return payload.folders ?? [];
|
|
173
|
+
}
|
|
174
|
+
async readFile(path) {
|
|
175
|
+
const res = await fetch(this.fileUrl(path), {
|
|
176
|
+
method: 'GET',
|
|
177
|
+
headers: this.headers(),
|
|
178
|
+
});
|
|
179
|
+
if (!res.ok) {
|
|
180
|
+
throw new Error(`Failed to read ${path}: HTTP ${res.status}`);
|
|
181
|
+
}
|
|
182
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
183
|
+
}
|
|
184
|
+
async writeFile(path, data) {
|
|
185
|
+
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
|
|
186
|
+
const res = await fetch(this.fileUrl(path), {
|
|
187
|
+
method: 'PUT',
|
|
188
|
+
headers: this.headers({ 'Content-Type': 'application/octet-stream' }),
|
|
189
|
+
body: bytes,
|
|
190
|
+
});
|
|
191
|
+
if (!res.ok && res.status !== 201 && res.status !== 204) {
|
|
192
|
+
throw new Error(`Failed to write ${path}: HTTP ${res.status}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async deleteFile(path) {
|
|
196
|
+
const res = await fetch(this.fileUrl(path), {
|
|
197
|
+
method: 'DELETE',
|
|
198
|
+
headers: this.headers(),
|
|
199
|
+
});
|
|
200
|
+
if (!res.ok && res.status !== 404 && res.status !== 405) {
|
|
201
|
+
throw new Error(`Failed to delete ${path}: HTTP ${res.status}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async getFileMetadata(path) {
|
|
205
|
+
const res = await fetch(this.ioUrl('/metadata'), {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: this.headers({ 'Content-Type': 'application/json; charset=utf-8' }),
|
|
208
|
+
body: JSON.stringify({ path }),
|
|
209
|
+
});
|
|
210
|
+
if (res.status === 404)
|
|
211
|
+
return null;
|
|
212
|
+
if (!res.ok)
|
|
213
|
+
return null;
|
|
214
|
+
const payload = await res.json();
|
|
215
|
+
const f = payload.file;
|
|
216
|
+
if (!f)
|
|
217
|
+
return null;
|
|
218
|
+
return {
|
|
219
|
+
name: f.name,
|
|
220
|
+
path: f.path,
|
|
221
|
+
size: f.size,
|
|
222
|
+
modifiedTime: f.modifiedTime,
|
|
223
|
+
etag: f.etag,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../src/adapters/cloudflare.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Drive Storage Adapter
|
|
3
|
+
*
|
|
4
|
+
* Uses Google Drive API v3 via OAuth2 with `drive.file` scope.
|
|
5
|
+
* The app can only see files it created or the user explicitly shared.
|
|
6
|
+
*
|
|
7
|
+
* Requires: Google API client ID from Google Cloud Console.
|
|
8
|
+
* Auth flow: Google Identity Services (GIS) popup or redirect.
|
|
9
|
+
*/
|
|
10
|
+
import type { StorageAdapter, FileEntry } from '../core/types.ts';
|
|
11
|
+
interface GoogleDriveConfig {
|
|
12
|
+
clientId: string;
|
|
13
|
+
/** Redirect URI for OAuth (default: current origin) */
|
|
14
|
+
redirectUri?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Google Drive adapter using the browser OAuth token flow.
|
|
18
|
+
*
|
|
19
|
+
* This is the easiest zero-infrastructure option when your users already live
|
|
20
|
+
* in Google Workspace or personal Drive.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const adapter = new GoogleDriveAdapter({ clientId: 'YOUR_GOOGLE_CLIENT_ID' });
|
|
25
|
+
* const engine = new SyncEngine(adapter, { remotePath: '/MyApp' });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare class GoogleDriveAdapter implements StorageAdapter {
|
|
29
|
+
readonly name = "google-drive";
|
|
30
|
+
private config;
|
|
31
|
+
private accessToken;
|
|
32
|
+
private fileIdCache;
|
|
33
|
+
private folderIdCache;
|
|
34
|
+
constructor(config: GoogleDriveConfig);
|
|
35
|
+
authenticate(): Promise<void>;
|
|
36
|
+
/** Set token directly (for apps that handle their own OAuth flow). */
|
|
37
|
+
setAccessToken(token: string): void;
|
|
38
|
+
/**
|
|
39
|
+
* Returns the Google OAuth clientId for embedding in a QR payload.
|
|
40
|
+
* The scanner uses this to configure their GoogleDriveAdapter.
|
|
41
|
+
* The OAuth flow (and resulting access token) is performed separately by the user.
|
|
42
|
+
*/
|
|
43
|
+
getHandshakeConfig(): string;
|
|
44
|
+
isAuthenticated(): boolean;
|
|
45
|
+
private verifyToken;
|
|
46
|
+
private headers;
|
|
47
|
+
/**
|
|
48
|
+
* Resolve a path like "/Interocitor/changes/dev_abc.ndjson"
|
|
49
|
+
* to a Google Drive file ID by walking the folder tree.
|
|
50
|
+
*/
|
|
51
|
+
private resolveFileId;
|
|
52
|
+
private resolveFolderId;
|
|
53
|
+
ensureFolder(path: string): Promise<void>;
|
|
54
|
+
listFiles(folderPath: string): Promise<FileEntry[]>;
|
|
55
|
+
listFolders(folderPath: string): Promise<string[]>;
|
|
56
|
+
readFile(path: string): Promise<Uint8Array>;
|
|
57
|
+
writeFile(path: string, data: Uint8Array | string): Promise<void>;
|
|
58
|
+
deleteFile(path: string): Promise<void>;
|
|
59
|
+
getFileMetadata(path: string): Promise<FileEntry | null>;
|
|
60
|
+
/** Clear caches (useful after compaction deletes files). */
|
|
61
|
+
clearCache(): void;
|
|
62
|
+
}
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=google-drive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google-drive.d.ts","sourceRoot":"","sources":["../../src/adapters/google-drive.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAMlE,UAAU,iBAAiB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,kBAAmB,YAAW,cAAc;IACvD,QAAQ,CAAC,IAAI,kBAAkB;IAE/B,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,WAAW,CAAuB;IAG1C,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,aAAa,CAAkC;gBAE3C,MAAM,EAAE,iBAAiB;IAM/B,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAkDnC,sEAAsE;IACtE,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKnC;;;;OAIG;IACH,kBAAkB,IAAI,MAAM;IAI5B,eAAe,IAAI,OAAO;YAIZ,WAAW;IAWzB,OAAO,CAAC,OAAO;IAOf;;;OAGG;YACW,aAAa;YAyCb,eAAe;IAiCvB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6CzC,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAwBnD,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAclD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAc3C,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4EjE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAoB9D,4DAA4D;IAC5D,UAAU,IAAI,IAAI;CAInB"}
|