@mukae/client 0.0.0-stage → 0.1.0-beta.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/dist/choose-connect-action.d.ts +20 -0
- package/dist/choose-connect-action.js +15 -0
- package/dist/choose-connect-action.js.map +1 -0
- package/dist/connect.d.ts +32 -0
- package/dist/connect.js +77 -0
- package/dist/connect.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/pick-port.d.ts +6 -0
- package/dist/pick-port.js +9 -0
- package/dist/pick-port.js.map +1 -0
- package/package.json +31 -3
- package/README.md +0 -3
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { GatewayStatus } from "@mukae/contract";
|
|
2
|
+
export type StatusObservation = GatewayStatus | "unreachable" | "rejected";
|
|
3
|
+
export type ConnectAction = {
|
|
4
|
+
type: "wait";
|
|
5
|
+
} | {
|
|
6
|
+
type: "open-socket";
|
|
7
|
+
} | {
|
|
8
|
+
type: "unconfigured";
|
|
9
|
+
} | {
|
|
10
|
+
type: "cancelled";
|
|
11
|
+
} | {
|
|
12
|
+
type: "documentation";
|
|
13
|
+
url: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function chooseConnectAction(input: {
|
|
16
|
+
observation: StatusObservation;
|
|
17
|
+
elapsedMs: number;
|
|
18
|
+
readyTimeoutMs: number;
|
|
19
|
+
documentationUrl: string;
|
|
20
|
+
}): ConnectAction;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function chooseConnectAction(input) {
|
|
2
|
+
if (input.observation === "waiting")
|
|
3
|
+
return { type: "open-socket" };
|
|
4
|
+
if (input.observation === "unconfigured")
|
|
5
|
+
return { type: "unconfigured" };
|
|
6
|
+
if (input.observation === "cancelled")
|
|
7
|
+
return { type: "cancelled" };
|
|
8
|
+
if (input.observation === "choosing")
|
|
9
|
+
return { type: "wait" };
|
|
10
|
+
if (input.observation === "rejected" || input.elapsedMs >= input.readyTimeoutMs) {
|
|
11
|
+
return { type: "documentation", url: input.documentationUrl };
|
|
12
|
+
}
|
|
13
|
+
return { type: "wait" };
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=choose-connect-action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"choose-connect-action.js","sourceRoot":"","sources":["../src/choose-connect-action.ts"],"names":[],"mappings":"AAWA,MAAM,UAAU,mBAAmB,CAAC,KAKnC;IACC,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IACpE,IAAI,KAAK,CAAC,WAAW,KAAK,cAAc;QAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IAC1E,IAAI,KAAK,CAAC,WAAW,KAAK,WAAW;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACpE,IAAI,KAAK,CAAC,WAAW,KAAK,UAAU;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC9D,IAAI,KAAK,CAAC,WAAW,KAAK,UAAU,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC;QAChF,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,KAAK,CAAC,gBAAgB,EAAE,CAAC;IAChE,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type StatusObservation } from "./choose-connect-action.js";
|
|
2
|
+
export type ConnectOutcome = {
|
|
3
|
+
connected: true;
|
|
4
|
+
url: string;
|
|
5
|
+
} | {
|
|
6
|
+
connected: false;
|
|
7
|
+
reason: "unconfigured" | "cancelled" | "not_ready" | "no_free_port";
|
|
8
|
+
};
|
|
9
|
+
export declare const defaultReadyTimeoutMs = 60000;
|
|
10
|
+
/** Published setup page. A web app passes this as `documentationUrl`. */
|
|
11
|
+
export declare const explainPageUrl = "https://lulu0119.github.io/mukae/explain/";
|
|
12
|
+
export type ConnectDependencies = {
|
|
13
|
+
documentationUrl: string;
|
|
14
|
+
agentType: string;
|
|
15
|
+
port: number;
|
|
16
|
+
readyTimeoutMs: number;
|
|
17
|
+
origin: string;
|
|
18
|
+
createNonce: () => string;
|
|
19
|
+
openLaunchUrl: (url: string) => void;
|
|
20
|
+
readStatus: (port: number) => Promise<StatusObservation>;
|
|
21
|
+
goToDocumentation: (url: string) => void;
|
|
22
|
+
sleep: (milliseconds: number) => Promise<void>;
|
|
23
|
+
now: () => number;
|
|
24
|
+
};
|
|
25
|
+
/** Opens one more connection on a free port, so existing connections stay open. */
|
|
26
|
+
export declare function connectLocalGateway(options: {
|
|
27
|
+
documentationUrl: string;
|
|
28
|
+
agentType: string;
|
|
29
|
+
readyTimeoutMs?: number;
|
|
30
|
+
}): Promise<ConnectOutcome>;
|
|
31
|
+
export declare function runConnect(input: ConnectDependencies): Promise<ConnectOutcome>;
|
|
32
|
+
export declare function readGatewayStatus(port: number): Promise<StatusObservation>;
|
package/dist/connect.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { buildLaunchUrl, buildStatusUrl, buildWebSocketUrl, gatewayPortCandidates, parseStatusBody, } from "@mukae/contract";
|
|
2
|
+
import { chooseConnectAction } from "./choose-connect-action.js";
|
|
3
|
+
import { pickFreeGatewayPort } from "./pick-port.js";
|
|
4
|
+
export const defaultReadyTimeoutMs = 60_000;
|
|
5
|
+
/** Published setup page. A web app passes this as `documentationUrl`. */
|
|
6
|
+
export const explainPageUrl = "https://lulu0119.github.io/mukae/explain/";
|
|
7
|
+
const pollIntervalMs = 250;
|
|
8
|
+
/** Opens one more connection on a free port, so existing connections stay open. */
|
|
9
|
+
export async function connectLocalGateway(options) {
|
|
10
|
+
const port = await pickFreeGatewayPort({ candidates: gatewayPortCandidates, readStatus: readGatewayStatus });
|
|
11
|
+
if (port === undefined)
|
|
12
|
+
return { connected: false, reason: "no_free_port" };
|
|
13
|
+
return runConnect({
|
|
14
|
+
documentationUrl: options.documentationUrl,
|
|
15
|
+
agentType: options.agentType,
|
|
16
|
+
port,
|
|
17
|
+
readyTimeoutMs: options.readyTimeoutMs ?? defaultReadyTimeoutMs,
|
|
18
|
+
origin: window.location.origin,
|
|
19
|
+
createNonce: () => crypto.randomUUID(),
|
|
20
|
+
openLaunchUrl: openCustomScheme,
|
|
21
|
+
readStatus: readGatewayStatus,
|
|
22
|
+
goToDocumentation: (url) => window.location.assign(url),
|
|
23
|
+
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
|
|
24
|
+
now: () => Date.now(),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export async function runConnect(input) {
|
|
28
|
+
const nonce = input.createNonce();
|
|
29
|
+
input.openLaunchUrl(buildLaunchUrl({ nonce, origin: input.origin, agentType: input.agentType, port: input.port }));
|
|
30
|
+
const started = input.now();
|
|
31
|
+
while (true) {
|
|
32
|
+
const observation = await input.readStatus(input.port);
|
|
33
|
+
const elapsedMs = input.now() - started;
|
|
34
|
+
const action = chooseConnectAction({
|
|
35
|
+
observation,
|
|
36
|
+
elapsedMs,
|
|
37
|
+
readyTimeoutMs: input.readyTimeoutMs,
|
|
38
|
+
documentationUrl: input.documentationUrl,
|
|
39
|
+
});
|
|
40
|
+
if (action.type === "wait") {
|
|
41
|
+
await input.sleep(pollIntervalMs);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (action.type === "unconfigured")
|
|
45
|
+
return { connected: false, reason: "unconfigured" };
|
|
46
|
+
if (action.type === "cancelled")
|
|
47
|
+
return { connected: false, reason: "cancelled" };
|
|
48
|
+
if (action.type === "documentation") {
|
|
49
|
+
input.goToDocumentation(action.url);
|
|
50
|
+
return { connected: false, reason: "not_ready" };
|
|
51
|
+
}
|
|
52
|
+
return { connected: true, url: buildWebSocketUrl({ port: input.port, nonce }) };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export async function readGatewayStatus(port) {
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch(buildStatusUrl(port), { targetAddressSpace: "loopback" });
|
|
58
|
+
if (response.status === 403)
|
|
59
|
+
return "rejected";
|
|
60
|
+
if (!response.ok)
|
|
61
|
+
return "unreachable";
|
|
62
|
+
const status = parseStatusBody(await response.json());
|
|
63
|
+
return status ?? "unreachable";
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return "unreachable";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function openCustomScheme(url) {
|
|
70
|
+
const link = document.createElement("a");
|
|
71
|
+
link.href = url;
|
|
72
|
+
link.hidden = true;
|
|
73
|
+
document.body.appendChild(link);
|
|
74
|
+
link.click();
|
|
75
|
+
link.remove();
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=connect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.js","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { connectLocalGateway, explainPageUrl, type ConnectOutcome } from "./connect.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAuB,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { StatusObservation } from "./choose-connect-action.js";
|
|
2
|
+
/** The first candidate port with no gateway answering on it. */
|
|
3
|
+
export declare function pickFreeGatewayPort(input: {
|
|
4
|
+
candidates: readonly number[];
|
|
5
|
+
readStatus: (port: number) => Promise<StatusObservation>;
|
|
6
|
+
}): Promise<number | undefined>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The first candidate port with no gateway answering on it. */
|
|
2
|
+
export async function pickFreeGatewayPort(input) {
|
|
3
|
+
for (const port of input.candidates) {
|
|
4
|
+
if ((await input.readStatus(port)) === "unreachable")
|
|
5
|
+
return port;
|
|
6
|
+
}
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=pick-port.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pick-port.js","sourceRoot":"","sources":["../src/pick-port.ts"],"names":[],"mappings":"AAEA,gEAAgE;AAChE,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,KAGzC;IACC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QACpC,IAAI,CAAC,MAAM,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,aAAa;YAAE,OAAO,IAAI,CAAC;IACpE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mukae/client",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"description": "Browser helper that opens the local mukae gateway from a click.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/lulu0119/mukae.git",
|
|
12
|
+
"directory": "packages/client"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@mukae/contract": "0.1.0-beta.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^24.6.0",
|
|
28
|
+
"typescript": "^5.9.2"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -p tsconfig.json && printf '%s\n' '*.test.js' '*.test.js.map' '*.test.d.ts' '*.test.d.ts.map' '*.tsbuildinfo' > dist/.npmignore",
|
|
32
|
+
"test": "tsc -p tsconfig.json && node --test dist/choose-connect-action.test.js dist/connect.test.js dist/pick-port.test.js"
|
|
33
|
+
}
|
|
6
34
|
}
|
package/README.md
DELETED