@meshline/expo 0.1.0-alpha.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Meshline contributors
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,94 @@
1
+ # Meshline Expo native adapters
2
+
3
+ Native SQLite, cryptographic randomness, HTTP, and WebSocket adapters for an
4
+ Expo Modules application. This ESM package includes TypeScript declarations.
5
+ Use `@meshline/storage-browser` and browser transport for an Expo web build.
6
+
7
+ ## Install
8
+
9
+ Install the matching core and adapter together:
10
+
11
+ ```sh
12
+ npm install @meshline/sdk@0.1.0-alpha.1 @meshline/expo@0.1.0-alpha.1
13
+ ```
14
+
15
+ Use Expo **57.0.25**, Expo Modules Core **57.0.19**, and React Native
16
+ **>=0.86.3 <0.87.0**. The native dependencies are `expo-crypto ^57.0.3`,
17
+ `expo-file-system ^57.0.7`, and `expo-sqlite ^57.0.3`.
18
+ In an Expo 57 app, install matching versions:
19
+
20
+ ```sh
21
+ npx expo install expo@57.0.25 expo-modules-core@57.0.19 react-native@0.86.3 expo-crypto@57.0.3 expo-file-system@57.0.7 expo-sqlite@57.0.3
22
+ ```
23
+
24
+ Rebuild the native app after installing this package. **Expo Go does not contain
25
+ the included `MeshlineRelaySocket` module.** A missing module produces an error;
26
+ the adapter does not fall back to React Native's default WebSocket.
27
+
28
+ ## Android compatibility
29
+
30
+ Apply the shipped compatibility fixes after installing dependencies and before
31
+ building Android:
32
+
33
+ ```sh
34
+ npx meshline-expo-android-compat . --apply
35
+ npx meshline-expo-android-compat . --check
36
+ npx expo run:android
37
+ ```
38
+
39
+ The fixes address shared-object registry access and HTTP startup in the pinned
40
+ Expo dependencies. Installation does not apply them automatically.
41
+ The command checks exact versions and original/already-fixed source hashes,
42
+ is idempotent, and refuses unknown or locally modified sources.
43
+
44
+ Repeat after a clean dependency installation and rebuild the native app.
45
+ Reloading JavaScript cannot apply native fixes. Do not bypass version or source
46
+ mismatch errors. The patches affect Android only.
47
+
48
+ ## Configure a session
49
+
50
+ Import `ExpoSqliteStore`, `expoRandom`, `expoRelayFetch`, and
51
+ `createExpoSocketFactory` from `@meshline/expo`.
52
+
53
+ - Construct `ExpoSqliteStore({ databaseName })`, migrate explicitly, then pass it
54
+ to the client before initialization. The name is a basename; optional
55
+ `directory` accepts an absolute app-private path or local file URI.
56
+ - Pass `fetch: expoRelayFetch`, `socketFactory: createExpoSocketFactory()`, and
57
+ `random: expoRandom` to `RelayClientPool`.
58
+ - Pass `random: expoRandom` to `MeshlineClient` along with the store, pool, and
59
+ application integrations.
60
+ - Supply a persistent, purpose-bound `SecretProtector`; manage protection keys
61
+ through your application's Keychain/Keystore integration.
62
+
63
+ The [Expo session example](https://github.com/meshline-network/sdk/blob/main/typescript/examples/expo.ts)
64
+ shows construction and cleanup. If implementing an account signer using
65
+ `signAccount`, pass `expoRandom` as its third argument too. A hardware-backed
66
+ signer owns its own randomness.
67
+
68
+ ## Runtime behavior
69
+
70
+ SQLite writers serialize transactions and readers retain independent fixed WAL
71
+ snapshots. Dispose query readers promptly. Cancellation before commit rolls back
72
+ the transaction; a canceled read does not consume a page. Retain the original
73
+ database and protector across restart. Secret protection does not encrypt the
74
+ whole database or stored message text.
75
+
76
+ `expoRelayFetch` uses `expo/fetch`, omits cookies, rejects redirects, and consumes
77
+ response streams so cancellation and partial failures settle reads.
78
+ It does not use React Native's legacy global fetch.
79
+
80
+ The native WebSocket module uses dedicated Android OkHttp and iOS ephemeral
81
+ URLSession clients without cookie storage, credential storage, or caching.
82
+ It rejects redirects and retains platform TLS trust checks.
83
+
84
+ Use Bundler resolution, the `react-native` condition, and DOM request/stream types
85
+ for TypeScript. The
86
+ [native example configuration](https://github.com/meshline-network/sdk/blob/main/typescript/examples/tsconfig.expo.json)
87
+ uses `skipLibCheck` for overlapping Expo/React Native dependency declarations.
88
+
89
+ Android execution has been exercised on Android 11/API 30 with Hermes.
90
+ iOS native builds and runtime behavior remain unverified. Background execution
91
+ depends on OS/OEM policy; persistent recovery does not guarantee background delivery.
92
+
93
+ [Application guide](https://github.com/meshline-network/sdk/blob/main/typescript/docs/README.md)
94
+ · [Platform requirements and limits](https://github.com/meshline-network/sdk/blob/main/typescript/docs/platforms.md)
@@ -0,0 +1,20 @@
1
+ plugins {
2
+ id 'com.android.library'
3
+ id 'expo-module-gradle-plugin'
4
+ }
5
+
6
+ group = 'org.meshline.expo'
7
+ version = '0.1.0-alpha.1'
8
+
9
+ android {
10
+ namespace 'org.meshline.expo'
11
+ defaultConfig {
12
+ versionCode 1
13
+ versionName project.version.toString()
14
+ }
15
+ }
16
+
17
+ dependencies {
18
+ // Align with the React Native 0.86.3 dependency, without using its shared client.
19
+ implementation 'com.squareup.okhttp3:okhttp:4.9.2'
20
+ }
@@ -0,0 +1,3 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.INTERNET" />
3
+ </manifest>
@@ -0,0 +1,82 @@
1
+ package org.meshline.expo
2
+
3
+ import expo.modules.kotlin.modules.Module
4
+ import expo.modules.kotlin.modules.ModuleDefinition
5
+ import okhttp3.Authenticator
6
+ import okhttp3.CookieJar
7
+ import okhttp3.OkHttpClient
8
+ import okhttp3.Request
9
+ import okhttp3.Response
10
+ import okhttp3.WebSocket
11
+ import okhttp3.WebSocketListener
12
+ import okio.ByteString
13
+ import java.net.URI
14
+ import java.util.concurrent.ConcurrentHashMap
15
+ import java.util.concurrent.TimeUnit
16
+
17
+ class MeshlineRelaySocketModule : Module() {
18
+ private class Entry { @Volatile var socket: WebSocket? = null }
19
+ private val sockets = ConcurrentHashMap<Double, Entry>()
20
+ private val client by lazy {
21
+ // A dedicated client: no React Native interceptors, cookie jar or credentials.
22
+ OkHttpClient.Builder()
23
+ .cookieJar(CookieJar.NO_COOKIES).cache(null)
24
+ .authenticator(Authenticator.NONE).proxyAuthenticator(Authenticator.NONE)
25
+ .followRedirects(false).followSslRedirects(false).retryOnConnectionFailure(false)
26
+ .connectTimeout(60, TimeUnit.SECONDS).readTimeout(0, TimeUnit.MILLISECONDS)
27
+ .build()
28
+ }
29
+
30
+ override fun definition() = ModuleDefinition {
31
+ Name("MeshlineRelaySocket")
32
+ Events("socket")
33
+ Function("connect") { id: Double, endpoint: String -> connect(id, endpoint) }
34
+ Function("send") { id: Double, text: String ->
35
+ val socket = sockets[id]?.socket ?: throw IllegalStateException("WebSocket is not open.")
36
+ check(socket.send(text)) { "WebSocket rejected the outgoing message." }
37
+ }
38
+ Function("close") { id: Double, code: Int, reason: String ->
39
+ sockets[id]?.socket?.close(code, reason)
40
+ Unit
41
+ }
42
+ Function("cancel") { id: Double ->
43
+ sockets.remove(id)?.socket?.cancel()
44
+ Unit
45
+ }
46
+ Function("bufferedAmount") { id: Double -> sockets[id]?.socket?.queueSize()?.toDouble() ?: 0.0 }
47
+ OnDestroy {
48
+ val closing = sockets.values.toList(); sockets.clear()
49
+ closing.forEach { it.socket?.cancel() }
50
+ client.dispatcher.executorService.shutdown()
51
+ client.connectionPool.evictAll()
52
+ }
53
+ }
54
+
55
+ private fun connect(id: Double, endpoint: String) {
56
+ val uri = URI(endpoint)
57
+ require(uri.scheme == "wss" && uri.host != null && uri.rawUserInfo == null && uri.rawQuery == null && uri.rawFragment == null) { "Expected a credential-free WSS endpoint." }
58
+ val entry = Entry()
59
+ check(sockets.putIfAbsent(id, entry) == null) { "Duplicate native WebSocket identifier." }
60
+ fun emit(type: String, fields: Map<String, Any> = emptyMap()) {
61
+ if (sockets[id] === entry) sendEvent("socket", mapOf("id" to id, "type" to type) + fields)
62
+ }
63
+ try {
64
+ val socket = client.newWebSocket(Request.Builder().url(endpoint).build(), object : WebSocketListener() {
65
+ override fun onOpen(webSocket: WebSocket, response: Response) { emit("open") }
66
+ override fun onMessage(webSocket: WebSocket, text: String) { emit("text", mapOf("text" to text)) }
67
+ override fun onMessage(webSocket: WebSocket, bytes: ByteString) { emit("binary") }
68
+ override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { webSocket.close(code, reason) }
69
+ override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
70
+ emit("close", mapOf("code" to code, "reason" to reason)); sockets.remove(id, entry)
71
+ }
72
+ override fun onFailure(webSocket: WebSocket, error: Throwable, response: Response?) {
73
+ emit("error", mapOf("message" to (error.message ?: "Native WebSocket failed.")))
74
+ emit("close", mapOf("code" to 1006, "reason" to "Native WebSocket failed.")); sockets.remove(id, entry)
75
+ response?.close()
76
+ }
77
+ })
78
+ entry.socket = socket
79
+ if (sockets[id] !== entry) socket.cancel()
80
+ } catch (error: Throwable) { sockets.remove(id, entry); throw error }
81
+ }
82
+ }
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ import { readFile, realpath, writeFile } from 'node:fs/promises';
3
+ import { createHash } from 'node:crypto';
4
+ import { isAbsolute, join, relative, resolve, sep } from 'node:path';
5
+
6
+ const [appDirectory, mode = '--check'] = process.argv.slice(2);
7
+ if (!appDirectory || !['--check', '--apply'].includes(mode) || process.argv.length > 4) throw new Error('Usage: meshline-expo-android-compat <app-directory> [--check|--apply]');
8
+ const app = await realpath(resolve(appDirectory));
9
+ const digest = value => createHash('sha256').update(value).digest('hex');
10
+ const fixes = [{
11
+ name: 'expo-modules-core', version: '57.0.19', path: 'android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObjectRegistry.kt',
12
+ originalSha256: 'c41c2a8aab60bef0476b2f805aa863507b96144701d782a228e76c1afbf1b4b3',
13
+ fixedSha256: 'b9417c719e9cdd92c1f08ba8d841e4b9cbfd933dd031b7b2e3a8ff44b9861df4',
14
+ apply: source => source.replace(
15
+ ' val native = pairs[id.ensureWasNotRelease()]?.first\n return native ?: throw InvalidSharedObjectIdException()',
16
+ ' return synchronized(this) {\n val native = pairs[id.ensureWasNotRelease()]?.first\n native ?: throw InvalidSharedObjectIdException()\n }',
17
+ ).replace(
18
+ ' return pairs[id]?.first',
19
+ ' return synchronized(this) {\n pairs[id]?.first\n }',
20
+ ),
21
+ }, {
22
+ name: 'expo', version: '57.0.25', path: 'android/src/main/java/expo/modules/fetch/NativeRequest.kt',
23
+ originalSha256: '4e35ef082ef083490e7088d732af787c348678bc653deab3ef646ef9c1707938',
24
+ fixedSha256: '6fc9d773e12eb1788cde5392c666e495131240b2240657c85664de02aa3fc458',
25
+ apply: source => source.replace(' this.task?.enqueue(this.response)\n response.onStarted()',
26
+ ' response.onStarted()\n this.task?.enqueue(this.response)'),
27
+ }];
28
+ // Validate every target before writing either dependency. A rejected second
29
+ // dependency must not leave the first dependency partially patched.
30
+ const changes = [];
31
+ for (const fix of fixes) {
32
+ const dependency = await realpath(join(app, 'node_modules', fix.name));
33
+ const withinApp = relative(app, dependency);
34
+ if (!withinApp || isAbsolute(withinApp) || withinApp.startsWith('..' + sep) || withinApp === '..') throw new Error('The dependency must be installed inside the explicitly selected app directory.');
35
+ const metadata = JSON.parse(await readFile(join(dependency, 'package.json'), 'utf8'));
36
+ if (metadata.name !== fix.name || metadata.version !== fix.version) throw new Error(`This reviewed compatibility fix targets ${fix.name} ${fix.version} only; review other versions separately.`);
37
+ const sourcePath = join(dependency, fix.path); const source = await readFile(sourcePath, 'utf8'); const current = digest(source);
38
+ if (current !== fix.originalSha256 && current !== fix.fixedSha256) throw new Error(`${fix.name} native source differs from both reviewed versions; refusing to replace it.`);
39
+ if (current === fix.originalSha256 && mode !== '--apply') throw new Error(`${fix.name} Android compatibility fix is missing. Apply the reviewed patch and rebuild the native app.`);
40
+ const updated = current === fix.fixedSha256 ? source : fix.apply(source);
41
+ if (digest(updated) !== fix.fixedSha256) throw new Error('Patched source fingerprint differs from the reviewed fix.');
42
+ changes.push({ fix, sourcePath, current, updated });
43
+ }
44
+ for (const change of changes) if (change.current !== change.fix.fixedSha256) await writeFile(change.sourcePath, change.updated);
45
+ const receipt = change => ({ version: change.fix.version, status: change.current === change.fix.fixedSha256 ? 'already applied' : 'applied', source: change.sourcePath, sourceSha256: change.fix.fixedSha256, nativeRebuildRequired: true });
46
+ console.log(JSON.stringify({ ...receipt(changes[0]), http: receipt(changes[1]) }));
@@ -0,0 +1,13 @@
1
+ diff --git a/node_modules/expo/android/src/main/java/expo/modules/fetch/NativeRequest.kt b/node_modules/expo/android/src/main/java/expo/modules/fetch/NativeRequest.kt
2
+ --- a/node_modules/expo/android/src/main/java/expo/modules/fetch/NativeRequest.kt
3
+ +++ b/node_modules/expo/android/src/main/java/expo/modules/fetch/NativeRequest.kt
4
+ @@ -73,8 +73,8 @@ internal class NativeRequest(appContext: AppContext, internal val response: NativeR
5
+ this.requestHolder.request = request
6
+
7
+ this.task = newClient.newCall(request)
8
+ - this.task?.enqueue(this.response)
9
+ response.onStarted()
10
+ + this.task?.enqueue(this.response)
11
+ }
12
+
13
+ fun cancel() {
@@ -0,0 +1,27 @@
1
+ diff --git a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObjectRegistry.kt b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObjectRegistry.kt
2
+ --- a/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObjectRegistry.kt
3
+ +++ b/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/sharedobjects/SharedObjectRegistry.kt
4
+ @@ -94,8 +94,10 @@ class SharedObjectRegistry(runtime: Runtime) {
5
+ }
6
+
7
+ internal fun toNativeObject(id: SharedObjectId): SharedObject {
8
+ - val native = pairs[id.ensureWasNotRelease()]?.first
9
+ - return native ?: throw InvalidSharedObjectIdException()
10
+ + return synchronized(this) {
11
+ + val native = pairs[id.ensureWasNotRelease()]?.first
12
+ + native ?: throw InvalidSharedObjectIdException()
13
+ + }
14
+ }
15
+
16
+ internal fun toNativeObjectOrNull(id: SharedObjectId): SharedObject? {
17
+ @@ -110,7 +112,9 @@ class SharedObjectRegistry(runtime: Runtime) {
18
+ }
19
+
20
+ val id = SharedObjectId(js.getProperty(sharedObjectIdPropertyName).getInt())
21
+ - return pairs[id]?.first
22
+ + return synchronized(this) {
23
+ + pairs[id]?.first
24
+ + }
25
+ }
26
+
27
+ internal fun toJavaScriptObjectOrNull(native: SharedObject): JavaScriptObject? {
@@ -0,0 +1,7 @@
1
+ /** Both Expo SQLite and FileSystem consume file URIs on Android and iOS. */
2
+ export declare function databaseLocation(directory: string, name: string): {
3
+ directoryUri: string;
4
+ encodedName: string;
5
+ fileUri: string;
6
+ };
7
+ //# sourceMappingURL=database-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-path.d.ts","sourceRoot":"","sources":["../src/database-path.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAehI"}
@@ -0,0 +1,22 @@
1
+ /** Both Expo SQLite and FileSystem consume file URIs on Android and iOS. */
2
+ export function databaseLocation(directory, name) {
3
+ if (!name.trim() || name === '.' || name === '..' || /[\x00-\x1f\x7f/\\:?]/.test(name))
4
+ throw new TypeError('Expected a persistent SQLite database basename.');
5
+ if (typeof directory !== 'string' || directory.length === 0)
6
+ throw new Error('A native SQLite database directory is required.');
7
+ let path = directory;
8
+ if (directory.startsWith('file:///')) {
9
+ if (/[?#]/.test(directory))
10
+ throw new TypeError('A database directory URI cannot contain a query or fragment.');
11
+ path = decodeURIComponent(directory.slice('file://'.length));
12
+ }
13
+ if (!path.startsWith('/') || /[\x00-\x1f\x7f\\]/.test(path))
14
+ throw new TypeError('Expected an absolute local database directory or file URI.');
15
+ // Android's defaultDatabaseDirectory is an absolute path without a URI scheme.
16
+ // Escape each component once so #, %, spaces and Unicode name the same file
17
+ // in the existence probe and in SQLite's native URI-to-path conversion.
18
+ const directoryUri = `file://${path.split('/').map(encodeURIComponent).join('/').replace(/\/+$/, '')}`;
19
+ const encodedName = encodeURIComponent(name);
20
+ return { directoryUri, encodedName, fileUri: `${directoryUri}/${encodedName}` };
21
+ }
22
+ //# sourceMappingURL=database-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-path.js","sourceRoot":"","sources":["../src/database-path.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,IAAY;IAC5D,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,iDAAiD,CAAC,CAAC;IAC/J,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IAChI,IAAI,IAAI,GAAG,SAAS,CAAC;IACrB,IAAI,SAAS,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACnC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,8DAA8D,CAAC,CAAC;QAChH,IAAI,GAAG,kBAAkB,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,4DAA4D,CAAC,CAAC;IAC/I,+EAA+E;IAC/E,4EAA4E;IAC5E,wEAAwE;IACxE,MAAM,YAAY,GAAG,UAAU,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;IACvG,MAAM,WAAW,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAC7C,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,YAAY,IAAI,WAAW,EAAE,EAAE,CAAC;AACpF,CAAC"}
package/dist/http.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { type RelayFetch, type RelayFetchInit, type RelayFetchResponse } from '@meshline/sdk';
2
+ interface NativeResponse extends Omit<RelayFetchResponse, 'body'> {
3
+ readonly body: ReadableStream<Uint8Array> | null;
4
+ }
5
+ /** Read the abort/error-aware stream, not Expo's native arrayBuffer fast path:
6
+ * that path can remain pending when native state becomes ERROR_RECEIVED. */
7
+ export declare function createExpoRelayFetch(fetch: (url: string, init: RelayFetchInit) => Promise<NativeResponse>): RelayFetch;
8
+ export {};
9
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAE9G,UAAU,cAAe,SAAQ,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC;IAAG,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAA;CAAE;AAEtH;4EAC4E;AAC5E,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC,cAAc,CAAC,GAAG,UAAU,CAqCtH"}
package/dist/http.js ADDED
@@ -0,0 +1,64 @@
1
+ import { throwIfAborted } from '@meshline/sdk';
2
+ /** Read the abort/error-aware stream, not Expo's native arrayBuffer fast path:
3
+ * that path can remain pending when native state becomes ERROR_RECEIVED. */
4
+ export function createExpoRelayFetch(fetch) {
5
+ return async (url, init) => {
6
+ throwIfAborted(init.signal);
7
+ const response = await fetch(url, { ...init, headers: { ...init.headers, 'Cache-Control': 'no-store, no-cache', Pragma: 'no-cache' }, credentials: 'omit', redirect: 'error' });
8
+ const body = response.body;
9
+ let consumed = false;
10
+ return {
11
+ status: response.status, headers: response.headers,
12
+ ...(response.redirected === undefined ? {} : { redirected: response.redirected }), ...(response.type === undefined ? {} : { type: response.type }),
13
+ body: body ? { async cancel() { consumed = true; await body.cancel(); } } : null,
14
+ async arrayBuffer() {
15
+ throwIfAborted(init.signal);
16
+ if (consumed)
17
+ throw new TypeError('Response body has already been consumed.');
18
+ consumed = true;
19
+ if (!body)
20
+ return new ArrayBuffer(0);
21
+ const reader = body.getReader();
22
+ const chunks = [];
23
+ let length = 0;
24
+ let cancellation;
25
+ let cancellationFailure;
26
+ const abort = () => {
27
+ // Observe cancellation immediately, then propagate any failure
28
+ // in finally after the pending read settles and its lock releases.
29
+ cancellation = reader.cancel(init.signal.reason).then(() => undefined, error => { cancellationFailure = { error }; });
30
+ };
31
+ init.signal.addEventListener('abort', abort, { once: true });
32
+ try {
33
+ for (;;) {
34
+ const chunk = await reader.read();
35
+ throwIfAborted(init.signal);
36
+ if (chunk.done)
37
+ break;
38
+ chunks.push(chunk.value);
39
+ length += chunk.value.byteLength;
40
+ }
41
+ const bytes = new Uint8Array(length);
42
+ let offset = 0;
43
+ for (const chunk of chunks) {
44
+ bytes.set(chunk, offset);
45
+ offset += chunk.byteLength;
46
+ }
47
+ return bytes.buffer;
48
+ }
49
+ finally {
50
+ init.signal.removeEventListener('abort', abort);
51
+ try {
52
+ await cancellation;
53
+ if (cancellationFailure)
54
+ throw cancellationFailure.error;
55
+ }
56
+ finally {
57
+ reader.releaseLock();
58
+ }
59
+ }
60
+ },
61
+ };
62
+ };
63
+ }
64
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { type RandomSource } from '@meshline/sdk';
2
+ import { NativeSqliteStore } from './sqlite-store.js';
3
+ import type { RelaySocketFactory } from '@meshline/sdk';
4
+ /** Requires a development/production build containing this package's native module. */
5
+ export declare function createExpoSocketFactory(): RelaySocketFactory;
6
+ export interface ExpoSqliteStoreOptions {
7
+ /** Persistent database basename, not an absolute path or SQLite connection URI. */
8
+ readonly databaseName: string;
9
+ /** Absolute app-private directory path or local file URI. Omit to use expo-sqlite's default. */
10
+ readonly directory?: string;
11
+ }
12
+ /** Native SQLite storage for Expo Modules applications; constructors never open or create files. */
13
+ export declare class ExpoSqliteStore extends NativeSqliteStore {
14
+ constructor(options: ExpoSqliteStoreOptions);
15
+ }
16
+ /** This API always uses the native cryptographic source, including development builds. */
17
+ export declare const expoRandom: RandomSource;
18
+ /** Uses expo/fetch explicitly: React Native's legacy global fetch cannot enforce these transport policies. */
19
+ export declare const expoRelayFetch: import("@meshline/sdk").RelayFetch;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAIxD,uFAAuF;AACvF,wBAAgB,uBAAuB,IAAI,kBAAkB,CAE5D;AAED,MAAM,WAAW,sBAAsB;IACnC,mFAAmF;IACnF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,gGAAgG;IAChG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,oGAAoG;AACpG,qBAAa,eAAgB,SAAQ,iBAAiB;IAClD,YAAY,OAAO,EAAE,sBAAsB,EAM1C;CACJ;AAED,0FAA0F;AAC1F,eAAO,MAAM,UAAU,EAAE,YAOxB,CAAC;AAEF,8GAA8G;AAC9G,eAAO,MAAM,cAAc,oCAA8D,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,38 @@
1
+ import { fetch as nativeFetch } from 'expo/fetch';
2
+ import { getRandomValues } from 'expo-crypto';
3
+ import { File } from 'expo-file-system';
4
+ import { defaultDatabaseDirectory, openDatabaseAsync } from 'expo-sqlite';
5
+ import {} from '@meshline/sdk';
6
+ import { NativeSqliteStore } from './sqlite-store.js';
7
+ import { requireNativeModule } from 'expo';
8
+ import { nativeSocketFactory } from './socket.js';
9
+ import { databaseLocation } from './database-path.js';
10
+ import { createExpoRelayFetch } from './http.js';
11
+ /** Requires a development/production build containing this package's native module. */
12
+ export function createExpoSocketFactory() {
13
+ return nativeSocketFactory(requireNativeModule('MeshlineRelaySocket'));
14
+ }
15
+ /** Native SQLite storage for Expo Modules applications; constructors never open or create files. */
16
+ export class ExpoSqliteStore extends NativeSqliteStore {
17
+ constructor(options) {
18
+ const location = databaseLocation(options.directory ?? defaultDatabaseDirectory, options.databaseName);
19
+ super({
20
+ async exists() { return new File(location.fileUri).exists; },
21
+ open() { return openDatabaseAsync(location.encodedName, { useNewConnection: true }, location.directoryUri); },
22
+ });
23
+ }
24
+ }
25
+ /** This API always uses the native cryptographic source, including development builds. */
26
+ export const expoRandom = {
27
+ bytes(length) {
28
+ if (!Number.isSafeInteger(length) || length < 0)
29
+ throw new RangeError('Invalid random byte count.');
30
+ const bytes = new Uint8Array(length);
31
+ for (let offset = 0; offset < length; offset += 65536)
32
+ getRandomValues(bytes.subarray(offset, offset + 65536));
33
+ return bytes;
34
+ },
35
+ };
36
+ /** Uses expo/fetch explicitly: React Native's legacy global fetch cannot enforce these transport policies. */
37
+ export const expoRelayFetch = createExpoRelayFetch((url, init) => nativeFetch(url, init));
38
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAqB,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAA2B,MAAM,aAAa,CAAC;AAE3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAEjD,uFAAuF;AACvF,MAAM,UAAU,uBAAuB;IACnC,OAAO,mBAAmB,CAAC,mBAAmB,CAAqB,qBAAqB,CAAC,CAAC,CAAC;AAC/F,CAAC;AASD,oGAAoG;AACpG,MAAM,OAAO,eAAgB,SAAQ,iBAAiB;IAClD,YAAY,OAA+B;QACvC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO,CAAC,SAAS,IAAI,wBAAwB,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;QACvG,KAAK,CAAC;YACF,KAAK,CAAC,MAAM,KAAK,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;YAC5D,IAAI,KAAK,OAAO,iBAAiB,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;SAChH,CAAC,CAAC;IACP,CAAC;CACJ;AAED,0FAA0F;AAC1F,MAAM,CAAC,MAAM,UAAU,GAAiB;IACpC,KAAK,CAAC,MAAM;QACR,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC;YAAE,MAAM,IAAI,UAAU,CAAC,4BAA4B,CAAC,CAAC;QACpG,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;QACrC,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,KAAK;YAAE,eAAe,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC;QAC/G,OAAO,KAAK,CAAC;IACjB,CAAC;CACJ,CAAC;AAEF,8GAA8G;AAC9G,MAAM,CAAC,MAAM,cAAc,GAAG,oBAAoB,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { type RelaySocketFactory } from '@meshline/sdk';
2
+ /** Internal Expo Modules contract, separated from module loading for adapter tests. */
3
+ export interface NativeSocketEvent {
4
+ readonly id: number;
5
+ readonly type: 'open' | 'text' | 'binary' | 'error' | 'close';
6
+ readonly text?: string;
7
+ readonly message?: string;
8
+ readonly code?: number;
9
+ readonly reason?: string;
10
+ }
11
+ export interface NativeSocketModule {
12
+ connect(id: number, endpoint: string): void;
13
+ send(id: number, text: string): void;
14
+ close(id: number, code: number, reason: string): void;
15
+ cancel(id: number): void;
16
+ bufferedAmount(id: number): number;
17
+ addListener(event: 'socket', listener: (event: NativeSocketEvent) => void): {
18
+ remove(): void;
19
+ };
20
+ }
21
+ /** The native implementation owns TLS, cookie/redirect policy and actual RFC close codes. */
22
+ export declare function nativeSocketFactory(module: NativeSocketModule): RelaySocketFactory;
23
+ //# sourceMappingURL=socket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAE3H,uFAAuF;AACvF,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC5B;AACD,MAAM,WAAW,kBAAkB;IAC/B,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG;QAAE,MAAM,IAAI,IAAI,CAAA;KAAE,CAAC;CAClG;AAID,6FAA6F;AAC7F,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,CAqElF"}