@knpkv/agent-usage 0.2.1 → 0.4.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/main.js +3 -2
- package/dist/main.js.map +1 -1
- package/dist/server/Api.d.ts +54 -54
- package/dist/server/ControlSocket.d.ts +2 -2
- package/dist/server/ControlSocket.js +2 -1
- package/dist/server/ControlSocket.js.map +1 -1
- package/dist/server/HttpApplication.d.ts +9 -4
- package/dist/server/HttpApplication.js +15 -60
- package/dist/server/HttpApplication.js.map +1 -1
- package/dist/server/Live.d.ts +18 -2
- package/dist/server/Live.js +4 -9
- package/dist/server/Live.js.map +1 -1
- package/dist/server/OwnerSession.d.ts +19 -64
- package/dist/server/OwnerSession.js +20 -202
- package/dist/server/OwnerSession.js.map +1 -1
- package/dist/server/Server.d.ts +3 -3
- package/dist/server/Server.js +3 -2
- package/dist/server/Server.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
|
@@ -1,214 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* **Mental model**
|
|
5
|
-
*
|
|
6
|
-
* - **One process, one operator.** The server binds a loopback address and mints a session token for
|
|
7
|
-
* itself at startup. There is no user table and no login: the person who can read the terminal is
|
|
8
|
-
* the person who gets in, which is the authority a page showing their own agents' usage, ticket
|
|
9
|
-
* keys and working directories should demand.
|
|
10
|
-
* - **The URL is the handshake.** A bootstrap code rides in a fragment the browser never sends
|
|
11
|
-
* upstream and is spent the first time it is exchanged for the session cookie. One is minted when
|
|
12
|
-
* the server starts and another each time `agent-usage login` asks over the control socket; both
|
|
13
|
-
* go through {@link mintBootstrapUrl}, and a newer code replaces an unspent one.
|
|
14
|
-
* - **Every route only reads, and a read needs the cookie and must not be a browser cross-origin
|
|
15
|
-
* request.** Another page in the same browser can make the browser send a cookie, but Fetch
|
|
16
|
-
* Metadata keeps that page from reading usage without an Origin. With no writes there is no CSRF
|
|
17
|
-
* token to issue.
|
|
18
|
-
*
|
|
19
|
-
* The rules are `@knpkv/jcf-web`'s owner session minus its write path, over the same
|
|
20
|
-
* `@knpkv/browser-pairing` credential primitives. They are re-stated rather than imported because
|
|
21
|
-
* that module is private to that application; duplicating the *policy* while sharing the
|
|
22
|
-
* *primitives* is the smaller of the two mistakes available.
|
|
23
|
-
*
|
|
24
|
-
* @module
|
|
25
|
-
*/
|
|
26
|
-
import { credentialValuesEqual, decideOneTimeCredential, expiresAt, issuePairingCode, issueSessionToken, serializeCredentialCookie } from "@knpkv/browser-pairing";
|
|
27
|
-
import { Clock, Context, Effect, Layer, Redacted, Ref, Schema } from "effect";
|
|
28
|
-
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
1
|
+
import * as OwnerSession from "@knpkv/browser-pairing/owner-session";
|
|
2
|
+
import { Effect, Layer, Redacted } from "effect";
|
|
29
3
|
import { ForbiddenApiError, OwnerSessionAuth, UnauthorizedApiError } from "./Api.js";
|
|
30
|
-
/**
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
/** Methods that only read: the only ones this server answers on its API. */
|
|
40
|
-
const safeMethods = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
41
|
-
/**
|
|
42
|
-
* The one origin other than the bound server that may hold a session: the Vite dev server, which
|
|
43
|
-
* proxies both the API and the bootstrap exchange, so the browser stays on a single origin.
|
|
44
|
-
*/
|
|
45
|
-
const devPublicOrigin = "http://localhost:5173";
|
|
46
|
-
export class OwnerSessionSecrets extends Context.Service()("@knpkv/agent-usage/OwnerSessionSecrets") {
|
|
47
|
-
}
|
|
48
|
-
export class UnsafeServerOriginError extends Schema.TaggedError()("UnsafeServerOriginError", { message: Schema.String, origin: Schema.String }) {
|
|
49
|
-
}
|
|
50
|
-
export const isLoopbackHostname = (hostname) => hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1" || hostname === "[::1]";
|
|
51
|
-
export const ownerSessionOrigin = (hostname, port) => `http://${hostname === "::1" ? "[::1]" : hostname}:${port}`;
|
|
52
|
-
export const requireLoopbackOrigin = Effect.fn("OwnerSession.requireLoopbackOrigin")(function* (origin) {
|
|
53
|
-
const url = yield* Effect.try({
|
|
54
|
-
catch: () => new UnsafeServerOriginError({ message: "agent-usage needs a valid HTTP origin", origin }),
|
|
55
|
-
try: () => new URL(origin)
|
|
56
|
-
});
|
|
57
|
-
if (url.protocol !== "http:" || !isLoopbackHostname(url.hostname) || url.search !== "" || url.hash !== "") {
|
|
58
|
-
return yield* new UnsafeServerOriginError({
|
|
59
|
-
message: "agent-usage may only be reached over HTTP on a loopback host",
|
|
60
|
-
origin
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
return url.origin;
|
|
64
|
-
});
|
|
65
|
-
/** The advertised origin: the bound server, or the dev server that proxies to it. */
|
|
66
|
-
export const resolvePublicOrigin = Effect.fn("OwnerSession.resolvePublicOrigin")(function* (configuredOrigin, authorityOrigin) {
|
|
67
|
-
const authority = yield* requireLoopbackOrigin(authorityOrigin);
|
|
68
|
-
if (configuredOrigin === undefined)
|
|
69
|
-
return authority;
|
|
70
|
-
const configured = yield* requireLoopbackOrigin(configuredOrigin);
|
|
71
|
-
if (configured !== authority && configured !== devPublicOrigin) {
|
|
72
|
-
return yield* new UnsafeServerOriginError({
|
|
73
|
-
message: "agent-usage's public origin must be the bound server or its dev proxy",
|
|
74
|
-
origin: configuredOrigin
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
return configured;
|
|
78
|
-
});
|
|
79
|
-
export const makeOwnerSessionSecrets = Effect.fn("OwnerSession.makeSecrets")(function* (authorityOrigin, configuredPublicOrigin) {
|
|
80
|
-
const validated = yield* requireLoopbackOrigin(authorityOrigin);
|
|
81
|
-
const browserOrigin = yield* resolvePublicOrigin(configuredPublicOrigin, validated);
|
|
82
|
-
const ownerToken = yield* issueSessionToken();
|
|
83
|
-
return OwnerSessionSecrets.of({
|
|
84
|
-
authorityOrigin: validated,
|
|
85
|
-
bootstrap: yield* Ref.make(undefined),
|
|
86
|
-
bootstrapAttemptState: yield* Ref.make({ failedAttempts: 0, inFlight: 0 }),
|
|
87
|
-
browserOrigin,
|
|
88
|
-
ownerToken
|
|
4
|
+
/** Build the session for a server bound at `authorityOrigin`, advertised at `configuredPublicOrigin`. */
|
|
5
|
+
export const makeOwnerSession = Effect.fn("AgentUsage.makeOwnerSession")(function* (authorityOrigin, configuredPublicOrigin) {
|
|
6
|
+
return yield* OwnerSession.make({
|
|
7
|
+
authorityOrigin,
|
|
8
|
+
browserOrigin: yield* OwnerSession.resolvePublicOrigin(configuredPublicOrigin, authorityOrigin),
|
|
9
|
+
cookieName: "agent_usage_owner",
|
|
10
|
+
freshUrlHint: "run `agent-usage login` for a fresh URL",
|
|
11
|
+
product: "agent-usage",
|
|
12
|
+
writes: "none"
|
|
89
13
|
});
|
|
90
14
|
});
|
|
91
15
|
/**
|
|
92
|
-
* Mints a fresh one-time code and returns the URL that carries it
|
|
93
|
-
*
|
|
94
|
-
* one and reopens an exchange that guesses had closed. Call it only once the server is listening.
|
|
95
|
-
*
|
|
96
|
-
* The code rides in the fragment, which browsers do not send to servers and proxies do not log,
|
|
97
|
-
* and the page strips it from the address bar once it has been spent.
|
|
16
|
+
* Mints a fresh one-time code and returns the URL that carries it. The startup link and every
|
|
17
|
+
* `agent-usage login` link come from here. Call it only once the server is listening.
|
|
98
18
|
*/
|
|
99
|
-
export const mintBootstrapUrl = Effect.
|
|
100
|
-
const token = yield* issuePairingCode();
|
|
101
|
-
const now = yield* Clock.currentTimeMillis;
|
|
102
|
-
const until = yield* expiresAt(now, BOOTSTRAP_LIFETIME_MILLIS);
|
|
103
|
-
yield* Ref.set(secrets.bootstrap, { token, expiresAt: until, available: true });
|
|
104
|
-
yield* Ref.set(secrets.bootstrapAttemptState, { failedAttempts: 0, inFlight: 0 });
|
|
105
|
-
return `${secrets.browserOrigin.replace(/\/+$/u, "")}/#bootstrap_token=${encodeURIComponent(Redacted.value(token))}`;
|
|
106
|
-
});
|
|
107
|
-
export const ownerSessionCookie = (secrets) => serializeCredentialCookie(secrets.ownerToken, {
|
|
108
|
-
httpOnly: true,
|
|
109
|
-
name: "agent_usage_owner",
|
|
110
|
-
path: "/api",
|
|
111
|
-
sameSite: "strict",
|
|
112
|
-
secure: false
|
|
113
|
-
});
|
|
114
|
-
const fetchSites = new Set(["cross-site", "none", "same-origin", "same-site"]);
|
|
115
|
-
export const authorizeOwnerRequest = Effect.fn("OwnerSession.authorizeRequest")(function* (request, secrets) {
|
|
116
|
-
if (credentialValuesEqual(request.credential, Redacted.value(secrets.ownerToken)) !== true) {
|
|
117
|
-
return yield* new UnauthorizedApiError({ message: "Missing or invalid owner session" });
|
|
118
|
-
}
|
|
119
|
-
const sameOrigin = request.origin !== undefined && request.origin === secrets.browserOrigin;
|
|
120
|
-
if (request.origin !== undefined && !sameOrigin) {
|
|
121
|
-
return yield* new ForbiddenApiError({ message: "Request origin does not match this agent-usage server" });
|
|
122
|
-
}
|
|
123
|
-
if (request.fetchSite !== undefined && !fetchSites.has(request.fetchSite)) {
|
|
124
|
-
return yield* new ForbiddenApiError({ message: "Request carries invalid browser site metadata" });
|
|
125
|
-
}
|
|
126
|
-
if (request.fetchSite === "cross-site" ||
|
|
127
|
-
(!sameOrigin && (request.fetchSite === "same-site" || request.fetchSite === "none"))) {
|
|
128
|
-
return yield* new ForbiddenApiError({ message: "Browser request is not from this agent-usage page" });
|
|
129
|
-
}
|
|
130
|
-
// An explicit client such as curl carries neither Origin nor Fetch Metadata and remains allowed:
|
|
131
|
-
// it still has to present the process-scoped session cookie.
|
|
132
|
-
if (!safeMethods.has(request.method.toUpperCase())) {
|
|
133
|
-
return yield* new ForbiddenApiError({ message: "This server only answers reads" });
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
const bearerToken = (authorization) => authorization !== undefined && authorization.startsWith("Bearer ")
|
|
137
|
-
? authorization.slice("Bearer ".length)
|
|
138
|
-
: undefined;
|
|
139
|
-
export const authorizeBootstrapRequest = Effect.fn("OwnerSession.authorizeBootstrap")(function* (request, secrets) {
|
|
140
|
-
if (request.origin !== secrets.browserOrigin) {
|
|
141
|
-
return yield* new ForbiddenApiError({ message: "Bootstrap origin does not match this agent-usage server" });
|
|
142
|
-
}
|
|
143
|
-
const supplied = bearerToken(request.authorization);
|
|
144
|
-
const current = yield* Ref.get(secrets.bootstrap);
|
|
145
|
-
// Counted before the code is even compared, so a burst of parallel guesses cannot all pass the
|
|
146
|
-
// check together and then be counted afterwards.
|
|
147
|
-
const admit = Ref.modify(secrets.bootstrapAttemptState, (state) => {
|
|
148
|
-
if (state.failedAttempts + state.inFlight >= MAX_BOOTSTRAP_FAILURES)
|
|
149
|
-
return ["unavailable", state];
|
|
150
|
-
const matches = supplied !== undefined && current !== undefined &&
|
|
151
|
-
credentialValuesEqual(supplied, Redacted.value(current.token)) === true;
|
|
152
|
-
if (!matches) {
|
|
153
|
-
return ["invalid", {
|
|
154
|
-
failedAttempts: Math.min(MAX_BOOTSTRAP_FAILURES, state.failedAttempts + 1),
|
|
155
|
-
inFlight: state.inFlight
|
|
156
|
-
}];
|
|
157
|
-
}
|
|
158
|
-
return ["accepted", { ...state, inFlight: state.inFlight + 1 }];
|
|
159
|
-
}).pipe(Effect.flatMap((admission) => admission === "unavailable"
|
|
160
|
-
? Effect.fail(new UnauthorizedApiError({ message: "Bootstrap is closed for this process" }))
|
|
161
|
-
: admission === "invalid"
|
|
162
|
-
? Effect.fail(new UnauthorizedApiError({ message: "Missing or invalid bootstrap token" }))
|
|
163
|
-
: Effect.void));
|
|
164
|
-
return yield* Effect.acquireUseRelease(admit, () => Effect.gen(function* () {
|
|
165
|
-
const now = yield* Clock.currentTimeMillis;
|
|
166
|
-
// Spent only if it is still the code that was compared: a newer mint replaced it otherwise.
|
|
167
|
-
const decision = yield* Ref.modify(secrets.bootstrap, (code) => {
|
|
168
|
-
if (code === undefined || current === undefined || code.token !== current.token) {
|
|
169
|
-
return ["consumed", code];
|
|
170
|
-
}
|
|
171
|
-
const next = decideOneTimeCredential({ consumedAt: code.available ? null : 0, expiresAt: code.expiresAt, revokedAt: null }, now);
|
|
172
|
-
return [next, next === "accepted" ? { ...code, available: false } : code];
|
|
173
|
-
});
|
|
174
|
-
if (decision === "accepted")
|
|
175
|
-
return;
|
|
176
|
-
const message = decision === "expired"
|
|
177
|
-
? "Bootstrap token has expired"
|
|
178
|
-
: decision === "consumed"
|
|
179
|
-
? "Bootstrap token has already been used"
|
|
180
|
-
: "Bootstrap token state is invalid";
|
|
181
|
-
return yield* new UnauthorizedApiError({ message });
|
|
182
|
-
}), () => Ref.update(secrets.bootstrapAttemptState, (state) => ({ ...state, inFlight: Math.max(0, state.inFlight - 1) })));
|
|
183
|
-
});
|
|
19
|
+
export const mintBootstrapUrl = (session) => Effect.map(session.mintBootstrapCode, (code) => OwnerSession.bootstrapUrl(session.browserOrigin, code));
|
|
184
20
|
export const ownerSessionAuthLayer = Layer.effect(OwnerSessionAuth, Effect.gen(function* () {
|
|
185
|
-
const
|
|
21
|
+
const session = yield* OwnerSession.OwnerSession;
|
|
186
22
|
return OwnerSessionAuth.of({
|
|
187
|
-
ownerCookie: Effect.fn("
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
method: request.method,
|
|
193
|
-
origin: request.headers.origin
|
|
194
|
-
}, secrets);
|
|
23
|
+
ownerCookie: Effect.fn("AgentUsage.ownerCookie")(function* (httpEffect, { credential }) {
|
|
24
|
+
yield* session.authorizeHttp(Redacted.value(credential)).pipe(Effect.catchTags({
|
|
25
|
+
OwnerSessionForbiddenError: ({ message }) => Effect.fail(new ForbiddenApiError({ message })),
|
|
26
|
+
OwnerSessionUnauthorizedError: ({ message }) => Effect.fail(new UnauthorizedApiError({ message }))
|
|
27
|
+
}));
|
|
195
28
|
return yield* httpEffect;
|
|
196
29
|
})
|
|
197
30
|
});
|
|
198
31
|
}));
|
|
199
|
-
/** The one route outside the authenticated API: spending the printed code for a session. */
|
|
200
|
-
export const OwnerSessionBootstrapRouter = HttpRouter.use((router) => router.add("POST", "/auth/bootstrap", Effect.gen(function* () {
|
|
201
|
-
const secrets = yield* OwnerSessionSecrets;
|
|
202
|
-
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
203
|
-
const result = yield* Effect.result(authorizeBootstrapRequest({ authorization: request.headers.authorization, origin: request.headers.origin }, secrets));
|
|
204
|
-
if (result._tag === "Failure") {
|
|
205
|
-
return HttpServerResponse.text(result.failure.message, {
|
|
206
|
-
status: result.failure._tag === "UnauthorizedApiError" ? 401 : 403
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
return yield* HttpServerResponse.json({}, {
|
|
210
|
-
headers: { "cache-control": "no-store", "set-cookie": ownerSessionCookie(secrets) },
|
|
211
|
-
status: 200
|
|
212
|
-
});
|
|
213
|
-
})));
|
|
214
32
|
//# sourceMappingURL=OwnerSession.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OwnerSession.js","sourceRoot":"","sources":["../../src/server/OwnerSession.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OwnerSession.js","sourceRoot":"","sources":["../../src/server/OwnerSession.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,YAAY,MAAM,sCAAsC,CAAA;AACpE,OAAO,EAAe,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAC7D,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAEpF,yGAAyG;AACzG,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,EAAE,CAAC,6BAA6B,CAAC,CACtE,QAAQ,CAAC,EACP,eAAuB,EACvB,sBAA+B;IAM/B,OAAO,KAAK,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC;QAC9B,eAAe;QACf,aAAa,EAAE,KAAK,CAAC,CAAC,YAAY,CAAC,mBAAmB,CAAC,sBAAsB,EAAE,eAAe,CAAC;QAC/F,UAAU,EAAE,mBAAmB;QAC/B,YAAY,EAAE,yCAAyC;QACvD,OAAO,EAAE,aAAa;QACtB,MAAM,EAAE,MAAM;KACf,CAAC,CAAA;AACJ,CAAC,CACF,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAyC,EAAE,EAAE,CAC5E,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAA;AAEzG,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC,MAAM,CAC/C,gBAAgB,EAChB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,YAAY,CAAC,YAAY,CAAA;IAChD,OAAO,gBAAgB,CAAC,EAAE,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC,EAAE,CAAC,wBAAwB,CAAC,CAAC,QAAQ,CAAC,EAAC,UAAU,EAAE,EAAE,UAAU,EAAE;YACnF,KAAK,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAC3D,MAAM,CAAC,SAAS,CAAC;gBACf,0BAA0B,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC5F,6BAA6B,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;aACnG,CAAC,CACH,CAAA;YACD,OAAO,KAAK,CAAC,CAAC,UAAU,CAAA;QAC1B,CAAC,CAAC;KACH,CAAC,CAAA;AACJ,CAAC,CAAC,CACH,CAAA"}
|
package/dist/server/Server.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import * as OwnerSession from "@knpkv/browser-pairing/owner-session";
|
|
1
2
|
import { Config, Deferred, Layer, Schema } from "effect";
|
|
2
3
|
import type { AgentUsageConfig } from "./Config.js";
|
|
3
|
-
import { type OwnerSessionSecretsContract } from "./OwnerSession.js";
|
|
4
4
|
export interface AgentUsageServerOptions {
|
|
5
5
|
readonly config: AgentUsageConfig;
|
|
6
6
|
readonly hostname?: string;
|
|
7
7
|
readonly port: number;
|
|
8
8
|
readonly ready?: Deferred.Deferred<string>;
|
|
9
|
-
readonly security:
|
|
9
|
+
readonly security: OwnerSession.OwnerSessionService;
|
|
10
10
|
}
|
|
11
|
-
export declare const makeServer: (options: AgentUsageServerOptions) => Layer.Layer<never, import("@knpkv/browser-pairing").BrowserPairingError | Schema.SchemaError | import("effect/http/HttpServerError").ServeError | import("./ControlSocket.js").ServerAlreadyRunning | import("./ControlSocket.js").SocketPathUnsafe | import("./ControlSocket.js").SocketRefused | import("../core/Store.js").StoreError,
|
|
11
|
+
export declare const makeServer: (options: AgentUsageServerOptions) => Layer.Layer<never, import("@knpkv/browser-pairing").BrowserPairingError | Schema.SchemaError | import("effect/http/HttpServerError").ServeError | import("./ControlSocket.js").ServerAlreadyRunning | import("./ControlSocket.js").SocketPathUnsafe | import("./ControlSocket.js").SocketRefused | import("../core/Store.js").StoreError, never>;
|
|
12
12
|
/** The port to bind: next to jcf-web's 3111. */
|
|
13
13
|
export declare const Port: Config.Config<number>;
|
|
14
14
|
/** Set by `pnpm dev` so the printed URL points at the Vite dev server that proxies here. */
|
package/dist/server/Server.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* @module
|
|
12
12
|
*/
|
|
13
13
|
import { NodeHttpServer, NodeServices } from "@effect/platform-node";
|
|
14
|
+
import * as OwnerSession from "@knpkv/browser-pairing/owner-session";
|
|
14
15
|
import { Config, Deferred, Effect, Layer, Schema } from "effect";
|
|
15
16
|
import { Etag, FetchHttpClient, HttpPlatform, HttpRouter } from "effect/http";
|
|
16
17
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
@@ -20,7 +21,7 @@ import { databaseLayer } from "../core/Database.js";
|
|
|
20
21
|
import { acliTicketSearch } from "../core/Tickets.js";
|
|
21
22
|
import { controlSocket } from "./ControlSocket.js";
|
|
22
23
|
import { application } from "./HttpApplication.js";
|
|
23
|
-
import { mintBootstrapUrl
|
|
24
|
+
import { mintBootstrapUrl } from "./OwnerSession.js";
|
|
24
25
|
import { backgroundLayer, RuntimeState } from "./Runtime.js";
|
|
25
26
|
const HttpPlatformLive = HttpPlatform.layer.pipe(Layer.provide(NodeServices.layer));
|
|
26
27
|
const LoopbackHostname = Schema.Literals(["127.0.0.1", "localhost", "::1"]);
|
|
@@ -41,7 +42,7 @@ export const makeServer = (options) => Layer.unwrap(Effect.gen(function* () {
|
|
|
41
42
|
const services = Layer.mergeAll(store, RuntimeState.layer(options.config.roots.machine, options.config.projects));
|
|
42
43
|
// Bound only once the control layer holds the store's lock.
|
|
43
44
|
const listener = NodeHttpServer.layerServer(createServer, { host: hostname, port: options.port }).pipe(Layer.provide(control));
|
|
44
|
-
return Layer.mergeAll(HttpRouter.serve(application), background(options.config)).pipe(Layer.provide(services), Layer.provide(listener), Layer.provide(Etag.layer), Layer.provide(HttpPlatformLive), Layer.provide(FetchHttpClient.layer), Layer.provide(NodeServices.layer), Layer.provide(Layer.succeed(
|
|
45
|
+
return Layer.mergeAll(HttpRouter.serve(application), background(options.config)).pipe(Layer.provide(services), Layer.provide(listener), Layer.provide(Etag.layer), Layer.provide(HttpPlatformLive), Layer.provide(FetchHttpClient.layer), Layer.provide(NodeServices.layer), Layer.provide(Layer.succeed(OwnerSession.OwnerSession, options.security)), Layer.tap(() =>
|
|
45
46
|
// The startup link: minted only once the server is listening, handed to whoever prints it.
|
|
46
47
|
mintBootstrapUrl(options.security).pipe(Effect.tap(() => Deferred.succeed(listening, undefined)), Effect.flatMap((url) => options.ready === undefined ? Effect.void : Deferred.succeed(options.ready, url)))));
|
|
47
48
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Server.js","sourceRoot":"","sources":["../../src/server/Server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAChE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7E,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAErD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAE,
|
|
1
|
+
{"version":3,"file":"Server.js","sourceRoot":"","sources":["../../src/server/Server.ts"],"names":[],"mappings":"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"}
|