@corpus-core/colibri-tor 1.1.22
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/README.md +90 -0
- package/dist/browser.d.ts +48 -0
- package/dist/browser.js +96 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +24 -0
- package/dist/node.d.ts +34 -0
- package/dist/node.js +275 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.js +23 -0
- package/node_modules/tor-js/README.md +166 -0
- package/node_modules/tor-js/dist/Log.d.ts +24 -0
- package/node_modules/tor-js/dist/Log.d.ts.map +1 -0
- package/node_modules/tor-js/dist/TorClient.d.ts +37 -0
- package/node_modules/tor-js/dist/TorClient.d.ts.map +1 -0
- package/node_modules/tor-js/dist/commonExports.d.ts +6 -0
- package/node_modules/tor-js/dist/commonExports.d.ts.map +1 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-base64/index.d.ts +3 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-base64/index.d.ts.map +1 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-base64/index.js +2139 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-base64/index.js.map +1 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-base64/singleton.d.ts +4 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-base64/singleton.d.ts.map +1 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-base64/singleton.js +2187 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-base64/singleton.js.map +1 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-cdn/index.d.ts +3 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-cdn/index.d.ts.map +1 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-cdn/index.js +2242 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-cdn/index.js.map +1 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-cdn/singleton.d.ts +4 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-cdn/singleton.d.ts.map +1 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-cdn/singleton.js +2290 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-cdn/singleton.js.map +1 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-file/index.d.ts +3 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-file/index.d.ts.map +1 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-file/index.js +2139 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-file/index.js.map +1 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-file/singleton.d.ts +4 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-file/singleton.d.ts.map +1 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-file/singleton.js +2187 -0
- package/node_modules/tor-js/dist/entryPoints/wasm-file/singleton.js.map +1 -0
- package/node_modules/tor-js/dist/helpers.d.ts +7 -0
- package/node_modules/tor-js/dist/helpers.d.ts.map +1 -0
- package/node_modules/tor-js/dist/polyfills.d.ts +1 -0
- package/node_modules/tor-js/dist/polyfills.d.ts.map +1 -0
- package/node_modules/tor-js/dist/singleton.d.ts +24 -0
- package/node_modules/tor-js/dist/singleton.d.ts.map +1 -0
- package/node_modules/tor-js/dist/socketProvider.d.ts +76 -0
- package/node_modules/tor-js/dist/socketProvider.d.ts.map +1 -0
- package/node_modules/tor-js/dist/storage/filesystem.d.ts +18 -0
- package/node_modules/tor-js/dist/storage/filesystem.d.ts.map +1 -0
- package/node_modules/tor-js/dist/storage/index.d.ts +7 -0
- package/node_modules/tor-js/dist/storage/index.d.ts.map +1 -0
- package/node_modules/tor-js/dist/storage/indexeddb.d.ts +14 -0
- package/node_modules/tor-js/dist/storage/indexeddb.d.ts.map +1 -0
- package/node_modules/tor-js/dist/storage/locking.d.ts +15 -0
- package/node_modules/tor-js/dist/storage/locking.d.ts.map +1 -0
- package/node_modules/tor-js/dist/storage/memory.d.ts +13 -0
- package/node_modules/tor-js/dist/storage/memory.d.ts.map +1 -0
- package/node_modules/tor-js/dist/storage/node-deps.d.ts +8 -0
- package/node_modules/tor-js/dist/storage/node-deps.d.ts.map +1 -0
- package/node_modules/tor-js/dist/tor_js_bg.wasm +0 -0
- package/node_modules/tor-js/dist/types.d.ts +41 -0
- package/node_modules/tor-js/dist/types.d.ts.map +1 -0
- package/node_modules/tor-js/dist/wasm-B6es-efC.d.ts +302 -0
- package/node_modules/tor-js/dist/wasm-pkg/tor_js.d.ts +311 -0
- package/node_modules/tor-js/dist/wasm-pkg/tor_js.js +1159 -0
- package/node_modules/tor-js/dist/wasm.d.ts +31 -0
- package/node_modules/tor-js/dist/wasm.d.ts.map +1 -0
- package/node_modules/tor-js/package.json +61 -0
- package/node_modules/tor-js/src/Log.ts +100 -0
- package/node_modules/tor-js/src/TorClient.ts +134 -0
- package/node_modules/tor-js/src/commonExports.ts +7 -0
- package/node_modules/tor-js/src/entryPoints/wasm-base64/index.ts +17 -0
- package/node_modules/tor-js/src/entryPoints/wasm-base64/singleton.ts +7 -0
- package/node_modules/tor-js/src/entryPoints/wasm-cdn/index.ts +155 -0
- package/node_modules/tor-js/src/entryPoints/wasm-cdn/singleton.ts +7 -0
- package/node_modules/tor-js/src/entryPoints/wasm-file/index.ts +19 -0
- package/node_modules/tor-js/src/entryPoints/wasm-file/singleton.ts +7 -0
- package/node_modules/tor-js/src/globals.d.ts +2 -0
- package/node_modules/tor-js/src/helpers.ts +20 -0
- package/node_modules/tor-js/src/polyfills.ts +4 -0
- package/node_modules/tor-js/src/singleton.ts +54 -0
- package/node_modules/tor-js/src/socketProvider.ts +405 -0
- package/node_modules/tor-js/src/storage/filesystem.ts +171 -0
- package/node_modules/tor-js/src/storage/index.ts +21 -0
- package/node_modules/tor-js/src/storage/indexeddb.ts +99 -0
- package/node_modules/tor-js/src/storage/locking.ts +195 -0
- package/node_modules/tor-js/src/storage/memory.ts +42 -0
- package/node_modules/tor-js/src/storage/node-deps.ts +23 -0
- package/node_modules/tor-js/src/types.ts +48 -0
- package/node_modules/tor-js/src/wasm-base64-data.d.ts +3 -0
- package/node_modules/tor-js/src/wasm.ts +135 -0
- package/package.json +67 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { TorStorage } from '#wasm';
|
|
2
|
+
import { getNodeDeps } from './node-deps.js';
|
|
3
|
+
|
|
4
|
+
/** Storage interface without locking — just the CRUD methods. */
|
|
5
|
+
export type TorStorageSimple = Omit<TorStorage, 'tryLock' | 'unlock'>;
|
|
6
|
+
|
|
7
|
+
function isNodeError(err: unknown): err is NodeJS.ErrnoException {
|
|
8
|
+
return err instanceof Error && 'code' in err;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Wrap a simple storage with platform-detected locking.
|
|
13
|
+
*
|
|
14
|
+
* - Browser: Web Locks API (`navigator.locks`)
|
|
15
|
+
* - Node.js: lock file at `~/.local/share/${name}/.lock`
|
|
16
|
+
*
|
|
17
|
+
* If the real lock can't be acquired (another tab/process holds it),
|
|
18
|
+
* the wrapper degrades gracefully: reads fall through to the inner storage,
|
|
19
|
+
* writes go to an in-memory overlay, and `tryLock()` still returns `true`.
|
|
20
|
+
*/
|
|
21
|
+
export function addLocking(inner: TorStorageSimple, name: string): TorStorage {
|
|
22
|
+
let hasRealLock = false;
|
|
23
|
+
let overlay: Map<string, string | null> | null = null;
|
|
24
|
+
|
|
25
|
+
// Web Locks state
|
|
26
|
+
let releaseLock: (() => void) | undefined;
|
|
27
|
+
let lockRequestDone: Promise<unknown> | undefined;
|
|
28
|
+
|
|
29
|
+
// Filesystem lock state
|
|
30
|
+
let lockPath: string | null = null;
|
|
31
|
+
let exitHandler: (() => void) | null = null;
|
|
32
|
+
let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
33
|
+
|
|
34
|
+
const STALE_MS = 30_000;
|
|
35
|
+
const HEARTBEAT_MS = 10_000;
|
|
36
|
+
|
|
37
|
+
async function tryAcquireReal(): Promise<boolean> {
|
|
38
|
+
// Browser
|
|
39
|
+
if (typeof navigator !== 'undefined' && navigator.locks) {
|
|
40
|
+
let resolveAcquired!: (v: boolean) => void;
|
|
41
|
+
const acquired = new Promise<boolean>(r => { resolveAcquired = r; });
|
|
42
|
+
|
|
43
|
+
lockRequestDone = navigator.locks.request(
|
|
44
|
+
`tor-js:${name}`,
|
|
45
|
+
{ ifAvailable: true },
|
|
46
|
+
(lock) => {
|
|
47
|
+
if (lock) {
|
|
48
|
+
resolveAcquired(true);
|
|
49
|
+
return new Promise<void>(r => { releaseLock = r; });
|
|
50
|
+
}
|
|
51
|
+
resolveAcquired(false);
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return acquired;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Node.js
|
|
59
|
+
if (typeof process !== 'undefined' && process.versions?.node) {
|
|
60
|
+
try {
|
|
61
|
+
const { fs, fsSync, path, os } = await getNodeDeps();
|
|
62
|
+
const dir = path.join(os.homedir(), '.local', 'share', name);
|
|
63
|
+
await fs.mkdir(dir, { recursive: true });
|
|
64
|
+
const lp = path.join(dir, '.lock');
|
|
65
|
+
|
|
66
|
+
// Try exclusive create
|
|
67
|
+
try {
|
|
68
|
+
await fs.writeFile(lp, `${process.pid}`, { flag: 'wx' });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (!isNodeError(err) || err.code !== 'EEXIST') throw err;
|
|
71
|
+
// Check if the existing lock is stale (mtime older than STALE_MS)
|
|
72
|
+
const stat = await fs.stat(lp);
|
|
73
|
+
if (Date.now() - stat.mtimeMs < STALE_MS) return false;
|
|
74
|
+
// Stale — take over
|
|
75
|
+
await fs.writeFile(lp, `${process.pid}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
lockPath = lp;
|
|
79
|
+
|
|
80
|
+
// Heartbeat: touch the lock file periodically so others know we're alive
|
|
81
|
+
heartbeatTimer = setInterval(async () => {
|
|
82
|
+
try {
|
|
83
|
+
const now = new Date();
|
|
84
|
+
await fs.utimes(lp, now, now);
|
|
85
|
+
} catch {}
|
|
86
|
+
}, HEARTBEAT_MS);
|
|
87
|
+
if (heartbeatTimer.unref) heartbeatTimer.unref();
|
|
88
|
+
|
|
89
|
+
exitHandler = () => {
|
|
90
|
+
try { fsSync.unlinkSync(lp); } catch {}
|
|
91
|
+
};
|
|
92
|
+
process.on('exit', exitHandler);
|
|
93
|
+
|
|
94
|
+
return true;
|
|
95
|
+
} catch (err) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
throw new Error("Failed to detect suitable locking mechanism");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function releaseReal(): Promise<void> {
|
|
104
|
+
// Web Locks — resolve the held promise, then wait for the browser
|
|
105
|
+
// to actually free the lock before returning.
|
|
106
|
+
if (releaseLock) {
|
|
107
|
+
releaseLock();
|
|
108
|
+
releaseLock = undefined;
|
|
109
|
+
await lockRequestDone;
|
|
110
|
+
lockRequestDone = undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Filesystem
|
|
114
|
+
if (heartbeatTimer) {
|
|
115
|
+
clearInterval(heartbeatTimer);
|
|
116
|
+
heartbeatTimer = null;
|
|
117
|
+
}
|
|
118
|
+
if (lockPath) {
|
|
119
|
+
const { fs } = await getNodeDeps();
|
|
120
|
+
try {
|
|
121
|
+
await fs.unlink(lockPath);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
if (!isNodeError(err) || err.code !== 'ENOENT') throw err;
|
|
124
|
+
}
|
|
125
|
+
lockPath = null;
|
|
126
|
+
}
|
|
127
|
+
if (exitHandler) {
|
|
128
|
+
process.removeListener('exit', exitHandler);
|
|
129
|
+
exitHandler = null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
async get(key: string): Promise<string | null> {
|
|
135
|
+
if (overlay?.has(key)) return overlay.get(key)!;
|
|
136
|
+
return inner.get(key);
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
async set(key: string, value: string): Promise<void> {
|
|
140
|
+
if (overlay) {
|
|
141
|
+
overlay.set(key, value);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
return inner.set(key, value);
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
async delete(key: string): Promise<void> {
|
|
148
|
+
if (overlay) {
|
|
149
|
+
overlay.set(key, null);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
return inner.delete(key);
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
async keys(prefix: string): Promise<string[]> {
|
|
156
|
+
const base = await inner.keys(prefix);
|
|
157
|
+
if (!overlay) return base;
|
|
158
|
+
const result = new Set(base);
|
|
159
|
+
for (const [k, v] of overlay) {
|
|
160
|
+
if (!k.startsWith(prefix)) continue;
|
|
161
|
+
if (v !== null) result.add(k);
|
|
162
|
+
else result.delete(k);
|
|
163
|
+
}
|
|
164
|
+
return [...result].sort();
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
async getAll(prefix: string): Promise<[string, string][]> {
|
|
168
|
+
const base = await inner.getAll(prefix);
|
|
169
|
+
if (!overlay) return base;
|
|
170
|
+
const merged = new Map<string, string>(base);
|
|
171
|
+
for (const [k, v] of overlay) {
|
|
172
|
+
if (!k.startsWith(prefix)) continue;
|
|
173
|
+
if (v !== null) merged.set(k, v);
|
|
174
|
+
else merged.delete(k);
|
|
175
|
+
}
|
|
176
|
+
return [...merged.entries()];
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
async tryLock(): Promise<boolean> {
|
|
180
|
+
if (hasRealLock) return false;
|
|
181
|
+
|
|
182
|
+
const acquired = await tryAcquireReal();
|
|
183
|
+
hasRealLock = acquired;
|
|
184
|
+
overlay = acquired ? null : (overlay ?? new Map());
|
|
185
|
+
|
|
186
|
+
return true;
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
async unlock(): Promise<void> {
|
|
190
|
+
await releaseReal();
|
|
191
|
+
hasRealLock = false;
|
|
192
|
+
overlay = null;
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { TorStorage } from '#wasm';
|
|
2
|
+
|
|
3
|
+
export class MemoryStorage implements TorStorage {
|
|
4
|
+
private data = new Map<string, string>();
|
|
5
|
+
private locked = false;
|
|
6
|
+
|
|
7
|
+
async get(key: string): Promise<string | null> {
|
|
8
|
+
return this.data.get(key) ?? null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async set(key: string, value: string): Promise<void> {
|
|
12
|
+
this.data.set(key, value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async delete(key: string): Promise<void> {
|
|
16
|
+
this.data.delete(key);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async keys(prefix: string): Promise<string[]> {
|
|
20
|
+
return [...this.data.keys()].filter(k => k.startsWith(prefix)).sort();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async getAll(prefix: string): Promise<[string, string][]> {
|
|
24
|
+
const result: [string, string][] = [];
|
|
25
|
+
for (const [key, value] of this.data) {
|
|
26
|
+
if (key.startsWith(prefix)) {
|
|
27
|
+
result.push([key, value]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async tryLock(): Promise<boolean> {
|
|
34
|
+
if (this.locked) return false;
|
|
35
|
+
this.locked = true;
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async unlock(): Promise<void> {
|
|
40
|
+
this.locked = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type NodeDeps = {
|
|
2
|
+
fs: typeof import('node:fs/promises');
|
|
3
|
+
fsSync: typeof import('node:fs');
|
|
4
|
+
os: typeof import('node:os');
|
|
5
|
+
path: typeof import('node:path');
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
let promise: Promise<NodeDeps> | undefined;
|
|
9
|
+
|
|
10
|
+
export function getNodeDeps(): Promise<NodeDeps> {
|
|
11
|
+
if (!promise) {
|
|
12
|
+
promise = (async () => {
|
|
13
|
+
const [fs, fsSync, os, path] = await Promise.all([
|
|
14
|
+
import('node:fs/promises').then(m => m.default ?? m),
|
|
15
|
+
import('node:fs').then(m => m.default ?? m),
|
|
16
|
+
import('node:os').then(m => m.default ?? m),
|
|
17
|
+
import('node:path').then(m => m.default ?? m),
|
|
18
|
+
]);
|
|
19
|
+
return { fs, fsSync, os, path };
|
|
20
|
+
})();
|
|
21
|
+
}
|
|
22
|
+
return promise;
|
|
23
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Log } from './Log.js';
|
|
2
|
+
import type { TorStorage } from '#wasm';
|
|
3
|
+
import type { ArtiSocketProvider } from './socketProvider.js';
|
|
4
|
+
|
|
5
|
+
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Options for creating a TorClient.
|
|
9
|
+
*
|
|
10
|
+
* In browsers, provide a gateway URL for WebSocket/WebRTC relay connections
|
|
11
|
+
* and fast bootstrap. In Node.js/Deno, the gateway connects via direct TCP
|
|
12
|
+
* without a URL; providing one enables fast bootstrap from that server.
|
|
13
|
+
*/
|
|
14
|
+
export type TorClientOptions = {
|
|
15
|
+
/** Gateway URL (e.g., `"https://tor-js-gateway.voltrevo.com"`). Required in browsers for relay connections; optional in Node.js/Deno (enables fast bootstrap). */
|
|
16
|
+
gateway?: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Optional logger instance.
|
|
20
|
+
* Note: WASM logging is global, so all TorClient instances receive all WASM
|
|
21
|
+
* log events, not just their own. This is because wasm-bindgen generates a
|
|
22
|
+
* single module-level instance (`let wasm;`), so all Rust global state
|
|
23
|
+
* (including the tracing subscriber) is shared.
|
|
24
|
+
*/
|
|
25
|
+
log?: Log;
|
|
26
|
+
|
|
27
|
+
/** Optional storage for persistent state (implements TorStorage). */
|
|
28
|
+
storage?: TorStorage;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Minimum log level for this client's log listener. Defaults to 'debug'.
|
|
32
|
+
* Can be changed at runtime via `TorClient.setLogLevel()`.
|
|
33
|
+
* The WASM subscriber auto-widens to the broadest level across all clients.
|
|
34
|
+
*/
|
|
35
|
+
logLevel?: LogLevel;
|
|
36
|
+
|
|
37
|
+
/** Optional custom socket provider. When set, overrides the default ArtiSocketProvider created from the gateway URL. */
|
|
38
|
+
socketProvider?: ArtiSocketProvider;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type { TorStorage } from '#wasm';
|
|
42
|
+
|
|
43
|
+
export interface FetchInit {
|
|
44
|
+
method?: string;
|
|
45
|
+
headers?: Record<string, string>;
|
|
46
|
+
body?: string | Uint8Array | ArrayBuffer;
|
|
47
|
+
signal?: AbortSignal;
|
|
48
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import './polyfills.js';
|
|
2
|
+
import initWasm, {
|
|
3
|
+
init as wasmInit,
|
|
4
|
+
setLogCallback as wasmSetLogCallback,
|
|
5
|
+
setLogLevel as wasmSetLogLevel,
|
|
6
|
+
TorClient as WasmTorClient,
|
|
7
|
+
TorClientOptions as WasmTorClientOptions,
|
|
8
|
+
} from '#wasm';
|
|
9
|
+
|
|
10
|
+
export { WasmTorClient, WasmTorClientOptions };
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Log listener management
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
type WasmLogCallback = (level: string, target: string, message: string) => void;
|
|
17
|
+
|
|
18
|
+
const LEVEL_ORDER = ['trace', 'debug', 'info', 'warn', 'error'] as const;
|
|
19
|
+
|
|
20
|
+
function levelIndex(level: string): number {
|
|
21
|
+
const idx = LEVEL_ORDER.indexOf(level as typeof LEVEL_ORDER[number]);
|
|
22
|
+
return idx === -1 ? 1 : idx; // default to debug
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface LogListener {
|
|
26
|
+
callback: WasmLogCallback;
|
|
27
|
+
levelIdx: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const logListeners = new Map<WasmLogCallback, LogListener>();
|
|
31
|
+
|
|
32
|
+
/** Recompute the broadest level across all listeners and update the WASM filter. */
|
|
33
|
+
function syncWasmLogLevel(): void {
|
|
34
|
+
let broadestIdx = LEVEL_ORDER.length - 1; // start at narrowest (error)
|
|
35
|
+
for (const listener of logListeners.values()) {
|
|
36
|
+
if (listener.levelIdx < broadestIdx) {
|
|
37
|
+
broadestIdx = listener.levelIdx;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
wasmSetLogLevel(LEVEL_ORDER[broadestIdx]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Register a log callback at a given level. The WASM subscriber is
|
|
45
|
+
* automatically widened to the broadest level across all listeners.
|
|
46
|
+
* Each listener only receives events at or above its own level.
|
|
47
|
+
* Returns an unregister function.
|
|
48
|
+
*/
|
|
49
|
+
export function addLogListener(cb: WasmLogCallback, level: string = 'debug'): () => void {
|
|
50
|
+
logListeners.set(cb, { callback: cb, levelIdx: levelIndex(level) });
|
|
51
|
+
syncWasmLogLevel();
|
|
52
|
+
return () => {
|
|
53
|
+
logListeners.delete(cb);
|
|
54
|
+
if (logListeners.size > 0) {
|
|
55
|
+
syncWasmLogLevel();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Update the level for an existing listener and re-sync the WASM filter.
|
|
62
|
+
*/
|
|
63
|
+
export function setListenerLevel(cb: WasmLogCallback, level: string): void {
|
|
64
|
+
const listener = logListeners.get(cb);
|
|
65
|
+
if (listener) {
|
|
66
|
+
listener.levelIdx = levelIndex(level);
|
|
67
|
+
syncWasmLogLevel();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// WASM initialization
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
type WasmSourceProvider = () => Promise<BufferSource | Uint8Array>;
|
|
76
|
+
|
|
77
|
+
let initPromise: Promise<void> | null = null;
|
|
78
|
+
let customWasmUrl: string | URL | undefined;
|
|
79
|
+
let wasmSourceProvider: WasmSourceProvider | undefined;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Override the WASM binary URL. Must be called before any TorClient is created.
|
|
83
|
+
*/
|
|
84
|
+
export function setWasmUrl(url: string | URL): void {
|
|
85
|
+
if (initPromise) {
|
|
86
|
+
throw new Error('setWasmUrl() must be called before any TorClient is created');
|
|
87
|
+
}
|
|
88
|
+
customWasmUrl = url;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Set a custom WASM source provider. Called by entry points to configure
|
|
93
|
+
* how the WASM binary is loaded (e.g. base64 decode, CDN fetch).
|
|
94
|
+
* Must be called before any TorClient is created.
|
|
95
|
+
*/
|
|
96
|
+
export function setWasmSourceProvider(provider: WasmSourceProvider): void {
|
|
97
|
+
if (initPromise) {
|
|
98
|
+
throw new Error('setWasmSourceProvider() must be called before any TorClient is created');
|
|
99
|
+
}
|
|
100
|
+
wasmSourceProvider = provider;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Ensures the WASM module is loaded and initialized. Idempotent.
|
|
105
|
+
*/
|
|
106
|
+
export async function ensureWasmInitialized(): Promise<void> {
|
|
107
|
+
if (initPromise) return initPromise;
|
|
108
|
+
initPromise = doInit();
|
|
109
|
+
return initPromise;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function doInit(): Promise<void> {
|
|
113
|
+
if (customWasmUrl) {
|
|
114
|
+
await initWasm({ module_or_path: customWasmUrl });
|
|
115
|
+
} else if (wasmSourceProvider) {
|
|
116
|
+
await initWasm({ module_or_path: await wasmSourceProvider() });
|
|
117
|
+
} else {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'No WASM source configured. Import from a specific entry point '
|
|
120
|
+
+ '(tor-js/wasm-base64, tor-js/wasm-cdn, or tor-js/wasm-file) '
|
|
121
|
+
+ 'or call setWasmUrl() before creating a TorClient.',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
wasmInit();
|
|
125
|
+
|
|
126
|
+
// Install a single fan-out callback that dispatches to matching listeners
|
|
127
|
+
wasmSetLogCallback((level: string, target: string, message: string) => {
|
|
128
|
+
const lvl = levelIndex(level);
|
|
129
|
+
for (const listener of logListeners.values()) {
|
|
130
|
+
if (lvl >= listener.levelIdx) {
|
|
131
|
+
listener.callback(level, target, message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@corpus-core/colibri-tor",
|
|
3
|
+
"version": "1.1.22",
|
|
4
|
+
"description": "Tor network transport for Colibri Stateless. Routes RPC requests through Tor for enhanced privacy -- via Arti WASM in browsers, via SOCKS5 proxy in Node.js.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./browser": {
|
|
14
|
+
"types": "./dist/browser.d.ts",
|
|
15
|
+
"import": "./dist/browser.js"
|
|
16
|
+
},
|
|
17
|
+
"./node": {
|
|
18
|
+
"types": "./dist/node.d.ts",
|
|
19
|
+
"import": "./dist/node.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"bundleDependencies": [
|
|
27
|
+
"tor-js"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"build:arti": "bash scripts/build-arti.sh",
|
|
32
|
+
"test": "node --test test/*.test.mjs"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/corpus-core/c4.git",
|
|
37
|
+
"directory": "bindings/emscripten/tor"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"ethereum",
|
|
41
|
+
"tor",
|
|
42
|
+
"arti",
|
|
43
|
+
"privacy",
|
|
44
|
+
"socks5",
|
|
45
|
+
"colibri",
|
|
46
|
+
"stateless"
|
|
47
|
+
],
|
|
48
|
+
"author": "Simon Jentzsch <simon@corpus.io>",
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"tor-js": "file:.arti-build/crates/tor-js/ts-wrapper"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@corpus-core/colibri-stateless": ">=0.1.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"@corpus-core/colibri-stateless": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"typescript": "^5.5.0"
|
|
63
|
+
},
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"access": "public"
|
|
66
|
+
}
|
|
67
|
+
}
|