@arcote.tech/arc-host 0.7.5 → 0.7.7
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/index.js +208 -105
- package/dist/index.js.map +8 -7
- package/dist/src/context-handler.d.ts +6 -2
- package/dist/src/context-handler.d.ts.map +1 -1
- package/dist/src/create-server.d.ts +6 -0
- package/dist/src/create-server.d.ts.map +1 -1
- package/package.json +9 -3
- package/src/context-handler.ts +53 -24
- package/src/create-server.ts +120 -48
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { type ArcContextAny, type ArcEventAny, type DatabaseAdapter, LocalEventPublisher, MasterDataStorage, Model } from "@arcote.tech/arc";
|
|
2
|
+
import type { ArcTelemetry } from "@arcote.tech/arc-otel";
|
|
2
3
|
import type { EventAuthContext, SyncableEvent, TokenPayload } from "./types";
|
|
3
4
|
/**
|
|
4
5
|
* Handles a single context on the host
|
|
5
6
|
*/
|
|
6
7
|
export declare class ContextHandler {
|
|
7
8
|
readonly context: ArcContextAny;
|
|
9
|
+
private readonly telemetry?;
|
|
8
10
|
private model;
|
|
9
11
|
private dataStorage;
|
|
10
12
|
private eventPublisher;
|
|
11
13
|
private eventDefinitions;
|
|
12
14
|
private hostEventIdCounter;
|
|
13
15
|
private initialized;
|
|
14
|
-
constructor(context: ArcContextAny, dbAdapter: Promise<DatabaseAdapter
|
|
16
|
+
constructor(context: ArcContextAny, dbAdapter: Promise<DatabaseAdapter>, telemetry?: ArcTelemetry | undefined);
|
|
15
17
|
/**
|
|
16
18
|
* Initialize the context handler and run seed data if needed.
|
|
17
19
|
*/
|
|
@@ -22,7 +24,9 @@ export declare class ContextHandler {
|
|
|
22
24
|
*/
|
|
23
25
|
private runSeeds;
|
|
24
26
|
/**
|
|
25
|
-
* Execute a command
|
|
27
|
+
* Execute a command. When `telemetry` is wired, the entire dispatch is
|
|
28
|
+
* traced as `command.<name>` with RPC semantic conventions, parented to
|
|
29
|
+
* whatever context is active (HTTP request span, WS message span, etc.).
|
|
26
30
|
*/
|
|
27
31
|
executeCommand(commandName: string, params: any, rawToken: string | null): Promise<any>;
|
|
28
32
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-handler.d.ts","sourceRoot":"","sources":["../../src/context-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,EAGN,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"context-handler.d.ts","sourceRoot":"","sources":["../../src/context-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,EAGN,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE7E;;GAEG;AACH,qBAAa,cAAc;aASP,OAAO,EAAE,aAAa;IAEtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAV7B,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,cAAc,CAAsB;IAC5C,OAAO,CAAC,gBAAgB,CAAkC;IAC1D,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,WAAW,CAAS;gBAGV,OAAO,EAAE,aAAa,EACtC,SAAS,EAAE,OAAO,CAAC,eAAe,CAAC,EAClB,SAAS,CAAC,EAAE,YAAY,YAAA;IAqB3C;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B;;;OAGG;YACW,QAAQ;IA4BtB;;;;OAIG;IACG,cAAc,CAClB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,GAAG,EACX,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,OAAO,CAAC,GAAG,CAAC;IAuDf;;OAEG;IACG,aAAa,CACjB,MAAM,EAAE,KAAK,CAAC;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,GAAG,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;KACvC,CAAC,EACF,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,YAAY,GAAG,IAAI,GACzB,OAAO,CAAC,aAAa,EAAE,CAAC;IA8C3B;;OAEG;IACG,cAAc,CAClB,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,KAAK,EAAE,YAAY,GAAG,IAAI,GACzB,OAAO,CAAC,aAAa,EAAE,CAAC;IAkD3B;;OAEG;IACH,QAAQ,IAAI,KAAK,CAAC,aAAa,CAAC;IAIhC;;OAEG;IACH,cAAc,IAAI,iBAAiB;IAInC;;OAEG;IACH,mBAAmB,IAAI,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC;IAI/C;;OAEG;IACH,iBAAiB,IAAI,mBAAmB;CAGzC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ArcContextAny, DatabaseAdapter } from "@arcote.tech/arc";
|
|
2
|
+
import type { ArcTelemetry } from "@arcote.tech/arc-otel";
|
|
2
3
|
import type { Server } from "bun";
|
|
3
4
|
import { ConnectionManager } from "./connection-manager";
|
|
4
5
|
import { ContextHandler } from "./context-handler";
|
|
@@ -16,6 +17,11 @@ export interface ArcServerConfig {
|
|
|
16
17
|
jwtSecret?: string;
|
|
17
18
|
/** Extra callback when a WS client disconnects (e.g. to clean up platform state) */
|
|
18
19
|
onWsClose?: (clientId: string) => void;
|
|
20
|
+
/** Optional OpenTelemetry instance. When provided, the HTTP fetch handler
|
|
21
|
+
* and WS message dispatch are wrapped in spans, with parent context
|
|
22
|
+
* extracted from `traceparent` (header / message field). All nested
|
|
23
|
+
* handler code automatically attaches to the active span. */
|
|
24
|
+
telemetry?: ArcTelemetry;
|
|
19
25
|
}
|
|
20
26
|
export interface ArcServer {
|
|
21
27
|
server: Server<WebSocketData>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-server.d.ts","sourceRoot":"","sources":["../../src/create-server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAElC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAOjD,OAAO,KAAK,EACV,cAAc,EAGd,YAAY,EACb,MAAM,oBAAoB,CAAC;AAG5B,KAAK,aAAa,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,aAAa,CAAC;IACvB,gBAAgB,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACzD,YAAY,CAAC,EAAE,cAAc,EAAE,CAAC;IAChC,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"create-server.d.ts","sourceRoot":"","sources":["../../src/create-server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAQ,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAElC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAOjD,OAAO,KAAK,EACV,cAAc,EAGd,YAAY,EACb,MAAM,oBAAoB,CAAC;AAG5B,KAAK,aAAa,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,aAAa,CAAC;IACvB,gBAAgB,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACzD,YAAY,CAAC,EAAE,cAAc,EAAE,CAAC;IAChC,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC;;;kEAG8D;IAC9D,SAAS,CAAC,EAAE,YAAY,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;IAC9B,cAAc,EAAE,cAAc,CAAC;IAC/B,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,aAAa,EAAE,aAAa,CAAC;IAC7B,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,SAAS,CAAC,CA0PpB"}
|
package/package.json
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"version": "0.7.
|
|
7
|
+
"version": "0.7.7",
|
|
8
8
|
"private": false,
|
|
9
9
|
"author": "Przemysław Krasiński [arcote.tech]",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@arcote.tech/arc-adapter-db-sqlite": "^0.7.
|
|
11
|
+
"@arcote.tech/arc-adapter-db-sqlite": "^0.7.7",
|
|
12
12
|
"croner": "^9.0.0",
|
|
13
13
|
"jsonwebtoken": "^9.0.2"
|
|
14
14
|
},
|
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
"@types/bun": "^1.2.0"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@arcote.tech/arc": "^0.7.
|
|
27
|
+
"@arcote.tech/arc": "^0.7.7",
|
|
28
|
+
"@arcote.tech/arc-otel": "^0.7.7"
|
|
29
|
+
},
|
|
30
|
+
"peerDependenciesMeta": {
|
|
31
|
+
"@arcote.tech/arc-otel": {
|
|
32
|
+
"optional": true
|
|
33
|
+
}
|
|
28
34
|
}
|
|
29
35
|
}
|
package/src/context-handler.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
ScopedModel,
|
|
9
9
|
mutationExecutor,
|
|
10
10
|
} from "@arcote.tech/arc";
|
|
11
|
+
import type { ArcTelemetry } from "@arcote.tech/arc-otel";
|
|
11
12
|
import { canTokenEmitEvent, filterEventsForToken } from "./event-auth";
|
|
12
13
|
import type { EventAuthContext, SyncableEvent, TokenPayload } from "./types";
|
|
13
14
|
|
|
@@ -25,6 +26,7 @@ export class ContextHandler {
|
|
|
25
26
|
constructor(
|
|
26
27
|
public readonly context: ArcContextAny,
|
|
27
28
|
dbAdapter: Promise<DatabaseAdapter>,
|
|
29
|
+
private readonly telemetry?: ArcTelemetry,
|
|
28
30
|
) {
|
|
29
31
|
this.dataStorage = new MasterDataStorage(dbAdapter);
|
|
30
32
|
this.eventPublisher = new LocalEventPublisher(this.dataStorage);
|
|
@@ -88,39 +90,66 @@ export class ContextHandler {
|
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
/**
|
|
91
|
-
* Execute a command
|
|
93
|
+
* Execute a command. When `telemetry` is wired, the entire dispatch is
|
|
94
|
+
* traced as `command.<name>` with RPC semantic conventions, parented to
|
|
95
|
+
* whatever context is active (HTTP request span, WS message span, etc.).
|
|
92
96
|
*/
|
|
93
97
|
async executeCommand(
|
|
94
98
|
commandName: string,
|
|
95
99
|
params: any,
|
|
96
100
|
rawToken: string | null,
|
|
97
101
|
): Promise<any> {
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
const includePayloads = this.telemetry?.shouldIncludePayloads() ?? false;
|
|
103
|
+
const baseAttrs = {
|
|
104
|
+
"rpc.system": "arc",
|
|
105
|
+
"rpc.method": commandName,
|
|
106
|
+
"arc.command.name": commandName,
|
|
107
|
+
"arc.command.params_size": params ? JSON.stringify(params).length : 0,
|
|
108
|
+
...(includePayloads ? { "arc.command.params": params } : {}),
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const runCommand = async (): Promise<any> => {
|
|
112
|
+
const scoped = new ScopedModel(this.model, "request");
|
|
113
|
+
if (rawToken) scoped.setToken(rawToken);
|
|
114
|
+
|
|
115
|
+
const mutations = mutationExecutor(scoped);
|
|
116
|
+
|
|
117
|
+
let command: any;
|
|
118
|
+
if (commandName.includes(".")) {
|
|
119
|
+
const [elementName, methodName] = commandName.split(".");
|
|
120
|
+
const element = (mutations as any)[elementName];
|
|
121
|
+
command = element?.[methodName];
|
|
122
|
+
} else {
|
|
123
|
+
command = (mutations as any)[commandName];
|
|
124
|
+
}
|
|
113
125
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
126
|
+
if (!command) {
|
|
127
|
+
throw new Error(`Command '${commandName}' not found`);
|
|
128
|
+
}
|
|
117
129
|
|
|
130
|
+
try {
|
|
131
|
+
return await command(params);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error(`[ARC] Command '${commandName}' failed:`, error);
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
if (!this.telemetry) return runCommand();
|
|
139
|
+
const start = Date.now();
|
|
118
140
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
141
|
+
return await this.telemetry.startSpan(
|
|
142
|
+
`command.${commandName}`,
|
|
143
|
+
runCommand,
|
|
144
|
+
{ attributes: baseAttrs },
|
|
145
|
+
);
|
|
146
|
+
} finally {
|
|
147
|
+
this.telemetry.measureSince("arc.command.duration_ms", start, {
|
|
148
|
+
"arc.command.name": commandName,
|
|
149
|
+
});
|
|
150
|
+
this.telemetry.incrementCounter("arc.commands.total", 1, {
|
|
151
|
+
"arc.command.name": commandName,
|
|
152
|
+
});
|
|
124
153
|
}
|
|
125
154
|
}
|
|
126
155
|
|
package/src/create-server.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ArcContextAny, DatabaseAdapter } from "@arcote.tech/arc";
|
|
2
|
+
import type { ArcTelemetry, Span } from "@arcote.tech/arc-otel";
|
|
2
3
|
import type { Server } from "bun";
|
|
3
4
|
import jwt from "jsonwebtoken";
|
|
4
5
|
import { ConnectionManager } from "./connection-manager";
|
|
@@ -29,6 +30,11 @@ export interface ArcServerConfig {
|
|
|
29
30
|
jwtSecret?: string;
|
|
30
31
|
/** Extra callback when a WS client disconnects (e.g. to clean up platform state) */
|
|
31
32
|
onWsClose?: (clientId: string) => void;
|
|
33
|
+
/** Optional OpenTelemetry instance. When provided, the HTTP fetch handler
|
|
34
|
+
* and WS message dispatch are wrapped in spans, with parent context
|
|
35
|
+
* extracted from `traceparent` (header / message field). All nested
|
|
36
|
+
* handler code automatically attaches to the active span. */
|
|
37
|
+
telemetry?: ArcTelemetry;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
export interface ArcServer {
|
|
@@ -55,9 +61,21 @@ export async function createArcServer(
|
|
|
55
61
|
"arc-host-secret-change-in-production";
|
|
56
62
|
const port = config.port || 5005;
|
|
57
63
|
|
|
58
|
-
// Init context handler
|
|
64
|
+
// Init context handler — telemetry (if any) flows through so executeCommand
|
|
65
|
+
// emits `command.<name>` spans parented to the active HTTP/WS span.
|
|
66
|
+
// DB adapter wrapping (per-transaction spans) is the caller's
|
|
67
|
+
// responsibility: pass an already-wrapped `dbAdapterFactory` if
|
|
68
|
+
// db spans are wanted. We don't `import()` arc-otel from here because
|
|
69
|
+
// doing so bundles its OTel SDK transitively into arc-host's dist,
|
|
70
|
+
// which then duplicates the @opentelemetry/api singleton when the CLI
|
|
71
|
+
// bundles arc-otel directly — leading to two global tracer providers
|
|
72
|
+
// and silent span loss.
|
|
59
73
|
const dbAdapter = config.dbAdapterFactory(config.context);
|
|
60
|
-
const contextHandler = new ContextHandler(
|
|
74
|
+
const contextHandler = new ContextHandler(
|
|
75
|
+
config.context,
|
|
76
|
+
dbAdapter,
|
|
77
|
+
config.telemetry,
|
|
78
|
+
);
|
|
61
79
|
await contextHandler.init();
|
|
62
80
|
|
|
63
81
|
// Start cron scheduler
|
|
@@ -136,58 +154,74 @@ export async function createArcServer(
|
|
|
136
154
|
|
|
137
155
|
async fetch(req, server) {
|
|
138
156
|
const url = new URL(req.url);
|
|
139
|
-
|
|
140
|
-
// Build CORS headers with request origin
|
|
141
157
|
const corsHeaders = buildCorsHeaders(req);
|
|
142
158
|
|
|
143
|
-
// CORS preflight
|
|
144
159
|
if (req.method === "OPTIONS") {
|
|
145
160
|
return new Response(null, { headers: corsHeaders });
|
|
146
161
|
}
|
|
147
162
|
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
// Inner request body — extracted so we can wrap it in an HTTP span
|
|
164
|
+
// when telemetry is enabled. The wrap MUST set the active context
|
|
165
|
+
// (via runWithExtractedContext + startSpan) so nested handlers'
|
|
166
|
+
// spans attach to this one as parent.
|
|
167
|
+
const handleRequest = async (span: Span | undefined): Promise<Response> => {
|
|
168
|
+
const authHeader = req.headers.get("Authorization");
|
|
169
|
+
let rawToken =
|
|
170
|
+
authHeader?.replace("Bearer ", "") ||
|
|
171
|
+
url.searchParams.get("token");
|
|
172
|
+
|
|
173
|
+
if (!rawToken) {
|
|
174
|
+
const cookieHeader = req.headers.get("Cookie");
|
|
175
|
+
if (cookieHeader) {
|
|
176
|
+
const match = cookieHeader.match(/arc_token=([^;]+)/);
|
|
177
|
+
if (match) rawToken = decodeURIComponent(match[1]);
|
|
178
|
+
}
|
|
159
179
|
}
|
|
160
|
-
|
|
180
|
+
const tokenPayload = rawToken ? verifyToken(rawToken) : null;
|
|
161
181
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
headers: corsHeaders,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
182
|
+
if (
|
|
183
|
+
url.pathname === "/ws" &&
|
|
184
|
+
req.headers.get("Upgrade") === "websocket"
|
|
185
|
+
) {
|
|
186
|
+
if (server.upgrade(req, { data: { clientId: "" } })) return undefined as unknown as Response;
|
|
187
|
+
return new Response("WebSocket upgrade failed", {
|
|
188
|
+
status: 500,
|
|
189
|
+
headers: corsHeaders,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
175
192
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
193
|
+
const reqCtx: ArcRequestContext = { rawToken, tokenPayload, corsHeaders };
|
|
194
|
+
for (const handler of httpHandlers) {
|
|
195
|
+
const response = await handler(req, url, reqCtx);
|
|
196
|
+
if (response) {
|
|
197
|
+
span?.setAttribute("http.response.status_code", response.status);
|
|
198
|
+
return response;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
span?.setAttribute("http.response.status_code", 404);
|
|
202
|
+
return new Response("Not Found", { status: 404, headers: corsHeaders });
|
|
181
203
|
};
|
|
182
|
-
for (const handler of httpHandlers) {
|
|
183
|
-
const response = await handler(req, url, reqCtx);
|
|
184
|
-
if (response) return response;
|
|
185
|
-
}
|
|
186
204
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
205
|
+
const telemetry = config.telemetry;
|
|
206
|
+
if (!telemetry) return handleRequest(undefined);
|
|
207
|
+
|
|
208
|
+
return telemetry.runWithExtractedContext(req.headers, () =>
|
|
209
|
+
telemetry.startSpan(
|
|
210
|
+
`${req.method} ${url.pathname}`,
|
|
211
|
+
(span) => handleRequest(span),
|
|
212
|
+
{
|
|
213
|
+
kind: 2 /* SpanKind.SERVER */,
|
|
214
|
+
attributes: {
|
|
215
|
+
"http.request.method": req.method,
|
|
216
|
+
"http.route": url.pathname,
|
|
217
|
+
"url.scheme": url.protocol.replace(":", ""),
|
|
218
|
+
"url.path": url.pathname,
|
|
219
|
+
"server.address": url.host,
|
|
220
|
+
"user_agent.original": req.headers.get("user-agent") ?? undefined,
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
),
|
|
224
|
+
);
|
|
191
225
|
},
|
|
192
226
|
|
|
193
227
|
websocket: {
|
|
@@ -198,15 +232,53 @@ export async function createArcServer(
|
|
|
198
232
|
const client = connectionManager.getClientByWs(ws as any);
|
|
199
233
|
if (!client) return;
|
|
200
234
|
|
|
235
|
+
let message: any;
|
|
201
236
|
try {
|
|
202
|
-
|
|
203
|
-
for (const handler of wsHandlers) {
|
|
204
|
-
const handled = await handler(client, message, wsCtx);
|
|
205
|
-
if (handled) break;
|
|
206
|
-
}
|
|
237
|
+
message = JSON.parse(messageStr as string);
|
|
207
238
|
} catch (error) {
|
|
208
239
|
console.error("Failed to parse WS message:", error);
|
|
240
|
+
return;
|
|
209
241
|
}
|
|
242
|
+
|
|
243
|
+
const dispatch = async (): Promise<void> => {
|
|
244
|
+
try {
|
|
245
|
+
for (const handler of wsHandlers) {
|
|
246
|
+
const handled = await handler(client, message, wsCtx);
|
|
247
|
+
if (handled) break;
|
|
248
|
+
}
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error("WS handler error:", error);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const telemetry = config.telemetry;
|
|
255
|
+
if (!telemetry) {
|
|
256
|
+
await dispatch();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Parent context can travel inside the message under `traceparent`
|
|
261
|
+
// (and optionally `tracestate`) so distributed traces from the
|
|
262
|
+
// browser propagate over the persistent WS connection.
|
|
263
|
+
const carrier: Record<string, string> = {};
|
|
264
|
+
if (typeof message?.traceparent === "string") carrier.traceparent = message.traceparent;
|
|
265
|
+
if (typeof message?.tracestate === "string") carrier.tracestate = message.tracestate;
|
|
266
|
+
|
|
267
|
+
await telemetry.runWithExtractedContext(carrier, () =>
|
|
268
|
+
telemetry.startSpan(
|
|
269
|
+
`ws.${message?.type ?? "message"}`,
|
|
270
|
+
() => dispatch(),
|
|
271
|
+
{
|
|
272
|
+
kind: 2 /* SpanKind.SERVER */,
|
|
273
|
+
attributes: {
|
|
274
|
+
"messaging.system": "arc-ws",
|
|
275
|
+
"messaging.operation": "process",
|
|
276
|
+
"messaging.message.type": message?.type,
|
|
277
|
+
"arc.ws.client_id": client.id,
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
),
|
|
281
|
+
);
|
|
210
282
|
},
|
|
211
283
|
close(ws) {
|
|
212
284
|
const client = connectionManager.getClientByWs(ws as any);
|