@absolutejs/sync-expo 0.0.1
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 +88 -0
- package/README.md +19 -0
- package/dist/bridge.d.ts +43 -0
- package/dist/client.d.ts +20 -0
- package/dist/client.js +255 -0
- package/dist/client.js.map +10 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +1392 -0
- package/dist/index.js.map +15 -0
- package/dist/store.d.ts +28 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
**Licensor:** Alex Kahn
|
|
4
|
+
|
|
5
|
+
**Licensed Work:** @absolutejs/sync-expo (https://github.com/absolutejs/sync-expo)
|
|
6
|
+
|
|
7
|
+
**Change Date:** May 29, 2030
|
|
8
|
+
|
|
9
|
+
**Change License:** Apache License, Version 2.0
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Terms
|
|
14
|
+
|
|
15
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
16
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
17
|
+
Licensor may make an Additional Use Grant, permitting limited production use.
|
|
18
|
+
|
|
19
|
+
### Additional Use Grant
|
|
20
|
+
|
|
21
|
+
You may use the Licensed Work in production, provided your use does not include
|
|
22
|
+
any of the following:
|
|
23
|
+
|
|
24
|
+
1. **Offering a Competing Service.** You may not offer the Licensed Work, or
|
|
25
|
+
any derivative or substantial portion of it, to third parties as a hosted or
|
|
26
|
+
managed service that competes with a hosted reactive sync engine, realtime database, or live-collaboration backend service (including, but not limited to, services like Convex, Liveblocks, Zero, Replicache, ElectricSQL, RxDB Cloud, or PowerSync). This includes any
|
|
27
|
+
product whose primary value to its users is the functionality the Licensed
|
|
28
|
+
Work provides.
|
|
29
|
+
|
|
30
|
+
2. **Resale or Redistribution as a Standalone Product.** You may not sell,
|
|
31
|
+
license, or distribute the Licensed Work, or any derivative or fork of it,
|
|
32
|
+
as a standalone commercial product.
|
|
33
|
+
|
|
34
|
+
3. **Removal of Attribution.** Any derivative work, fork, or redistribution of
|
|
35
|
+
the Licensed Work must prominently credit AbsoluteJS and include a link to
|
|
36
|
+
the original project repository (https://github.com/absolutejs/sync-expo).
|
|
37
|
+
|
|
38
|
+
For clarity, the following uses are expressly permitted:
|
|
39
|
+
|
|
40
|
+
- Using the Licensed Work to build and operate your own applications, websites,
|
|
41
|
+
internal tools, or SaaS products (whether commercial or non-commercial), so
|
|
42
|
+
long as the Licensed Work itself is not the primary product you are selling.
|
|
43
|
+
- Using the Licensed Work as a dependency in commercial software you build and
|
|
44
|
+
sell, as long as the software is not itself a competing managed service of
|
|
45
|
+
the kind described in clause 1.
|
|
46
|
+
- Providing consulting, development, or professional services to clients using
|
|
47
|
+
the Licensed Work.
|
|
48
|
+
- Forking and modifying the Licensed Work for your own internal use, provided
|
|
49
|
+
attribution is maintained.
|
|
50
|
+
|
|
51
|
+
### Change Date and Change License
|
|
52
|
+
|
|
53
|
+
On the Change Date specified above, or on such other date as the Licensor may
|
|
54
|
+
specify by written notice, the Licensed Work will be made available under the
|
|
55
|
+
Change License (Apache License, Version 2.0). Until the Change Date, the terms
|
|
56
|
+
of this Business Source License 1.1 apply.
|
|
57
|
+
|
|
58
|
+
### Trademark
|
|
59
|
+
|
|
60
|
+
This license does not grant you any rights to use the "AbsoluteJS" or
|
|
61
|
+
"@absolutejs" name, logo, or any related trademarks. Forks and derivative works
|
|
62
|
+
must not be named or branded in a manner that suggests endorsement by or
|
|
63
|
+
affiliation with AbsoluteJS or the Licensor.
|
|
64
|
+
|
|
65
|
+
### Notices
|
|
66
|
+
|
|
67
|
+
You must not remove or obscure any licensing, copyright, or other notices
|
|
68
|
+
included in the Licensed Work.
|
|
69
|
+
|
|
70
|
+
### No Warranty
|
|
71
|
+
|
|
72
|
+
THE LICENSED WORK IS PROVIDED "AS IS". THE LICENSOR HEREBY DISCLAIMS ALL
|
|
73
|
+
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
|
|
74
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO
|
|
75
|
+
EVENT SHALL THE LICENSOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY,
|
|
76
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR
|
|
77
|
+
IN CONNECTION WITH THE LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE
|
|
78
|
+
LICENSED WORK.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Contact
|
|
83
|
+
|
|
84
|
+
For commercial licensing inquiries or additional permissions, contact:
|
|
85
|
+
|
|
86
|
+
- **Alex Kahn**
|
|
87
|
+
- alexkahndev@gmail.com
|
|
88
|
+
- alexkahndev.github.io
|
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @absolutejs/sync-expo
|
|
2
|
+
|
|
3
|
+
Expo-native persistence and lifecycle integration for
|
|
4
|
+
[`@absolutejs/sync`](https://github.com/absolutejs/sync).
|
|
5
|
+
|
|
6
|
+
- Expo SQLite durable cache, outbox, receipts, cursors, and dead letters.
|
|
7
|
+
- Principal-partitioned storage with the same migrations and policy contract as
|
|
8
|
+
web and Capacitor.
|
|
9
|
+
- AES-256-GCM record protection with a device-only key held by Expo SecureStore.
|
|
10
|
+
- AppState and Expo Network wake-up handling.
|
|
11
|
+
- Bounded Expo Background Task integration.
|
|
12
|
+
|
|
13
|
+
AbsoluteJS provisions this package automatically for Expo applications that use
|
|
14
|
+
`@absolutejs/sync`. Direct package consumers can use the exported adapter
|
|
15
|
+
functions without AbsoluteJS.
|
|
16
|
+
|
|
17
|
+
Background execution is an acceleration only. Foreground startup, resume, and
|
|
18
|
+
connectivity recovery remain authoritative because Android and iOS decide when
|
|
19
|
+
deferrable work is allowed to run.
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { SyncLocalStore } from "@absolutejs/sync/client";
|
|
2
|
+
export type ExpoSyncBridgeHostOptions = {
|
|
3
|
+
store: SyncLocalStore;
|
|
4
|
+
namespace: string;
|
|
5
|
+
/** Maximum time one WebView may hold an atomic transaction. Defaults to 8s. */
|
|
6
|
+
transactionTimeoutMs?: number;
|
|
7
|
+
createId?: () => string;
|
|
8
|
+
};
|
|
9
|
+
export type ExpoSyncSocketBridgeEvent = {
|
|
10
|
+
socketId: string;
|
|
11
|
+
type: "close" | "error" | "message-chunk" | "open";
|
|
12
|
+
code?: number;
|
|
13
|
+
data?: string;
|
|
14
|
+
index?: number;
|
|
15
|
+
messageId?: string;
|
|
16
|
+
reason?: string;
|
|
17
|
+
total?: number;
|
|
18
|
+
};
|
|
19
|
+
export type ExpoSyncSocketBridgeHostOptions = {
|
|
20
|
+
allowedOrigin: string;
|
|
21
|
+
socketTicket: (audience?: string) => Promise<string>;
|
|
22
|
+
emit(event: ExpoSyncSocketBridgeEvent): void;
|
|
23
|
+
webSocketImpl?: typeof WebSocket;
|
|
24
|
+
maxSockets?: number;
|
|
25
|
+
/** Maximum encoded Sync frame size. Defaults to 4 MiB. */
|
|
26
|
+
maxFrameBytes?: number;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Native owner for WebView local-store transactions. It exposes only Sync's
|
|
30
|
+
* typed persistence contract and never accepts a namespace from page code.
|
|
31
|
+
*/
|
|
32
|
+
export declare const createExpoSyncBridgeHost: ({ store, namespace, transactionTimeoutMs, createId, }: ExpoSyncBridgeHostOptions) => {
|
|
33
|
+
close: () => Promise<void>;
|
|
34
|
+
request: (method: string, rawParams: unknown) => Promise<unknown>;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Owns authenticated sockets in native JavaScript. Only ordinary string Sync
|
|
38
|
+
* frames cross the WebView bridge; the single-use ticket is consumed here.
|
|
39
|
+
*/
|
|
40
|
+
export declare const createExpoSyncSocketBridgeHost: ({ allowedOrigin, socketTicket, emit, webSocketImpl, maxSockets, maxFrameBytes, }: ExpoSyncSocketBridgeHostOptions) => {
|
|
41
|
+
close: () => void;
|
|
42
|
+
request: (method: string, rawParams: unknown) => Promise<unknown>;
|
|
43
|
+
};
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SyncLocalStore, SyncLocalStoreSchemaInput } from "@absolutejs/sync/client";
|
|
2
|
+
export type AbsoluteExpoSyncBridgeProvider = {
|
|
3
|
+
on(event: "sync.socket", listener: (payload: Record<string, unknown>) => void): () => void;
|
|
4
|
+
request(method: string, params: Record<string, unknown>): Promise<unknown>;
|
|
5
|
+
};
|
|
6
|
+
export type ExpoSyncBridgeLocalStoreOptions = {
|
|
7
|
+
provider: AbsoluteExpoSyncBridgeProvider;
|
|
8
|
+
storageSchema?: SyncLocalStoreSchemaInput;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* SyncLocalStore proxy whose atomic transaction is owned by native SQLite.
|
|
12
|
+
* The namespace argument never crosses the bridge; the native Auth principal
|
|
13
|
+
* selected when the host was created is authoritative.
|
|
14
|
+
*/
|
|
15
|
+
export declare const createExpoSyncBridgeLocalStore: ({ provider, storageSchema, }: ExpoSyncBridgeLocalStoreOptions) => SyncLocalStore;
|
|
16
|
+
/**
|
|
17
|
+
* Browser-compatible string WebSocket facade backed by the native socket host.
|
|
18
|
+
* Default JSON Sync frames are supported; binary serializers fail explicitly.
|
|
19
|
+
*/
|
|
20
|
+
export declare const createExpoSyncBridgeWebSocket: (provider: AbsoluteExpoSyncBridgeProvider) => typeof WebSocket;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import {
|
|
3
|
+
resolveSyncLocalDataPolicy,
|
|
4
|
+
resolveSyncLocalMutationPolicy
|
|
5
|
+
} from "@absolutejs/sync/client";
|
|
6
|
+
var record = (value, label) => {
|
|
7
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
8
|
+
throw new TypeError(`Expo Sync bridge returned invalid ${label}.`);
|
|
9
|
+
return value;
|
|
10
|
+
};
|
|
11
|
+
var transactionId = (value) => {
|
|
12
|
+
const id = Reflect.get(record(value, "transaction"), "transactionId");
|
|
13
|
+
if (typeof id !== "string" || id.length === 0)
|
|
14
|
+
throw new TypeError("Expo Sync bridge returned an invalid transaction id.");
|
|
15
|
+
return id;
|
|
16
|
+
};
|
|
17
|
+
var nullable = (value) => value === null || value === undefined ? undefined : value;
|
|
18
|
+
var createExpoSyncBridgeLocalStore = ({
|
|
19
|
+
provider,
|
|
20
|
+
storageSchema = { version: 1 }
|
|
21
|
+
}) => {
|
|
22
|
+
const policy = resolveSyncLocalDataPolicy(storageSchema);
|
|
23
|
+
const transaction = async (_namespace, mode, run) => {
|
|
24
|
+
const id = transactionId(await provider.request("sync.store.begin", { mode }));
|
|
25
|
+
const request = (method, params = {}) => provider.request(method, { ...params, transactionId: id });
|
|
26
|
+
const remote = {
|
|
27
|
+
deleteCollection: async (key) => {
|
|
28
|
+
await request("sync.tx.deleteCollection", { key });
|
|
29
|
+
},
|
|
30
|
+
deleteMutation: async (operationId) => {
|
|
31
|
+
await request("sync.tx.deleteMutation", { operationId });
|
|
32
|
+
},
|
|
33
|
+
getCollection: async (key) => nullable(await request("sync.tx.getCollection", { key })),
|
|
34
|
+
getInstallationId: async () => nullable(await request("sync.tx.getInstallationId")),
|
|
35
|
+
getMutation: async (operationId) => nullable(await request("sync.tx.getMutation", { operationId })),
|
|
36
|
+
listCollections: async () => await request("sync.tx.listCollections"),
|
|
37
|
+
listMutations: async () => await request("sync.tx.listMutations"),
|
|
38
|
+
putCollection: async (key, collectionRecord) => {
|
|
39
|
+
await request("sync.tx.putCollection", {
|
|
40
|
+
key,
|
|
41
|
+
record: collectionRecord
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
putMutation: async (mutationRecord) => {
|
|
45
|
+
await request("sync.tx.putMutation", { record: mutationRecord });
|
|
46
|
+
},
|
|
47
|
+
resolveMutationPolicy: (name) => resolveSyncLocalMutationPolicy(policy, name),
|
|
48
|
+
setInstallationId: async (installationId) => {
|
|
49
|
+
await request("sync.tx.setInstallationId", { installationId });
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
try {
|
|
53
|
+
const result = await run(remote);
|
|
54
|
+
await provider.request("sync.store.end", {
|
|
55
|
+
commit: true,
|
|
56
|
+
transactionId: id
|
|
57
|
+
});
|
|
58
|
+
return result;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
await provider.request("sync.store.end", {
|
|
61
|
+
commit: false,
|
|
62
|
+
transactionId: id
|
|
63
|
+
}).catch(() => {
|
|
64
|
+
return;
|
|
65
|
+
});
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
deleteNamespace: async () => {
|
|
71
|
+
await provider.request("sync.store.deleteNamespace", {});
|
|
72
|
+
},
|
|
73
|
+
getSchemaStatus: async () => nullable(await provider.request("sync.store.schema", {})) ?? (() => {
|
|
74
|
+
throw new Error("Expo Sync native schema status is unavailable.");
|
|
75
|
+
})(),
|
|
76
|
+
transaction
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
var SOCKET_CHUNK_BYTES = 24 * 1024;
|
|
80
|
+
var MAX_SOCKET_FRAME_BYTES = 4 * 1024 * 1024;
|
|
81
|
+
var encodeBase64 = (value) => {
|
|
82
|
+
let binary = "";
|
|
83
|
+
for (const byte of value)
|
|
84
|
+
binary += String.fromCharCode(byte);
|
|
85
|
+
return btoa(binary);
|
|
86
|
+
};
|
|
87
|
+
var decodeBase64 = (value) => Uint8Array.from(atob(value), (character) => character.charCodeAt(0));
|
|
88
|
+
var createExpoSyncBridgeWebSocket = (provider) => {
|
|
89
|
+
|
|
90
|
+
class ExpoBridgeWebSocket extends EventTarget {
|
|
91
|
+
static CLOSED = 3;
|
|
92
|
+
static CLOSING = 2;
|
|
93
|
+
static CONNECTING = 0;
|
|
94
|
+
static OPEN = 1;
|
|
95
|
+
CLOSED = 3;
|
|
96
|
+
CLOSING = 2;
|
|
97
|
+
CONNECTING = 0;
|
|
98
|
+
OPEN = 1;
|
|
99
|
+
binaryType = "blob";
|
|
100
|
+
bufferedAmount = 0;
|
|
101
|
+
extensions = "";
|
|
102
|
+
onclose = null;
|
|
103
|
+
onerror = null;
|
|
104
|
+
onmessage = null;
|
|
105
|
+
onopen = null;
|
|
106
|
+
protocol = "";
|
|
107
|
+
readyState = ExpoBridgeWebSocket.CONNECTING;
|
|
108
|
+
url;
|
|
109
|
+
#socketId = `websocket_${crypto.randomUUID()}`;
|
|
110
|
+
#messages = new Map;
|
|
111
|
+
#remove;
|
|
112
|
+
#sendSequence = 0;
|
|
113
|
+
#sendTail = Promise.resolve();
|
|
114
|
+
constructor(url, protocols) {
|
|
115
|
+
super();
|
|
116
|
+
if (protocols !== undefined && (typeof protocols !== "string" || protocols.length > 0) && (!Array.isArray(protocols) || protocols.length > 0))
|
|
117
|
+
throw new TypeError("Expo Sync bridge does not support WebSocket subprotocols.");
|
|
118
|
+
this.url = String(url);
|
|
119
|
+
this.#remove = provider.on("sync.socket", (payload) => {
|
|
120
|
+
if (payload.socketId !== this.#socketId)
|
|
121
|
+
return;
|
|
122
|
+
this.#receive(payload);
|
|
123
|
+
});
|
|
124
|
+
provider.request("sync.socket.open", {
|
|
125
|
+
socketId: this.#socketId,
|
|
126
|
+
url: this.url
|
|
127
|
+
}).catch(() => this.#fail());
|
|
128
|
+
}
|
|
129
|
+
close(code, reason) {
|
|
130
|
+
if (this.readyState === ExpoBridgeWebSocket.CLOSED || this.readyState === ExpoBridgeWebSocket.CLOSING)
|
|
131
|
+
return;
|
|
132
|
+
this.readyState = ExpoBridgeWebSocket.CLOSING;
|
|
133
|
+
provider.request("sync.socket.close", {
|
|
134
|
+
...code === undefined ? {} : { code },
|
|
135
|
+
...reason === undefined ? {} : { reason },
|
|
136
|
+
socketId: this.#socketId
|
|
137
|
+
}).catch(() => this.#closed(code ?? 1006, reason ?? ""));
|
|
138
|
+
}
|
|
139
|
+
ping() {
|
|
140
|
+
throw new TypeError("Expo Sync bridge WebSocket ping is not supported.");
|
|
141
|
+
}
|
|
142
|
+
send(data) {
|
|
143
|
+
if (this.readyState !== ExpoBridgeWebSocket.OPEN)
|
|
144
|
+
throw new DOMException("WebSocket is not open.", "InvalidStateError");
|
|
145
|
+
if (typeof data !== "string")
|
|
146
|
+
throw new TypeError("Expo Sync bridge supports the default string serializer only.");
|
|
147
|
+
const bytes = new TextEncoder().encode(data);
|
|
148
|
+
if (bytes.byteLength > MAX_SOCKET_FRAME_BYTES)
|
|
149
|
+
throw new DOMException("Sync frame exceeds 4 MiB.", "QuotaExceededError");
|
|
150
|
+
const total = Math.max(1, Math.ceil(bytes.byteLength / SOCKET_CHUNK_BYTES));
|
|
151
|
+
const messageId = `web_${(this.#sendSequence += 1).toString(36)}`;
|
|
152
|
+
this.#sendTail = this.#sendTail.then(async () => {
|
|
153
|
+
for (let index = 0;index < total; index += 1)
|
|
154
|
+
await provider.request("sync.socket.sendChunk", {
|
|
155
|
+
data: encodeBase64(bytes.slice(index * SOCKET_CHUNK_BYTES, Math.min(bytes.byteLength, (index + 1) * SOCKET_CHUNK_BYTES))),
|
|
156
|
+
index,
|
|
157
|
+
messageId,
|
|
158
|
+
socketId: this.#socketId,
|
|
159
|
+
total
|
|
160
|
+
});
|
|
161
|
+
}).catch(() => this.#fail());
|
|
162
|
+
}
|
|
163
|
+
#dispatch(event, listener) {
|
|
164
|
+
this.dispatchEvent(event);
|
|
165
|
+
listener?.call(this, event);
|
|
166
|
+
}
|
|
167
|
+
#closed(code, reason) {
|
|
168
|
+
if (this.readyState === ExpoBridgeWebSocket.CLOSED)
|
|
169
|
+
return;
|
|
170
|
+
this.readyState = ExpoBridgeWebSocket.CLOSED;
|
|
171
|
+
this.#remove();
|
|
172
|
+
for (const message of this.#messages.values())
|
|
173
|
+
clearTimeout(message.timer);
|
|
174
|
+
this.#messages.clear();
|
|
175
|
+
this.#dispatch(new CloseEvent("close", { code, reason }), this.onclose);
|
|
176
|
+
}
|
|
177
|
+
#fail() {
|
|
178
|
+
if (this.readyState === ExpoBridgeWebSocket.CLOSED)
|
|
179
|
+
return;
|
|
180
|
+
this.#dispatch(new Event("error"), this.onerror);
|
|
181
|
+
this.#closed(1006, "");
|
|
182
|
+
}
|
|
183
|
+
#receive(payload) {
|
|
184
|
+
if (payload.type === "open") {
|
|
185
|
+
if (this.readyState !== ExpoBridgeWebSocket.CONNECTING)
|
|
186
|
+
return;
|
|
187
|
+
this.readyState = ExpoBridgeWebSocket.OPEN;
|
|
188
|
+
this.#dispatch(new Event("open"), this.onopen);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (payload.type === "error") {
|
|
192
|
+
this.#dispatch(new Event("error"), this.onerror);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (payload.type === "close") {
|
|
196
|
+
this.#closed(typeof payload.code === "number" ? payload.code : 1006, typeof payload.reason === "string" ? payload.reason : "");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (payload.type !== "message-chunk")
|
|
200
|
+
return;
|
|
201
|
+
const messageId = payload.messageId;
|
|
202
|
+
const index = payload.index;
|
|
203
|
+
const total = payload.total;
|
|
204
|
+
const data = payload.data;
|
|
205
|
+
if (typeof messageId !== "string" || typeof index !== "number" || !Number.isSafeInteger(index) || typeof total !== "number" || !Number.isSafeInteger(total) || typeof data !== "string" || index < 0 || total < 1 || index >= total || total > Math.ceil(MAX_SOCKET_FRAME_BYTES / SOCKET_CHUNK_BYTES)) {
|
|
206
|
+
this.#fail();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
let message = this.#messages.get(messageId);
|
|
210
|
+
if (!message) {
|
|
211
|
+
message = {
|
|
212
|
+
chunks: Array.from({ length: total }),
|
|
213
|
+
timer: setTimeout(() => this.#fail(), 1e4)
|
|
214
|
+
};
|
|
215
|
+
this.#messages.set(messageId, message);
|
|
216
|
+
}
|
|
217
|
+
if (message.chunks.length !== total || message.chunks[index]) {
|
|
218
|
+
this.#fail();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
message.chunks[index] = decodeBase64(data);
|
|
223
|
+
} catch {
|
|
224
|
+
this.#fail();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (!message.chunks.every((chunk) => chunk !== undefined))
|
|
228
|
+
return;
|
|
229
|
+
clearTimeout(message.timer);
|
|
230
|
+
this.#messages.delete(messageId);
|
|
231
|
+
const size = message.chunks.reduce((sum, chunk) => sum + (chunk?.byteLength ?? 0), 0);
|
|
232
|
+
if (size > MAX_SOCKET_FRAME_BYTES) {
|
|
233
|
+
this.#fail();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const bytes = new Uint8Array(size);
|
|
237
|
+
let offset = 0;
|
|
238
|
+
for (const chunk of message.chunks) {
|
|
239
|
+
bytes.set(chunk, offset);
|
|
240
|
+
offset += chunk.byteLength;
|
|
241
|
+
}
|
|
242
|
+
this.#dispatch(new MessageEvent("message", {
|
|
243
|
+
data: new TextDecoder().decode(bytes)
|
|
244
|
+
}), this.onmessage);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return ExpoBridgeWebSocket;
|
|
248
|
+
};
|
|
249
|
+
export {
|
|
250
|
+
createExpoSyncBridgeLocalStore,
|
|
251
|
+
createExpoSyncBridgeWebSocket
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
//# debugId=BF289F120D89D52A64756E2164756E21
|
|
255
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/client.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type {\n LocalCollectionRecord,\n LocalMutationRecord,\n SyncLocalStore,\n SyncLocalStoreMode,\n SyncLocalStoreSchemaInput,\n SyncLocalStoreSchemaStatus,\n SyncLocalTransaction,\n} from \"@absolutejs/sync/client\";\nimport {\n resolveSyncLocalDataPolicy,\n resolveSyncLocalMutationPolicy,\n} from \"@absolutejs/sync/client\";\n\nexport type AbsoluteExpoSyncBridgeProvider = {\n on(\n event: \"sync.socket\",\n listener: (payload: Record<string, unknown>) => void,\n ): () => void;\n request(method: string, params: Record<string, unknown>): Promise<unknown>;\n};\n\nexport type ExpoSyncBridgeLocalStoreOptions = {\n provider: AbsoluteExpoSyncBridgeProvider;\n storageSchema?: SyncLocalStoreSchemaInput;\n};\n\nconst record = (value: unknown, label: string) => {\n if (typeof value !== \"object\" || value === null || Array.isArray(value))\n throw new TypeError(`Expo Sync bridge returned invalid ${label}.`);\n\n return value as Record<string, unknown>;\n};\n\nconst transactionId = (value: unknown) => {\n const id = Reflect.get(record(value, \"transaction\"), \"transactionId\");\n if (typeof id !== \"string\" || id.length === 0)\n throw new TypeError(\"Expo Sync bridge returned an invalid transaction id.\");\n\n return id;\n};\n\nconst nullable = <T>(value: unknown): T | undefined =>\n value === null || value === undefined ? undefined : (value as T);\n\n/**\n * SyncLocalStore proxy whose atomic transaction is owned by native SQLite.\n * The namespace argument never crosses the bridge; the native Auth principal\n * selected when the host was created is authoritative.\n */\nexport const createExpoSyncBridgeLocalStore = ({\n provider,\n storageSchema = { version: 1 },\n}: ExpoSyncBridgeLocalStoreOptions): SyncLocalStore => {\n const policy = resolveSyncLocalDataPolicy(storageSchema);\n const transaction = async <T>(\n _namespace: string,\n mode: SyncLocalStoreMode,\n run: (transaction: SyncLocalTransaction) => Promise<T>,\n ) => {\n const id = transactionId(\n await provider.request(\"sync.store.begin\", { mode }),\n );\n const request = (method: string, params: Record<string, unknown> = {}) =>\n provider.request(method, { ...params, transactionId: id });\n const remote: SyncLocalTransaction = {\n deleteCollection: async (key) => {\n await request(\"sync.tx.deleteCollection\", { key });\n },\n deleteMutation: async (operationId) => {\n await request(\"sync.tx.deleteMutation\", { operationId });\n },\n getCollection: async <R>(key: string) =>\n nullable<LocalCollectionRecord<R>>(\n await request(\"sync.tx.getCollection\", { key }),\n ),\n getInstallationId: async () =>\n nullable<string>(await request(\"sync.tx.getInstallationId\")),\n getMutation: async (operationId) =>\n nullable<LocalMutationRecord>(\n await request(\"sync.tx.getMutation\", { operationId }),\n ),\n listCollections: async () =>\n (await request(\"sync.tx.listCollections\")) as Array<{\n key: string;\n record: LocalCollectionRecord;\n }>,\n listMutations: async () =>\n (await request(\"sync.tx.listMutations\")) as LocalMutationRecord[],\n putCollection: async (key, collectionRecord) => {\n await request(\"sync.tx.putCollection\", {\n key,\n record: collectionRecord,\n });\n },\n putMutation: async (mutationRecord) => {\n await request(\"sync.tx.putMutation\", { record: mutationRecord });\n },\n resolveMutationPolicy: (name) =>\n resolveSyncLocalMutationPolicy(policy, name),\n setInstallationId: async (installationId) => {\n await request(\"sync.tx.setInstallationId\", { installationId });\n },\n };\n try {\n const result = await run(remote);\n await provider.request(\"sync.store.end\", {\n commit: true,\n transactionId: id,\n });\n\n return result;\n } catch (error) {\n await provider\n .request(\"sync.store.end\", {\n commit: false,\n transactionId: id,\n })\n .catch(() => undefined);\n throw error;\n }\n };\n\n return {\n deleteNamespace: async () => {\n await provider.request(\"sync.store.deleteNamespace\", {});\n },\n getSchemaStatus: async () =>\n nullable<SyncLocalStoreSchemaStatus>(\n await provider.request(\"sync.store.schema\", {}),\n ) ??\n (() => {\n throw new Error(\"Expo Sync native schema status is unavailable.\");\n })(),\n transaction,\n };\n};\n\nconst SOCKET_CHUNK_BYTES = 24 * 1024;\nconst MAX_SOCKET_FRAME_BYTES = 4 * 1024 * 1024;\nconst encodeBase64 = (value: Uint8Array) => {\n let binary = \"\";\n for (const byte of value) binary += String.fromCharCode(byte);\n\n return btoa(binary);\n};\nconst decodeBase64 = (value: string) =>\n Uint8Array.from(atob(value), (character) => character.charCodeAt(0));\n\ntype PendingMessage = {\n chunks: Array<Uint8Array | undefined>;\n timer: ReturnType<typeof setTimeout>;\n};\n\n/**\n * Browser-compatible string WebSocket facade backed by the native socket host.\n * Default JSON Sync frames are supported; binary serializers fail explicitly.\n */\nexport const createExpoSyncBridgeWebSocket = (\n provider: AbsoluteExpoSyncBridgeProvider,\n): typeof WebSocket => {\n class ExpoBridgeWebSocket extends EventTarget implements WebSocket {\n static readonly CLOSED = 3;\n static readonly CLOSING = 2;\n static readonly CONNECTING = 0;\n static readonly OPEN = 1;\n readonly CLOSED = 3;\n readonly CLOSING = 2;\n readonly CONNECTING = 0;\n readonly OPEN = 1;\n binaryType: BinaryType = \"blob\";\n readonly bufferedAmount = 0;\n readonly extensions = \"\";\n onclose: ((this: WebSocket, event: CloseEvent) => unknown) | null = null;\n onerror: ((this: WebSocket, event: Event) => unknown) | null = null;\n onmessage:\n | ((this: WebSocket, event: MessageEvent<unknown>) => unknown)\n | null = null;\n onopen: ((this: WebSocket, event: Event) => unknown) | null = null;\n readonly protocol = \"\";\n readyState = ExpoBridgeWebSocket.CONNECTING;\n readonly url: string;\n readonly #socketId = `websocket_${crypto.randomUUID()}`;\n readonly #messages = new Map<string, PendingMessage>();\n readonly #remove: () => void;\n #sendSequence = 0;\n #sendTail = Promise.resolve();\n\n constructor(url: string | URL, protocols?: string | string[]) {\n super();\n if (\n protocols !== undefined &&\n (typeof protocols !== \"string\" || protocols.length > 0) &&\n (!Array.isArray(protocols) || protocols.length > 0)\n )\n throw new TypeError(\n \"Expo Sync bridge does not support WebSocket subprotocols.\",\n );\n this.url = String(url);\n this.#remove = provider.on(\"sync.socket\", (payload) => {\n if (payload.socketId !== this.#socketId) return;\n this.#receive(payload);\n });\n void provider\n .request(\"sync.socket.open\", {\n socketId: this.#socketId,\n url: this.url,\n })\n .catch(() => this.#fail());\n }\n\n close(code?: number, reason?: string): void {\n if (\n this.readyState === ExpoBridgeWebSocket.CLOSED ||\n this.readyState === ExpoBridgeWebSocket.CLOSING\n )\n return;\n this.readyState = ExpoBridgeWebSocket.CLOSING;\n void provider\n .request(\"sync.socket.close\", {\n ...(code === undefined ? {} : { code }),\n ...(reason === undefined ? {} : { reason }),\n socketId: this.#socketId,\n })\n .catch(() => this.#closed(code ?? 1006, reason ?? \"\"));\n }\n\n ping(): void {\n throw new TypeError(\"Expo Sync bridge WebSocket ping is not supported.\");\n }\n\n send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void {\n if (this.readyState !== ExpoBridgeWebSocket.OPEN)\n throw new DOMException(\"WebSocket is not open.\", \"InvalidStateError\");\n if (typeof data !== \"string\")\n throw new TypeError(\n \"Expo Sync bridge supports the default string serializer only.\",\n );\n const bytes = new TextEncoder().encode(data);\n if (bytes.byteLength > MAX_SOCKET_FRAME_BYTES)\n throw new DOMException(\n \"Sync frame exceeds 4 MiB.\",\n \"QuotaExceededError\",\n );\n const total = Math.max(\n 1,\n Math.ceil(bytes.byteLength / SOCKET_CHUNK_BYTES),\n );\n const messageId = `web_${(this.#sendSequence += 1).toString(36)}`;\n this.#sendTail = this.#sendTail\n .then(async () => {\n for (let index = 0; index < total; index += 1)\n await provider.request(\"sync.socket.sendChunk\", {\n data: encodeBase64(\n bytes.slice(\n index * SOCKET_CHUNK_BYTES,\n Math.min(bytes.byteLength, (index + 1) * SOCKET_CHUNK_BYTES),\n ),\n ),\n index,\n messageId,\n socketId: this.#socketId,\n total,\n });\n })\n .catch(() => this.#fail());\n }\n\n #dispatch<T extends Event>(\n event: T,\n listener: ((this: WebSocket, event: T) => unknown) | null,\n ) {\n this.dispatchEvent(event);\n listener?.call(this, event);\n }\n\n #closed(code: number, reason: string) {\n if (this.readyState === ExpoBridgeWebSocket.CLOSED) return;\n this.readyState = ExpoBridgeWebSocket.CLOSED;\n this.#remove();\n for (const message of this.#messages.values())\n clearTimeout(message.timer);\n this.#messages.clear();\n this.#dispatch(new CloseEvent(\"close\", { code, reason }), this.onclose);\n }\n\n #fail() {\n if (this.readyState === ExpoBridgeWebSocket.CLOSED) return;\n this.#dispatch(new Event(\"error\"), this.onerror);\n this.#closed(1006, \"\");\n }\n\n #receive(payload: Record<string, unknown>) {\n if (payload.type === \"open\") {\n if (this.readyState !== ExpoBridgeWebSocket.CONNECTING) return;\n this.readyState = ExpoBridgeWebSocket.OPEN;\n this.#dispatch(new Event(\"open\"), this.onopen);\n\n return;\n }\n if (payload.type === \"error\") {\n this.#dispatch(new Event(\"error\"), this.onerror);\n\n return;\n }\n if (payload.type === \"close\") {\n this.#closed(\n typeof payload.code === \"number\" ? payload.code : 1006,\n typeof payload.reason === \"string\" ? payload.reason : \"\",\n );\n\n return;\n }\n if (payload.type !== \"message-chunk\") return;\n const messageId = payload.messageId;\n const index = payload.index;\n const total = payload.total;\n const data = payload.data;\n if (\n typeof messageId !== \"string\" ||\n typeof index !== \"number\" ||\n !Number.isSafeInteger(index) ||\n typeof total !== \"number\" ||\n !Number.isSafeInteger(total) ||\n typeof data !== \"string\" ||\n index < 0 ||\n total < 1 ||\n index >= total ||\n total > Math.ceil(MAX_SOCKET_FRAME_BYTES / SOCKET_CHUNK_BYTES)\n ) {\n this.#fail();\n\n return;\n }\n let message = this.#messages.get(messageId);\n if (!message) {\n message = {\n chunks: Array.from({ length: total }),\n timer: setTimeout(() => this.#fail(), 10_000),\n };\n this.#messages.set(messageId, message);\n }\n if (message.chunks.length !== total || message.chunks[index]) {\n this.#fail();\n\n return;\n }\n try {\n message.chunks[index] = decodeBase64(data);\n } catch {\n this.#fail();\n\n return;\n }\n if (!message.chunks.every((chunk) => chunk !== undefined)) return;\n clearTimeout(message.timer);\n this.#messages.delete(messageId);\n const size = message.chunks.reduce(\n (sum, chunk) => sum + (chunk?.byteLength ?? 0),\n 0,\n );\n if (size > MAX_SOCKET_FRAME_BYTES) {\n this.#fail();\n\n return;\n }\n const bytes = new Uint8Array(size);\n let offset = 0;\n for (const chunk of message.chunks) {\n bytes.set(chunk!, offset);\n offset += chunk!.byteLength;\n }\n this.#dispatch(\n new MessageEvent(\"message\", {\n data: new TextDecoder().decode(bytes),\n }),\n this.onmessage,\n );\n }\n }\n\n return ExpoBridgeWebSocket as unknown as typeof WebSocket;\n};\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "BF289F120D89D52A64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { SyncClient } from "@absolutejs/sync/client";
|
|
2
|
+
import type { SyncLocalProtectionProvider } from "@absolutejs/sync/client";
|
|
3
|
+
export * from "./store";
|
|
4
|
+
export * from "./bridge";
|
|
5
|
+
export type ExpoSyncSecureStore = {
|
|
6
|
+
AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: number;
|
|
7
|
+
deleteItemAsync(key: string, options?: Record<string, unknown>): Promise<void>;
|
|
8
|
+
getItemAsync(key: string, options?: Record<string, unknown>): Promise<string | null>;
|
|
9
|
+
isAvailableAsync(): Promise<boolean>;
|
|
10
|
+
setItemAsync(key: string, value: string, options?: Record<string, unknown>): Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
export type ExpoSyncProtectionOptions = {
|
|
13
|
+
secureStore?: ExpoSyncSecureStore;
|
|
14
|
+
storagePrefix?: string;
|
|
15
|
+
};
|
|
16
|
+
/** AES-256-GCM records whose data key is retained only by Expo SecureStore. */
|
|
17
|
+
export declare const createExpoSyncProtection: (options?: ExpoSyncProtectionOptions) => SyncLocalProtectionProvider;
|
|
18
|
+
type Subscription = {
|
|
19
|
+
remove(): void;
|
|
20
|
+
};
|
|
21
|
+
export type ExpoSyncLifecycleDependencies = {
|
|
22
|
+
appState: {
|
|
23
|
+
currentState?: string | null;
|
|
24
|
+
addEventListener(type: "change", listener: (state: string) => void): Subscription;
|
|
25
|
+
};
|
|
26
|
+
network: {
|
|
27
|
+
addNetworkStateListener(listener: (state: {
|
|
28
|
+
isConnected?: boolean;
|
|
29
|
+
isInternetReachable?: boolean;
|
|
30
|
+
}) => void): Subscription;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export type ExpoSyncLifecycleOptions = {
|
|
34
|
+
client: Pick<SyncClient, "reconnect"> & Partial<Pick<SyncClient, "flush">>;
|
|
35
|
+
/** Finite outbox budget after a wake-up. Defaults to 10 seconds. */
|
|
36
|
+
flushTimeoutMs?: number;
|
|
37
|
+
onError?: (error: unknown) => void;
|
|
38
|
+
dependencies?: ExpoSyncLifecycleDependencies;
|
|
39
|
+
};
|
|
40
|
+
/** Reconnect and perform a bounded flush after foreground or connectivity. */
|
|
41
|
+
export declare const installExpoSyncLifecycle: ({ client, flushTimeoutMs, onError, dependencies, }: ExpoSyncLifecycleOptions) => () => void;
|
|
42
|
+
export type ExpoSyncBackgroundDependencies = {
|
|
43
|
+
backgroundTask: {
|
|
44
|
+
Failed: number;
|
|
45
|
+
Success: number;
|
|
46
|
+
getStatusAsync(): Promise<number | null>;
|
|
47
|
+
registerTaskAsync(taskName: string, options?: {
|
|
48
|
+
minimumInterval?: number;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
unregisterTaskAsync(taskName: string): Promise<void>;
|
|
51
|
+
};
|
|
52
|
+
taskManager: {
|
|
53
|
+
defineTask(taskName: string, run: () => Promise<number>): void;
|
|
54
|
+
isAvailableAsync(): Promise<boolean>;
|
|
55
|
+
isTaskDefined(taskName: string): boolean;
|
|
56
|
+
isTaskRegisteredAsync(taskName: string): Promise<boolean>;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
/** Define the task at module scope before registering it during app startup. */
|
|
60
|
+
export declare const defineExpoSyncBackgroundTask: (taskName: string, run: () => Promise<unknown>, dependencies?: ExpoSyncBackgroundDependencies) => void;
|
|
61
|
+
export type ExpoSyncBackgroundRegistration = {
|
|
62
|
+
available: boolean;
|
|
63
|
+
registered: boolean;
|
|
64
|
+
status: number | null;
|
|
65
|
+
};
|
|
66
|
+
export declare const registerExpoSyncBackgroundTask: (taskName: string, options?: {
|
|
67
|
+
minimumInterval?: number;
|
|
68
|
+
}, dependencies?: ExpoSyncBackgroundDependencies) => Promise<ExpoSyncBackgroundRegistration>;
|
|
69
|
+
export declare const unregisterExpoSyncBackgroundTask: (taskName: string, dependencies?: ExpoSyncBackgroundDependencies) => Promise<void>;
|