@stim-cli/transport 0.0.0-stage → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +28 -2
- package/dist/index.d.mts +59 -0
- package/dist/index.mjs +69 -0
- package/package.json +29 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Janic Duplessis
|
|
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
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @stim-cli/transport
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The contract between stim-server and the transports that carry phones and other machines to it. A transport
|
|
4
|
+
package exports `apiVersion`, `id` and `createTransport()`, and stim-server loads it with `loadTransport()`:
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { loadTransport } from '@stim-cli/transport';
|
|
8
|
+
|
|
9
|
+
const loaded = await loadTransport({
|
|
10
|
+
reference: '@stim-cli/transport-tailscale',
|
|
11
|
+
load: () => import('@stim-cli/transport-tailscale'),
|
|
12
|
+
input: { env: process.env, options: {} },
|
|
13
|
+
});
|
|
14
|
+
if ('unavailable' in loaded) console.error(loaded.unavailable);
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`loadTransport()` never throws. A missing package, a different `apiVersion`, a failing `createTransport()` or a
|
|
18
|
+
load that takes longer than 10 seconds comes back as `unavailable` with the reason.
|
|
19
|
+
|
|
20
|
+
A transport declares its `traits` and implements the capabilities it has:
|
|
21
|
+
|
|
22
|
+
- `bindAddresses()`: the addresses stim-server listens on, besides loopback, for the clients it carries.
|
|
23
|
+
- `watch(listener)`: called when those addresses or the transport's state may have changed.
|
|
24
|
+
- `identify({ address, request })`: the machine a connection comes from. stim-server binds a device token to it at
|
|
25
|
+
pairing and refuses the token from any other machine.
|
|
26
|
+
- `close()`: stops any background work.
|
|
27
|
+
|
|
28
|
+
The design and the capabilities still to come are in
|
|
29
|
+
[docs/specs/2026-10-13-pluggable-transports-design.md](../../docs/specs/2026-10-13-pluggable-transports-design.md).
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { IncomingMessage } from "node:http";
|
|
2
|
+
//#region index.d.ts
|
|
3
|
+
declare const TRANSPORT_API_VERSION: 1;
|
|
4
|
+
interface TransportTraits {
|
|
5
|
+
/** Traffic stays on a private overlay whose source addresses identify the sending machine. */
|
|
6
|
+
privateNetwork: boolean;
|
|
7
|
+
/** Where TLS ends: on this machine, at a provider's edge, or nowhere. */
|
|
8
|
+
tls: "end-to-end" | "edge" | "none";
|
|
9
|
+
/** `identify` names the machine a connection comes from, so a device token can be bound to it. */
|
|
10
|
+
peerIdentity: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** The machine a connection comes from, as the transport identifies it. `id` stays the same across renames. */
|
|
13
|
+
interface TransportIdentity {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
user: string;
|
|
17
|
+
}
|
|
18
|
+
interface TransportPeer {
|
|
19
|
+
/** The connection's source address as stim-server sees it, after any proxy the transport runs. */
|
|
20
|
+
address: string;
|
|
21
|
+
request: IncomingMessage;
|
|
22
|
+
}
|
|
23
|
+
interface Transport {
|
|
24
|
+
id: string;
|
|
25
|
+
traits: TransportTraits;
|
|
26
|
+
/** The addresses stim-server listens on, besides loopback, for clients this transport carries. */
|
|
27
|
+
bindAddresses?(): string[];
|
|
28
|
+
/** Calls `listener` whenever `bindAddresses` or the transport's state may have changed. */
|
|
29
|
+
watch?(listener: () => void): () => void;
|
|
30
|
+
identify?(peer: TransportPeer): Promise<TransportIdentity | null>;
|
|
31
|
+
close?(): void;
|
|
32
|
+
}
|
|
33
|
+
interface TransportInput {
|
|
34
|
+
env: NodeJS.ProcessEnv;
|
|
35
|
+
options: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
interface TransportModule {
|
|
38
|
+
apiVersion: typeof TRANSPORT_API_VERSION;
|
|
39
|
+
id: string;
|
|
40
|
+
createTransport(input: TransportInput): Transport | Promise<Transport>;
|
|
41
|
+
}
|
|
42
|
+
type LoadTransportResult = {
|
|
43
|
+
transport: Transport;
|
|
44
|
+
} | {
|
|
45
|
+
unavailable: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Imports the transport module `reference` with `load` and creates its transport. Never throws: a missing module, a
|
|
49
|
+
* wrong `apiVersion`, a failing factory, or a load that takes longer than `timeoutMs` comes back as `unavailable`
|
|
50
|
+
* with the reason.
|
|
51
|
+
*/
|
|
52
|
+
declare function loadTransport({ reference, load, input, timeoutMs }: {
|
|
53
|
+
reference: string;
|
|
54
|
+
load: () => Promise<unknown>;
|
|
55
|
+
input: TransportInput;
|
|
56
|
+
timeoutMs?: number;
|
|
57
|
+
}): Promise<LoadTransportResult>;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { LoadTransportResult, TRANSPORT_API_VERSION, Transport, TransportIdentity, TransportInput, TransportModule, TransportPeer, TransportTraits, loadTransport };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region index.ts
|
|
2
|
+
const TRANSPORT_API_VERSION = 1;
|
|
3
|
+
const LOAD_TIMEOUT_MS = 1e4;
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function reason(error) {
|
|
8
|
+
return (error instanceof Error ? error.message : String(error)).split("\n")[0] ?? "";
|
|
9
|
+
}
|
|
10
|
+
function hasFactory(value) {
|
|
11
|
+
return typeof value.createTransport === "function";
|
|
12
|
+
}
|
|
13
|
+
const CAPABILITIES = [
|
|
14
|
+
"bindAddresses",
|
|
15
|
+
"watch",
|
|
16
|
+
"identify",
|
|
17
|
+
"close"
|
|
18
|
+
];
|
|
19
|
+
function isTransport(value) {
|
|
20
|
+
return isRecord(value) && typeof value.id === "string" && isRecord(value.traits) && CAPABILITIES.every((name) => value[name] === void 0 || typeof value[name] === "function");
|
|
21
|
+
}
|
|
22
|
+
function moduleCandidate(namespace) {
|
|
23
|
+
if (!isRecord(namespace)) return namespace;
|
|
24
|
+
return "apiVersion" in namespace ? namespace : namespace.default;
|
|
25
|
+
}
|
|
26
|
+
async function importTransport(reference, load, input) {
|
|
27
|
+
let namespace;
|
|
28
|
+
try {
|
|
29
|
+
namespace = await load();
|
|
30
|
+
} catch (error) {
|
|
31
|
+
return { unavailable: `${reference} is not installed: ${reason(error)}` };
|
|
32
|
+
}
|
|
33
|
+
const candidate = moduleCandidate(namespace);
|
|
34
|
+
if (!isRecord(candidate) || candidate.apiVersion !== 1) {
|
|
35
|
+
const found = isRecord(candidate) ? candidate.apiVersion : candidate;
|
|
36
|
+
return { unavailable: `${reference}: expected apiVersion 1, found ${JSON.stringify(found)}` };
|
|
37
|
+
}
|
|
38
|
+
if (!hasFactory(candidate)) return { unavailable: `${reference} does not export createTransport()` };
|
|
39
|
+
let transport;
|
|
40
|
+
try {
|
|
41
|
+
transport = await candidate.createTransport(input);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return { unavailable: `${reference}: createTransport() failed: ${reason(error)}` };
|
|
44
|
+
}
|
|
45
|
+
if (!isTransport(transport)) return { unavailable: `${reference}: createTransport() did not return a transport` };
|
|
46
|
+
return { transport };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Imports the transport module `reference` with `load` and creates its transport. Never throws: a missing module, a
|
|
50
|
+
* wrong `apiVersion`, a failing factory, or a load that takes longer than `timeoutMs` comes back as `unavailable`
|
|
51
|
+
* with the reason.
|
|
52
|
+
*/
|
|
53
|
+
async function loadTransport({ reference, load, input, timeoutMs = LOAD_TIMEOUT_MS }) {
|
|
54
|
+
let timer;
|
|
55
|
+
const expired = new Promise((resolve) => {
|
|
56
|
+
timer = setTimeout(() => resolve("timeout"), timeoutMs);
|
|
57
|
+
});
|
|
58
|
+
const loading = importTransport(reference, load, input).catch((error) => ({ unavailable: reason(error) }));
|
|
59
|
+
try {
|
|
60
|
+
const result = await Promise.race([loading, expired]);
|
|
61
|
+
if (result !== "timeout") return result;
|
|
62
|
+
loading.then((late) => "transport" in late ? late.transport.close?.() : void 0);
|
|
63
|
+
return { unavailable: `${reference} did not load within ${timeoutMs}ms` };
|
|
64
|
+
} finally {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { TRANSPORT_API_VERSION, loadTransport };
|
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stim-cli/transport",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.21.0",
|
|
4
|
+
"description": "Transport contract for stim-server: how phones and other machines reach it.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/appandflow/stim.git",
|
|
9
|
+
"directory": "packages/transport"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "dist/index.mjs",
|
|
18
|
+
"types": "dist/index.d.mts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.mts",
|
|
22
|
+
"module-sync": "./dist/index.mjs",
|
|
23
|
+
"default": "./dist/index.mjs"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=22.12.0"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsdown"
|
|
31
|
+
}
|
|
6
32
|
}
|