@arcote.tech/arc-host 0.7.4 → 0.7.6
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 +2404 -268
- package/dist/index.js.map +64 -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 +126 -49
|
@@ -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,EACV,YAAY,EAEb,MAAM,uBAAuB,CAAC;AAC/B,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,CA4PpB"}
|
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.6",
|
|
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.6",
|
|
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.6",
|
|
28
|
+
"@arcote.tech/arc-otel": "^0.7.6"
|
|
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,8 @@
|
|
|
1
1
|
import type { ArcContextAny, DatabaseAdapter } from "@arcote.tech/arc";
|
|
2
|
+
import type {
|
|
3
|
+
ArcTelemetry,
|
|
4
|
+
Span,
|
|
5
|
+
} from "@arcote.tech/arc-otel";
|
|
2
6
|
import type { Server } from "bun";
|
|
3
7
|
import jwt from "jsonwebtoken";
|
|
4
8
|
import { ConnectionManager } from "./connection-manager";
|
|
@@ -29,6 +33,11 @@ export interface ArcServerConfig {
|
|
|
29
33
|
jwtSecret?: string;
|
|
30
34
|
/** Extra callback when a WS client disconnects (e.g. to clean up platform state) */
|
|
31
35
|
onWsClose?: (clientId: string) => void;
|
|
36
|
+
/** Optional OpenTelemetry instance. When provided, the HTTP fetch handler
|
|
37
|
+
* and WS message dispatch are wrapped in spans, with parent context
|
|
38
|
+
* extracted from `traceparent` (header / message field). All nested
|
|
39
|
+
* handler code automatically attaches to the active span. */
|
|
40
|
+
telemetry?: ArcTelemetry;
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
export interface ArcServer {
|
|
@@ -55,9 +64,23 @@ export async function createArcServer(
|
|
|
55
64
|
"arc-host-secret-change-in-production";
|
|
56
65
|
const port = config.port || 5005;
|
|
57
66
|
|
|
58
|
-
// Init context handler
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
// Init context handler — telemetry (if any) flows through so executeCommand
|
|
68
|
+
// emits `command.<name>` spans parented to the active HTTP/WS span. The
|
|
69
|
+
// adapter is wrapped post-await so every transaction's find/set/remove/
|
|
70
|
+
// commit lands as a child span automatically.
|
|
71
|
+
const rawDbAdapter = config.dbAdapterFactory(config.context);
|
|
72
|
+
const dbAdapter = config.telemetry
|
|
73
|
+
? rawDbAdapter.then(async (a) => {
|
|
74
|
+
const { wrapDbAdapter } = await import("@arcote.tech/arc-otel");
|
|
75
|
+
const dbSystem = process.env.DATABASE_URL ? "postgresql" : "sqlite";
|
|
76
|
+
return wrapDbAdapter(a, config.telemetry, dbSystem);
|
|
77
|
+
})
|
|
78
|
+
: rawDbAdapter;
|
|
79
|
+
const contextHandler = new ContextHandler(
|
|
80
|
+
config.context,
|
|
81
|
+
dbAdapter,
|
|
82
|
+
config.telemetry,
|
|
83
|
+
);
|
|
61
84
|
await contextHandler.init();
|
|
62
85
|
|
|
63
86
|
// Start cron scheduler
|
|
@@ -136,58 +159,74 @@ export async function createArcServer(
|
|
|
136
159
|
|
|
137
160
|
async fetch(req, server) {
|
|
138
161
|
const url = new URL(req.url);
|
|
139
|
-
|
|
140
|
-
// Build CORS headers with request origin
|
|
141
162
|
const corsHeaders = buildCorsHeaders(req);
|
|
142
163
|
|
|
143
|
-
// CORS preflight
|
|
144
164
|
if (req.method === "OPTIONS") {
|
|
145
165
|
return new Response(null, { headers: corsHeaders });
|
|
146
166
|
}
|
|
147
167
|
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
// Inner request body — extracted so we can wrap it in an HTTP span
|
|
169
|
+
// when telemetry is enabled. The wrap MUST set the active context
|
|
170
|
+
// (via runWithExtractedContext + startSpan) so nested handlers'
|
|
171
|
+
// spans attach to this one as parent.
|
|
172
|
+
const handleRequest = async (span: Span | undefined): Promise<Response> => {
|
|
173
|
+
const authHeader = req.headers.get("Authorization");
|
|
174
|
+
let rawToken =
|
|
175
|
+
authHeader?.replace("Bearer ", "") ||
|
|
176
|
+
url.searchParams.get("token");
|
|
177
|
+
|
|
178
|
+
if (!rawToken) {
|
|
179
|
+
const cookieHeader = req.headers.get("Cookie");
|
|
180
|
+
if (cookieHeader) {
|
|
181
|
+
const match = cookieHeader.match(/arc_token=([^;]+)/);
|
|
182
|
+
if (match) rawToken = decodeURIComponent(match[1]);
|
|
183
|
+
}
|
|
159
184
|
}
|
|
160
|
-
|
|
185
|
+
const tokenPayload = rawToken ? verifyToken(rawToken) : null;
|
|
161
186
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
headers: corsHeaders,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
187
|
+
if (
|
|
188
|
+
url.pathname === "/ws" &&
|
|
189
|
+
req.headers.get("Upgrade") === "websocket"
|
|
190
|
+
) {
|
|
191
|
+
if (server.upgrade(req, { data: { clientId: "" } })) return undefined as unknown as Response;
|
|
192
|
+
return new Response("WebSocket upgrade failed", {
|
|
193
|
+
status: 500,
|
|
194
|
+
headers: corsHeaders,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
175
197
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
198
|
+
const reqCtx: ArcRequestContext = { rawToken, tokenPayload, corsHeaders };
|
|
199
|
+
for (const handler of httpHandlers) {
|
|
200
|
+
const response = await handler(req, url, reqCtx);
|
|
201
|
+
if (response) {
|
|
202
|
+
span?.setAttribute("http.response.status_code", response.status);
|
|
203
|
+
return response;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
span?.setAttribute("http.response.status_code", 404);
|
|
207
|
+
return new Response("Not Found", { status: 404, headers: corsHeaders });
|
|
181
208
|
};
|
|
182
|
-
for (const handler of httpHandlers) {
|
|
183
|
-
const response = await handler(req, url, reqCtx);
|
|
184
|
-
if (response) return response;
|
|
185
|
-
}
|
|
186
209
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
210
|
+
const telemetry = config.telemetry;
|
|
211
|
+
if (!telemetry) return handleRequest(undefined);
|
|
212
|
+
|
|
213
|
+
return telemetry.runWithExtractedContext(req.headers, () =>
|
|
214
|
+
telemetry.startSpan(
|
|
215
|
+
`${req.method} ${url.pathname}`,
|
|
216
|
+
(span) => handleRequest(span),
|
|
217
|
+
{
|
|
218
|
+
kind: 2 /* SpanKind.SERVER */,
|
|
219
|
+
attributes: {
|
|
220
|
+
"http.request.method": req.method,
|
|
221
|
+
"http.route": url.pathname,
|
|
222
|
+
"url.scheme": url.protocol.replace(":", ""),
|
|
223
|
+
"url.path": url.pathname,
|
|
224
|
+
"server.address": url.host,
|
|
225
|
+
"user_agent.original": req.headers.get("user-agent") ?? undefined,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
),
|
|
229
|
+
);
|
|
191
230
|
},
|
|
192
231
|
|
|
193
232
|
websocket: {
|
|
@@ -198,15 +237,53 @@ export async function createArcServer(
|
|
|
198
237
|
const client = connectionManager.getClientByWs(ws as any);
|
|
199
238
|
if (!client) return;
|
|
200
239
|
|
|
240
|
+
let message: any;
|
|
201
241
|
try {
|
|
202
|
-
|
|
203
|
-
for (const handler of wsHandlers) {
|
|
204
|
-
const handled = await handler(client, message, wsCtx);
|
|
205
|
-
if (handled) break;
|
|
206
|
-
}
|
|
242
|
+
message = JSON.parse(messageStr as string);
|
|
207
243
|
} catch (error) {
|
|
208
244
|
console.error("Failed to parse WS message:", error);
|
|
245
|
+
return;
|
|
209
246
|
}
|
|
247
|
+
|
|
248
|
+
const dispatch = async (): Promise<void> => {
|
|
249
|
+
try {
|
|
250
|
+
for (const handler of wsHandlers) {
|
|
251
|
+
const handled = await handler(client, message, wsCtx);
|
|
252
|
+
if (handled) break;
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.error("WS handler error:", error);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const telemetry = config.telemetry;
|
|
260
|
+
if (!telemetry) {
|
|
261
|
+
await dispatch();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Parent context can travel inside the message under `traceparent`
|
|
266
|
+
// (and optionally `tracestate`) so distributed traces from the
|
|
267
|
+
// browser propagate over the persistent WS connection.
|
|
268
|
+
const carrier: Record<string, string> = {};
|
|
269
|
+
if (typeof message?.traceparent === "string") carrier.traceparent = message.traceparent;
|
|
270
|
+
if (typeof message?.tracestate === "string") carrier.tracestate = message.tracestate;
|
|
271
|
+
|
|
272
|
+
await telemetry.runWithExtractedContext(carrier, () =>
|
|
273
|
+
telemetry.startSpan(
|
|
274
|
+
`ws.${message?.type ?? "message"}`,
|
|
275
|
+
() => dispatch(),
|
|
276
|
+
{
|
|
277
|
+
kind: 2 /* SpanKind.SERVER */,
|
|
278
|
+
attributes: {
|
|
279
|
+
"messaging.system": "arc-ws",
|
|
280
|
+
"messaging.operation": "process",
|
|
281
|
+
"messaging.message.type": message?.type,
|
|
282
|
+
"arc.ws.client_id": client.id,
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
),
|
|
286
|
+
);
|
|
210
287
|
},
|
|
211
288
|
close(ws) {
|
|
212
289
|
const client = connectionManager.getClientByWs(ws as any);
|