@loggerjs/browser 0.5.0 → 0.5.1

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.
Files changed (103) hide show
  1. package/dist/broadcast-channel-transport.cjs +115 -0
  2. package/dist/broadcast-channel-transport.cjs.map +1 -0
  3. package/dist/broadcast-channel-transport.js +114 -0
  4. package/dist/broadcast-channel-transport.js.map +1 -0
  5. package/dist/console-integration.cjs +98 -0
  6. package/dist/console-integration.cjs.map +1 -0
  7. package/dist/console-integration.js +97 -0
  8. package/dist/console-integration.js.map +1 -0
  9. package/dist/context-propagation-integration.cjs +78 -0
  10. package/dist/context-propagation-integration.cjs.map +1 -0
  11. package/dist/context-propagation-integration.js +77 -0
  12. package/dist/context-propagation-integration.js.map +1 -0
  13. package/dist/error-integration.cjs +128 -0
  14. package/dist/error-integration.cjs.map +1 -0
  15. package/dist/error-integration.js +127 -0
  16. package/dist/error-integration.js.map +1 -0
  17. package/dist/fetch-integration.cjs +120 -0
  18. package/dist/fetch-integration.cjs.map +1 -0
  19. package/dist/fetch-integration.js +119 -0
  20. package/dist/fetch-integration.js.map +1 -0
  21. package/dist/framework-error-integration.cjs +102 -0
  22. package/dist/framework-error-integration.cjs.map +1 -0
  23. package/dist/framework-error-integration.js +101 -0
  24. package/dist/framework-error-integration.js.map +1 -0
  25. package/dist/framework-router-integrations.cjs +88 -0
  26. package/dist/framework-router-integrations.cjs.map +1 -0
  27. package/dist/framework-router-integrations.js +84 -0
  28. package/dist/framework-router-integrations.js.map +1 -0
  29. package/dist/http-transport.cjs +301 -0
  30. package/dist/http-transport.cjs.map +1 -0
  31. package/dist/http-transport.js +299 -0
  32. package/dist/http-transport.js.map +1 -0
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/indexeddb-offline-queue.cjs +105 -0
  36. package/dist/indexeddb-offline-queue.cjs.map +1 -0
  37. package/dist/indexeddb-offline-queue.js +104 -0
  38. package/dist/indexeddb-offline-queue.js.map +1 -0
  39. package/dist/indexeddb-transport.cjs +531 -0
  40. package/dist/indexeddb-transport.cjs.map +1 -0
  41. package/dist/indexeddb-transport.js +530 -0
  42. package/dist/indexeddb-transport.js.map +1 -0
  43. package/dist/offline-first-transport.cjs +675 -0
  44. package/dist/offline-first-transport.cjs.map +1 -0
  45. package/dist/offline-first-transport.js +674 -0
  46. package/dist/offline-first-transport.js.map +1 -0
  47. package/dist/page-lifecycle.cjs +39 -0
  48. package/dist/page-lifecycle.cjs.map +1 -0
  49. package/dist/page-lifecycle.js +38 -0
  50. package/dist/page-lifecycle.js.map +1 -0
  51. package/dist/payload-transforms.cjs +30 -0
  52. package/dist/payload-transforms.cjs.map +1 -0
  53. package/dist/payload-transforms.js +29 -0
  54. package/dist/payload-transforms.js.map +1 -0
  55. package/dist/performance-integration.cjs +135 -0
  56. package/dist/performance-integration.cjs.map +1 -0
  57. package/dist/performance-integration.js +133 -0
  58. package/dist/performance-integration.js.map +1 -0
  59. package/dist/reporting-integration.cjs +110 -0
  60. package/dist/reporting-integration.cjs.map +1 -0
  61. package/dist/reporting-integration.js +109 -0
  62. package/dist/reporting-integration.js.map +1 -0
  63. package/dist/router-integration.cjs +97 -0
  64. package/dist/router-integration.cjs.map +1 -0
  65. package/dist/router-integration.js +96 -0
  66. package/dist/router-integration.js.map +1 -0
  67. package/dist/runtime-host-integration.cjs +181 -0
  68. package/dist/runtime-host-integration.cjs.map +1 -0
  69. package/dist/runtime-host-integration.js +180 -0
  70. package/dist/runtime-host-integration.js.map +1 -0
  71. package/dist/service-worker-integration.cjs +109 -0
  72. package/dist/service-worker-integration.cjs.map +1 -0
  73. package/dist/service-worker-integration.js +108 -0
  74. package/dist/service-worker-integration.js.map +1 -0
  75. package/dist/service-worker-transport.cjs +184 -0
  76. package/dist/service-worker-transport.cjs.map +1 -0
  77. package/dist/service-worker-transport.js +183 -0
  78. package/dist/service-worker-transport.js.map +1 -0
  79. package/dist/user-action-integration.cjs +100 -0
  80. package/dist/user-action-integration.cjs.map +1 -0
  81. package/dist/user-action-integration.js +99 -0
  82. package/dist/user-action-integration.js.map +1 -0
  83. package/dist/web-vitals-integration.cjs +157 -0
  84. package/dist/web-vitals-integration.cjs.map +1 -0
  85. package/dist/web-vitals-integration.js +156 -0
  86. package/dist/web-vitals-integration.js.map +1 -0
  87. package/dist/websocket-integration.cjs +158 -0
  88. package/dist/websocket-integration.cjs.map +1 -0
  89. package/dist/websocket-integration.js +157 -0
  90. package/dist/websocket-integration.js.map +1 -0
  91. package/dist/websocket-transport.cjs +187 -0
  92. package/dist/websocket-transport.cjs.map +1 -0
  93. package/dist/websocket-transport.js +186 -0
  94. package/dist/websocket-transport.js.map +1 -0
  95. package/dist/xhr-integration.cjs +107 -0
  96. package/dist/xhr-integration.cjs.map +1 -0
  97. package/dist/xhr-integration.js +106 -0
  98. package/dist/xhr-integration.js.map +1 -0
  99. package/dist/zip-export.cjs +241 -0
  100. package/dist/zip-export.cjs.map +1 -0
  101. package/dist/zip-export.js +238 -0
  102. package/dist/zip-export.js.map +1 -0
  103. package/package.json +104 -77
@@ -0,0 +1,157 @@
1
+ import { normalizeValue } from "@loggerjs/core";
2
+ //#region src/http-capture-utils.ts
3
+ function nowMs() {
4
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
5
+ }
6
+ function durationMs(started) {
7
+ return nowMs() - started;
8
+ }
9
+ function sanitizeHttpUrl(rawUrl, sanitizer) {
10
+ const stripped = stripSensitiveUrlParts(rawUrl);
11
+ return sanitizer ? sanitizer(stripped) : stripped;
12
+ }
13
+ function shouldSample(sampleRate, random) {
14
+ if (sampleRate >= 1) return true;
15
+ if (sampleRate <= 0) return false;
16
+ return random() < sampleRate;
17
+ }
18
+ function stripSensitiveUrlParts(rawUrl) {
19
+ if (!/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(rawUrl)) return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;
20
+ try {
21
+ const url = new URL(rawUrl);
22
+ url.search = "";
23
+ url.hash = "";
24
+ return url.toString();
25
+ } catch {
26
+ return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;
27
+ }
28
+ }
29
+ //#endregion
30
+ //#region src/websocket-integration.ts
31
+ const registryKey = "browser.WebSocket";
32
+ const textEncoder = new TextEncoder();
33
+ function dataType(data) {
34
+ if (typeof data === "string") return "string";
35
+ if (data instanceof ArrayBuffer) return "arraybuffer";
36
+ if (ArrayBuffer.isView(data)) return data.constructor.name;
37
+ return data?.constructor?.name ?? typeof data;
38
+ }
39
+ function dataByteLength(data) {
40
+ if (typeof data === "string") return textEncoder.encode(data).byteLength;
41
+ if (data instanceof ArrayBuffer) return data.byteLength;
42
+ if (ArrayBuffer.isView(data)) return data.byteLength;
43
+ const size = data?.size;
44
+ return typeof size === "number" && Number.isFinite(size) ? size : void 0;
45
+ }
46
+ function messagePayload(direction, data, captureMessageData) {
47
+ return {
48
+ direction,
49
+ dataType: dataType(data),
50
+ byteLength: dataByteLength(data),
51
+ data: captureMessageData ? normalizeValue(data, {
52
+ maxDepth: 4,
53
+ maxObjectKeys: 80
54
+ }) : void 0
55
+ };
56
+ }
57
+ function closeEventPayload(event) {
58
+ const close = event;
59
+ return {
60
+ code: close.code,
61
+ reason: close.reason,
62
+ wasClean: close.wasClean
63
+ };
64
+ }
65
+ function copyStaticConstants(target, source) {
66
+ for (const key of [
67
+ "CONNECTING",
68
+ "OPEN",
69
+ "CLOSING",
70
+ "CLOSED"
71
+ ]) if (source[key] !== void 0) target[key] = source[key];
72
+ }
73
+ function captureWebSocketIntegration(options = {}) {
74
+ const level = options.level ?? "debug";
75
+ const captureConnect = options.captureConnect ?? true;
76
+ const captureOpen = options.captureOpen ?? true;
77
+ const captureClose = options.captureClose ?? true;
78
+ const captureError = options.captureError ?? true;
79
+ const captureMessages = options.captureMessages ?? false;
80
+ const captureSentMessages = options.captureSentMessages ?? false;
81
+ const captureMessageData = options.captureMessageData ?? false;
82
+ const sampleRate = options.sampleRate ?? 1;
83
+ const random = options.random ?? Math.random;
84
+ return {
85
+ name: "capture-websocket",
86
+ setup(api) {
87
+ const globalWebSocket = globalThis.WebSocket;
88
+ const current = options.WebSocket ?? globalWebSocket;
89
+ if (!current) return;
90
+ const original = api.unpatched.get(registryKey) ?? current;
91
+ api.unpatched.set(registryKey, original);
92
+ const capture = api.guard((input) => api.capture(input));
93
+ const PatchedWebSocket = function WebSocket(url, protocols) {
94
+ const started = nowMs();
95
+ const sanitizedUrl = sanitizeHttpUrl(String(url), options.sanitizeUrl);
96
+ const socket = new original(url, protocols);
97
+ const captureLifecycle = (event, extra = {}, error) => {
98
+ if (!shouldSample(sampleRate, random)) return;
99
+ capture({
100
+ level: event === "error" ? "error" : level,
101
+ message: `WebSocket ${event} ${sanitizedUrl}`,
102
+ error,
103
+ props: { websocket: {
104
+ kind: "websocket",
105
+ event,
106
+ url: sanitizedUrl,
107
+ durationMs: durationMs(started),
108
+ ...extra
109
+ } }
110
+ });
111
+ };
112
+ if (captureConnect) captureLifecycle("connect");
113
+ if (captureOpen) socket.addEventListener?.("open", () => captureLifecycle("open"));
114
+ if (captureClose) socket.addEventListener?.("close", (event) => captureLifecycle("close", closeEventPayload(event)));
115
+ if (captureError) socket.addEventListener?.("error", (event) => captureLifecycle("error", { type: event.type }, event));
116
+ if (captureMessages) socket.addEventListener?.("message", (event) => {
117
+ const message = event;
118
+ capture({
119
+ level,
120
+ message: `WebSocket message ${sanitizedUrl}`,
121
+ props: { websocket: {
122
+ kind: "websocket-message",
123
+ url: sanitizedUrl,
124
+ message: messagePayload("incoming", message.data, captureMessageData)
125
+ } }
126
+ });
127
+ });
128
+ if (captureSentMessages && socket.send) {
129
+ const originalSend = socket.send.bind(socket);
130
+ socket.send = (data) => {
131
+ capture({
132
+ level,
133
+ message: `WebSocket send ${sanitizedUrl}`,
134
+ props: { websocket: {
135
+ kind: "websocket-message",
136
+ url: sanitizedUrl,
137
+ message: messagePayload("outgoing", data, captureMessageData)
138
+ } }
139
+ });
140
+ return originalSend(data);
141
+ };
142
+ }
143
+ return socket;
144
+ };
145
+ PatchedWebSocket.prototype = original.prototype;
146
+ copyStaticConstants(PatchedWebSocket, original);
147
+ globalThis.WebSocket = PatchedWebSocket;
148
+ return () => {
149
+ globalThis.WebSocket = current;
150
+ };
151
+ }
152
+ };
153
+ }
154
+ //#endregion
155
+ export { captureWebSocketIntegration };
156
+
157
+ //# sourceMappingURL=websocket-integration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-integration.js","names":["record"],"sources":["../src/http-capture-utils.ts","../src/websocket-integration.ts"],"sourcesContent":["export function nowMs(): number {\n return typeof performance !== \"undefined\" ? performance.now() : Date.now();\n}\n\nexport function durationMs(started: number): number {\n return nowMs() - started;\n}\n\nexport function sanitizeHttpUrl(rawUrl: string, sanitizer?: (url: string) => string): string {\n const stripped = stripSensitiveUrlParts(rawUrl);\n return sanitizer ? sanitizer(stripped) : stripped;\n}\n\nexport function pickAllowedHeaders(\n headers: Headers | undefined,\n allowList: readonly string[] | undefined,\n): Record<string, string> | undefined {\n if (!headers || !allowList || allowList.length === 0) return undefined;\n const out: Record<string, string> = {};\n for (const name of allowList) {\n const value = headers.get(name);\n if (value !== null) out[name.toLowerCase()] = value;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nexport function headersFromInit(headers: HeadersInit | undefined): Headers | undefined {\n if (!headers) return undefined;\n try {\n return new Headers(headers);\n } catch {\n return undefined;\n }\n}\n\nexport function shouldSample(sampleRate: number, random: () => number): boolean {\n if (sampleRate >= 1) return true;\n if (sampleRate <= 0) return false;\n return random() < sampleRate;\n}\n\nfunction stripSensitiveUrlParts(rawUrl: string): string {\n if (!/^[a-zA-Z][a-zA-Z\\d+.-]*:/.test(rawUrl)) {\n return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;\n }\n\n try {\n const url = new URL(rawUrl);\n url.search = \"\";\n url.hash = \"\";\n return url.toString();\n } catch {\n return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;\n }\n}\n","import {\n normalizeValue,\n type CaptureInput,\n type Integration,\n type IntegrationSetupContext,\n type LoggerLevel,\n} from \"@loggerjs/core\";\nimport { durationMs, nowMs, sanitizeHttpUrl, shouldSample } from \"./http-capture-utils\";\n\ntype BrowserWebSocketSendPayload = Parameters<WebSocket[\"send\"]>[0];\n\nexport interface BrowserCapturedWebSocketLike {\n url?: string;\n send?: (data: BrowserWebSocketSendPayload) => void;\n close?: (code?: number, reason?: string) => void;\n addEventListener?: (type: string, listener: EventListenerOrEventListenerObject) => void;\n removeEventListener?: (type: string, listener: EventListenerOrEventListenerObject) => void;\n}\n\nexport interface BrowserCapturedWebSocketConstructor {\n new (url: string | URL, protocols?: string | string[]): BrowserCapturedWebSocketLike;\n prototype?: unknown;\n CONNECTING?: number;\n OPEN?: number;\n CLOSING?: number;\n CLOSED?: number;\n}\n\nexport type BrowserWebSocketDirection = \"incoming\" | \"outgoing\";\n\nexport interface BrowserWebSocketMessagePayload {\n direction: BrowserWebSocketDirection;\n dataType: string;\n byteLength?: number;\n data?: unknown;\n}\n\nexport interface CaptureWebSocketOptions {\n level?: LoggerLevel;\n captureConnect?: boolean;\n captureOpen?: boolean;\n captureClose?: boolean;\n captureError?: boolean;\n captureMessages?: boolean;\n captureSentMessages?: boolean;\n captureMessageData?: boolean;\n sampleRate?: number;\n random?: () => number;\n sanitizeUrl?: (url: string) => string;\n WebSocket?: BrowserCapturedWebSocketConstructor;\n}\n\nconst registryKey = \"browser.WebSocket\";\nconst textEncoder = new TextEncoder();\n\nfunction dataType(data: unknown): string {\n if (typeof data === \"string\") return \"string\";\n if (data instanceof ArrayBuffer) return \"arraybuffer\";\n if (ArrayBuffer.isView(data)) return data.constructor.name;\n const record = data as { constructor?: { name?: string } } | undefined;\n return record?.constructor?.name ?? typeof data;\n}\n\nfunction dataByteLength(data: unknown): number | undefined {\n if (typeof data === \"string\") return textEncoder.encode(data).byteLength;\n if (data instanceof ArrayBuffer) return data.byteLength;\n if (ArrayBuffer.isView(data)) return data.byteLength;\n const size = (data as { size?: unknown } | undefined)?.size;\n return typeof size === \"number\" && Number.isFinite(size) ? size : undefined;\n}\n\nfunction messagePayload(\n direction: BrowserWebSocketDirection,\n data: unknown,\n captureMessageData: boolean,\n): BrowserWebSocketMessagePayload {\n return {\n direction,\n dataType: dataType(data),\n byteLength: dataByteLength(data),\n data: captureMessageData ? normalizeValue(data, { maxDepth: 4, maxObjectKeys: 80 }) : undefined,\n };\n}\n\nfunction closeEventPayload(event: Event): Record<string, unknown> {\n const close = event as CloseEvent;\n return {\n code: close.code,\n reason: close.reason,\n wasClean: close.wasClean,\n };\n}\n\nfunction copyStaticConstants(\n target: BrowserCapturedWebSocketConstructor,\n source: BrowserCapturedWebSocketConstructor,\n) {\n for (const key of [\"CONNECTING\", \"OPEN\", \"CLOSING\", \"CLOSED\"] as const) {\n if (source[key] !== undefined) target[key] = source[key];\n }\n}\n\nexport function captureWebSocketIntegration(options: CaptureWebSocketOptions = {}): Integration {\n const level = options.level ?? \"debug\";\n const captureConnect = options.captureConnect ?? true;\n const captureOpen = options.captureOpen ?? true;\n const captureClose = options.captureClose ?? true;\n const captureError = options.captureError ?? true;\n const captureMessages = options.captureMessages ?? false;\n const captureSentMessages = options.captureSentMessages ?? false;\n const captureMessageData = options.captureMessageData ?? false;\n const sampleRate = options.sampleRate ?? 1;\n const random = options.random ?? Math.random;\n\n return {\n name: \"capture-websocket\",\n setup(api: IntegrationSetupContext) {\n const globalWebSocket = globalThis.WebSocket as\n | BrowserCapturedWebSocketConstructor\n | undefined;\n const current = options.WebSocket ?? globalWebSocket;\n if (!current) return;\n const original =\n api.unpatched.get<BrowserCapturedWebSocketConstructor>(registryKey) ?? current;\n api.unpatched.set(registryKey, original);\n const capture = api.guard((input: CaptureInput) => api.capture(input));\n\n const PatchedWebSocket = function WebSocket(\n this: BrowserCapturedWebSocketLike,\n url: string | URL,\n protocols?: string | string[],\n ) {\n const started = nowMs();\n const sanitizedUrl = sanitizeHttpUrl(String(url), options.sanitizeUrl);\n const socket = new original(url, protocols);\n\n const captureLifecycle = (\n event: \"close\" | \"connect\" | \"error\" | \"open\",\n extra: Record<string, unknown> = {},\n error?: unknown,\n ) => {\n if (!shouldSample(sampleRate, random)) return;\n capture({\n level: event === \"error\" ? \"error\" : level,\n message: `WebSocket ${event} ${sanitizedUrl}`,\n error,\n props: {\n websocket: {\n kind: \"websocket\",\n event,\n url: sanitizedUrl,\n durationMs: durationMs(started),\n ...extra,\n },\n },\n });\n };\n\n if (captureConnect) captureLifecycle(\"connect\");\n\n if (captureOpen) {\n socket.addEventListener?.(\"open\", () => captureLifecycle(\"open\"));\n }\n if (captureClose) {\n socket.addEventListener?.(\"close\", (event) =>\n captureLifecycle(\"close\", closeEventPayload(event)),\n );\n }\n if (captureError) {\n socket.addEventListener?.(\"error\", (event) =>\n captureLifecycle(\"error\", { type: event.type }, event),\n );\n }\n if (captureMessages) {\n socket.addEventListener?.(\"message\", (event) => {\n const message = event as MessageEvent;\n capture({\n level,\n message: `WebSocket message ${sanitizedUrl}`,\n props: {\n websocket: {\n kind: \"websocket-message\",\n url: sanitizedUrl,\n message: messagePayload(\"incoming\", message.data, captureMessageData),\n },\n },\n });\n });\n }\n\n if (captureSentMessages && socket.send) {\n const originalSend = socket.send.bind(socket);\n socket.send = (data: BrowserWebSocketSendPayload) => {\n capture({\n level,\n message: `WebSocket send ${sanitizedUrl}`,\n props: {\n websocket: {\n kind: \"websocket-message\",\n url: sanitizedUrl,\n message: messagePayload(\"outgoing\", data, captureMessageData),\n },\n },\n });\n return originalSend(data);\n };\n }\n\n return socket;\n } as unknown as BrowserCapturedWebSocketConstructor;\n\n PatchedWebSocket.prototype = original.prototype;\n copyStaticConstants(PatchedWebSocket, original);\n globalThis.WebSocket = PatchedWebSocket as unknown as typeof WebSocket;\n\n return () => {\n globalThis.WebSocket = current as unknown as typeof WebSocket;\n };\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,187 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _loggerjs_core = require("@loggerjs/core");
3
+ //#region src/websocket-transport.ts
4
+ const WS_CONNECTING = 0;
5
+ const WS_OPEN = 1;
6
+ function defaultWebSocketFactory(url, protocols) {
7
+ if (typeof WebSocket === "undefined") throw new Error("WebSocket is not available");
8
+ return new WebSocket(url, protocols);
9
+ }
10
+ function toSendPayload(payload) {
11
+ if (typeof payload === "string") return payload;
12
+ return Uint8Array.from(payload);
13
+ }
14
+ function browserWebSocketTransport(options) {
15
+ const transportName = options.name ?? "browser-websocket";
16
+ const codec = options.codec ?? (0, _loggerjs_core.safeJsonCodec)();
17
+ const maxQueueSize = options.maxQueueSize ?? 1e3;
18
+ const dropPolicy = options.dropPolicy ?? "drop-oldest";
19
+ const webSocketFactory = options.webSocketFactory ?? defaultWebSocketFactory;
20
+ const queue = [];
21
+ let active;
22
+ let closed = false;
23
+ let lastContext;
24
+ const reportDrop = (item, reason) => {
25
+ (0, _loggerjs_core.incrementLoggerMetaCounter)("transport.dropped", item.events.length);
26
+ (0, _loggerjs_core.incrementLoggerMetaCounter)(`transport.dropped.${reason}`, item.events.length);
27
+ for (const event of item.events) options.onDrop?.(event, reason);
28
+ };
29
+ const reportError = (error, context, detail) => {
30
+ try {
31
+ options.onError?.(error, detail);
32
+ } catch (onErrorError) {
33
+ context?.reportInternalError(onErrorError, {
34
+ operation: "on-error",
35
+ phase: "transport",
36
+ transport: transportName
37
+ });
38
+ }
39
+ context?.reportInternalError(error, {
40
+ operation: detail.operation,
41
+ phase: "transport",
42
+ transport: transportName
43
+ });
44
+ };
45
+ const detachSocket = () => {
46
+ if (!active) return;
47
+ const current = active;
48
+ current.socket.removeEventListener("open", current.onOpen);
49
+ current.socket.removeEventListener("close", current.onClose);
50
+ current.socket.removeEventListener("error", current.onError);
51
+ active = void 0;
52
+ };
53
+ const drainQueue = (context = lastContext) => {
54
+ const socket = active?.socket;
55
+ if (!socket || socket.readyState !== WS_OPEN) return;
56
+ while (queue.length > 0) {
57
+ const item = queue.shift();
58
+ if (!item) return;
59
+ try {
60
+ socket.send(toSendPayload(item.payload));
61
+ } catch (error) {
62
+ reportDrop(item, "send");
63
+ reportError(error, context, {
64
+ droppedEvents: item.events.length,
65
+ operation: "send"
66
+ });
67
+ return;
68
+ }
69
+ }
70
+ };
71
+ const createSocket = (context, droppedEvents) => {
72
+ if (closed) return void 0;
73
+ if (active && (active.socket.readyState === WS_CONNECTING || active.socket.readyState === WS_OPEN)) return active.socket;
74
+ detachSocket();
75
+ try {
76
+ const socket = webSocketFactory(options.url, options.protocols);
77
+ const nextActive = {
78
+ socket,
79
+ onOpen() {
80
+ drainQueue(context);
81
+ },
82
+ onClose() {
83
+ if (active?.socket === socket) detachSocket();
84
+ },
85
+ onError(errorEvent) {
86
+ reportError(errorEvent, context, {
87
+ droppedEvents: 0,
88
+ operation: "socket-error"
89
+ });
90
+ }
91
+ };
92
+ active = nextActive;
93
+ socket.addEventListener("open", nextActive.onOpen);
94
+ socket.addEventListener("close", nextActive.onClose);
95
+ socket.addEventListener("error", nextActive.onError);
96
+ return socket;
97
+ } catch (error) {
98
+ reportError(error, context, {
99
+ droppedEvents,
100
+ operation: "create-socket"
101
+ });
102
+ return;
103
+ }
104
+ };
105
+ const enqueue = (item) => {
106
+ if (queue.length >= maxQueueSize) {
107
+ if (dropPolicy === "drop-newest") {
108
+ reportDrop(item, "queue-full");
109
+ return false;
110
+ }
111
+ const dropped = queue.shift();
112
+ if (dropped) reportDrop(dropped, "queue-full");
113
+ }
114
+ queue.push(item);
115
+ return true;
116
+ };
117
+ const sendOrQueue = (item, context) => {
118
+ lastContext = context;
119
+ if (closed) {
120
+ reportDrop(item, "closed");
121
+ return;
122
+ }
123
+ const socket = createSocket(context, item.events.length);
124
+ if (!socket) {
125
+ reportDrop(item, "create-socket");
126
+ return;
127
+ }
128
+ if (socket.readyState === WS_OPEN) {
129
+ try {
130
+ socket.send(toSendPayload(item.payload));
131
+ } catch (error) {
132
+ reportDrop(item, "send");
133
+ reportError(error, context, {
134
+ droppedEvents: item.events.length,
135
+ operation: "send"
136
+ });
137
+ }
138
+ return;
139
+ }
140
+ enqueue(item);
141
+ };
142
+ return {
143
+ name: transportName,
144
+ minLevel: options.minLevel,
145
+ queueSize() {
146
+ return queue.length;
147
+ },
148
+ log(event, context) {
149
+ sendOrQueue({
150
+ events: [event],
151
+ payload: codec.encode(event)
152
+ }, context);
153
+ },
154
+ logBatch(events, context) {
155
+ if (events.length === 0) return;
156
+ sendOrQueue({
157
+ events,
158
+ payload: codec.encode(events)
159
+ }, context);
160
+ },
161
+ flush() {
162
+ drainQueue();
163
+ },
164
+ async close() {
165
+ closed = true;
166
+ while (queue.length > 0) {
167
+ const item = queue.shift();
168
+ if (item) reportDrop(item, "closed");
169
+ }
170
+ const socket = active?.socket;
171
+ detachSocket();
172
+ if (!socket) return;
173
+ try {
174
+ socket.close(options.closeCode, options.closeReason);
175
+ } catch (error) {
176
+ reportError(error, lastContext, {
177
+ droppedEvents: 0,
178
+ operation: "close-socket"
179
+ });
180
+ }
181
+ }
182
+ };
183
+ }
184
+ //#endregion
185
+ exports.browserWebSocketTransport = browserWebSocketTransport;
186
+
187
+ //# sourceMappingURL=websocket-transport.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-transport.cjs","names":[],"sources":["../src/websocket-transport.ts"],"sourcesContent":["import {\n incrementLoggerMetaCounter,\n safeJsonCodec,\n type Codec,\n type LogEvent,\n type LoggerLevel,\n type Transport,\n type TransportContext,\n} from \"@loggerjs/core\";\n\nexport type BrowserWebSocketDropPolicy = \"drop-oldest\" | \"drop-newest\";\nexport type BrowserWebSocketPayload = string | Uint8Array;\nexport type BrowserWebSocketEventType = \"close\" | \"error\" | \"open\";\ntype BrowserWebSocketSendPayload = Parameters<WebSocket[\"send\"]>[0];\n\nexport interface BrowserWebSocketLike {\n readonly readyState: number;\n send: (data: BrowserWebSocketSendPayload) => void;\n close: (code?: number, reason?: string) => void;\n addEventListener: (type: BrowserWebSocketEventType, listener: (event: Event) => void) => void;\n removeEventListener: (type: BrowserWebSocketEventType, listener: (event: Event) => void) => void;\n}\n\nexport type BrowserWebSocketFactory = (\n url: string,\n protocols?: string | string[],\n) => BrowserWebSocketLike;\n\nexport interface BrowserWebSocketErrorDetail {\n operation: \"close-socket\" | \"create-socket\" | \"send\" | \"socket-error\" | \"on-error\";\n droppedEvents: number;\n}\n\nexport interface BrowserWebSocketTransportOptions {\n url: string;\n name?: string;\n protocols?: string | string[];\n minLevel?: LoggerLevel;\n codec?: Codec<BrowserWebSocketPayload>;\n maxQueueSize?: number;\n dropPolicy?: BrowserWebSocketDropPolicy;\n webSocketFactory?: BrowserWebSocketFactory;\n closeCode?: number;\n closeReason?: string;\n onDrop?: (event: LogEvent, reason: string) => void;\n onError?: (error: unknown, detail: BrowserWebSocketErrorDetail) => void;\n}\n\nexport interface BrowserWebSocketTransport extends Transport {\n queueSize: () => number;\n}\n\ninterface QueueItem {\n events: readonly LogEvent[];\n payload: BrowserWebSocketPayload;\n}\n\ninterface ActiveSocket {\n socket: BrowserWebSocketLike;\n onOpen: (event: Event) => void;\n onClose: (event: Event) => void;\n onError: (event: Event) => void;\n}\n\nconst WS_CONNECTING = 0;\nconst WS_OPEN = 1;\n\nfunction defaultWebSocketFactory(url: string, protocols?: string | string[]): BrowserWebSocketLike {\n if (typeof WebSocket === \"undefined\") {\n throw new Error(\"WebSocket is not available\");\n }\n return new WebSocket(url, protocols);\n}\n\nfunction toSendPayload(payload: BrowserWebSocketPayload): BrowserWebSocketSendPayload {\n if (typeof payload === \"string\") return payload;\n return Uint8Array.from(payload);\n}\n\nexport function browserWebSocketTransport(\n options: BrowserWebSocketTransportOptions,\n): BrowserWebSocketTransport {\n const transportName = options.name ?? \"browser-websocket\";\n const codec = options.codec ?? safeJsonCodec();\n const maxQueueSize = options.maxQueueSize ?? 1000;\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n const webSocketFactory = options.webSocketFactory ?? defaultWebSocketFactory;\n const queue: QueueItem[] = [];\n let active: ActiveSocket | undefined;\n let closed = false;\n let lastContext: TransportContext | undefined;\n\n const reportDrop = (item: QueueItem, reason: string) => {\n incrementLoggerMetaCounter(\"transport.dropped\", item.events.length);\n incrementLoggerMetaCounter(`transport.dropped.${reason}`, item.events.length);\n for (const event of item.events) options.onDrop?.(event, reason);\n };\n\n const reportError = (\n error: unknown,\n context: TransportContext | undefined,\n detail: BrowserWebSocketErrorDetail,\n ) => {\n try {\n options.onError?.(error, detail);\n } catch (onErrorError) {\n context?.reportInternalError(onErrorError, {\n operation: \"on-error\",\n phase: \"transport\",\n transport: transportName,\n });\n }\n\n context?.reportInternalError(error, {\n operation: detail.operation,\n phase: \"transport\",\n transport: transportName,\n });\n };\n\n const detachSocket = () => {\n if (!active) return;\n const current = active;\n current.socket.removeEventListener(\"open\", current.onOpen);\n current.socket.removeEventListener(\"close\", current.onClose);\n current.socket.removeEventListener(\"error\", current.onError);\n active = undefined;\n };\n\n const drainQueue = (context = lastContext) => {\n const socket = active?.socket;\n if (!socket || socket.readyState !== WS_OPEN) return;\n\n while (queue.length > 0) {\n const item = queue.shift();\n if (!item) return;\n try {\n socket.send(toSendPayload(item.payload));\n } catch (error) {\n reportDrop(item, \"send\");\n reportError(error, context, {\n droppedEvents: item.events.length,\n operation: \"send\",\n });\n return;\n }\n }\n };\n\n const createSocket = (context: TransportContext, droppedEvents: number) => {\n if (closed) return undefined;\n if (\n active &&\n (active.socket.readyState === WS_CONNECTING || active.socket.readyState === WS_OPEN)\n ) {\n return active.socket;\n }\n\n detachSocket();\n\n try {\n const socket = webSocketFactory(options.url, options.protocols);\n const nextActive: ActiveSocket = {\n socket,\n onOpen() {\n drainQueue(context);\n },\n onClose() {\n if (active?.socket === socket) detachSocket();\n },\n onError(errorEvent) {\n reportError(errorEvent, context, {\n droppedEvents: 0,\n operation: \"socket-error\",\n });\n },\n };\n active = nextActive;\n socket.addEventListener(\"open\", nextActive.onOpen);\n socket.addEventListener(\"close\", nextActive.onClose);\n socket.addEventListener(\"error\", nextActive.onError);\n return socket;\n } catch (error) {\n reportError(error, context, {\n droppedEvents,\n operation: \"create-socket\",\n });\n return undefined;\n }\n };\n\n const enqueue = (item: QueueItem) => {\n if (queue.length >= maxQueueSize) {\n if (dropPolicy === \"drop-newest\") {\n reportDrop(item, \"queue-full\");\n return false;\n }\n\n const dropped = queue.shift();\n if (dropped) reportDrop(dropped, \"queue-full\");\n }\n\n queue.push(item);\n return true;\n };\n\n const sendOrQueue = (item: QueueItem, context: TransportContext) => {\n lastContext = context;\n if (closed) {\n reportDrop(item, \"closed\");\n return;\n }\n\n const socket = createSocket(context, item.events.length);\n if (!socket) {\n reportDrop(item, \"create-socket\");\n return;\n }\n\n if (socket.readyState === WS_OPEN) {\n try {\n socket.send(toSendPayload(item.payload));\n } catch (error) {\n reportDrop(item, \"send\");\n reportError(error, context, {\n droppedEvents: item.events.length,\n operation: \"send\",\n });\n }\n return;\n }\n\n enqueue(item);\n };\n\n return {\n name: transportName,\n minLevel: options.minLevel,\n queueSize() {\n return queue.length;\n },\n log(event, context) {\n sendOrQueue({ events: [event], payload: codec.encode(event) }, context);\n },\n logBatch(events, context) {\n if (events.length === 0) return;\n sendOrQueue({ events, payload: codec.encode(events) }, context);\n },\n flush() {\n drainQueue();\n },\n async close() {\n closed = true;\n while (queue.length > 0) {\n const item = queue.shift();\n if (item) reportDrop(item, \"closed\");\n }\n\n const socket = active?.socket;\n detachSocket();\n if (!socket) return;\n\n try {\n socket.close(options.closeCode, options.closeReason);\n } catch (error) {\n reportError(error, lastContext, {\n droppedEvents: 0,\n operation: \"close-socket\",\n });\n }\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,186 @@
1
+ import { incrementLoggerMetaCounter, safeJsonCodec } from "@loggerjs/core";
2
+ //#region src/websocket-transport.ts
3
+ const WS_CONNECTING = 0;
4
+ const WS_OPEN = 1;
5
+ function defaultWebSocketFactory(url, protocols) {
6
+ if (typeof WebSocket === "undefined") throw new Error("WebSocket is not available");
7
+ return new WebSocket(url, protocols);
8
+ }
9
+ function toSendPayload(payload) {
10
+ if (typeof payload === "string") return payload;
11
+ return Uint8Array.from(payload);
12
+ }
13
+ function browserWebSocketTransport(options) {
14
+ const transportName = options.name ?? "browser-websocket";
15
+ const codec = options.codec ?? safeJsonCodec();
16
+ const maxQueueSize = options.maxQueueSize ?? 1e3;
17
+ const dropPolicy = options.dropPolicy ?? "drop-oldest";
18
+ const webSocketFactory = options.webSocketFactory ?? defaultWebSocketFactory;
19
+ const queue = [];
20
+ let active;
21
+ let closed = false;
22
+ let lastContext;
23
+ const reportDrop = (item, reason) => {
24
+ incrementLoggerMetaCounter("transport.dropped", item.events.length);
25
+ incrementLoggerMetaCounter(`transport.dropped.${reason}`, item.events.length);
26
+ for (const event of item.events) options.onDrop?.(event, reason);
27
+ };
28
+ const reportError = (error, context, detail) => {
29
+ try {
30
+ options.onError?.(error, detail);
31
+ } catch (onErrorError) {
32
+ context?.reportInternalError(onErrorError, {
33
+ operation: "on-error",
34
+ phase: "transport",
35
+ transport: transportName
36
+ });
37
+ }
38
+ context?.reportInternalError(error, {
39
+ operation: detail.operation,
40
+ phase: "transport",
41
+ transport: transportName
42
+ });
43
+ };
44
+ const detachSocket = () => {
45
+ if (!active) return;
46
+ const current = active;
47
+ current.socket.removeEventListener("open", current.onOpen);
48
+ current.socket.removeEventListener("close", current.onClose);
49
+ current.socket.removeEventListener("error", current.onError);
50
+ active = void 0;
51
+ };
52
+ const drainQueue = (context = lastContext) => {
53
+ const socket = active?.socket;
54
+ if (!socket || socket.readyState !== WS_OPEN) return;
55
+ while (queue.length > 0) {
56
+ const item = queue.shift();
57
+ if (!item) return;
58
+ try {
59
+ socket.send(toSendPayload(item.payload));
60
+ } catch (error) {
61
+ reportDrop(item, "send");
62
+ reportError(error, context, {
63
+ droppedEvents: item.events.length,
64
+ operation: "send"
65
+ });
66
+ return;
67
+ }
68
+ }
69
+ };
70
+ const createSocket = (context, droppedEvents) => {
71
+ if (closed) return void 0;
72
+ if (active && (active.socket.readyState === WS_CONNECTING || active.socket.readyState === WS_OPEN)) return active.socket;
73
+ detachSocket();
74
+ try {
75
+ const socket = webSocketFactory(options.url, options.protocols);
76
+ const nextActive = {
77
+ socket,
78
+ onOpen() {
79
+ drainQueue(context);
80
+ },
81
+ onClose() {
82
+ if (active?.socket === socket) detachSocket();
83
+ },
84
+ onError(errorEvent) {
85
+ reportError(errorEvent, context, {
86
+ droppedEvents: 0,
87
+ operation: "socket-error"
88
+ });
89
+ }
90
+ };
91
+ active = nextActive;
92
+ socket.addEventListener("open", nextActive.onOpen);
93
+ socket.addEventListener("close", nextActive.onClose);
94
+ socket.addEventListener("error", nextActive.onError);
95
+ return socket;
96
+ } catch (error) {
97
+ reportError(error, context, {
98
+ droppedEvents,
99
+ operation: "create-socket"
100
+ });
101
+ return;
102
+ }
103
+ };
104
+ const enqueue = (item) => {
105
+ if (queue.length >= maxQueueSize) {
106
+ if (dropPolicy === "drop-newest") {
107
+ reportDrop(item, "queue-full");
108
+ return false;
109
+ }
110
+ const dropped = queue.shift();
111
+ if (dropped) reportDrop(dropped, "queue-full");
112
+ }
113
+ queue.push(item);
114
+ return true;
115
+ };
116
+ const sendOrQueue = (item, context) => {
117
+ lastContext = context;
118
+ if (closed) {
119
+ reportDrop(item, "closed");
120
+ return;
121
+ }
122
+ const socket = createSocket(context, item.events.length);
123
+ if (!socket) {
124
+ reportDrop(item, "create-socket");
125
+ return;
126
+ }
127
+ if (socket.readyState === WS_OPEN) {
128
+ try {
129
+ socket.send(toSendPayload(item.payload));
130
+ } catch (error) {
131
+ reportDrop(item, "send");
132
+ reportError(error, context, {
133
+ droppedEvents: item.events.length,
134
+ operation: "send"
135
+ });
136
+ }
137
+ return;
138
+ }
139
+ enqueue(item);
140
+ };
141
+ return {
142
+ name: transportName,
143
+ minLevel: options.minLevel,
144
+ queueSize() {
145
+ return queue.length;
146
+ },
147
+ log(event, context) {
148
+ sendOrQueue({
149
+ events: [event],
150
+ payload: codec.encode(event)
151
+ }, context);
152
+ },
153
+ logBatch(events, context) {
154
+ if (events.length === 0) return;
155
+ sendOrQueue({
156
+ events,
157
+ payload: codec.encode(events)
158
+ }, context);
159
+ },
160
+ flush() {
161
+ drainQueue();
162
+ },
163
+ async close() {
164
+ closed = true;
165
+ while (queue.length > 0) {
166
+ const item = queue.shift();
167
+ if (item) reportDrop(item, "closed");
168
+ }
169
+ const socket = active?.socket;
170
+ detachSocket();
171
+ if (!socket) return;
172
+ try {
173
+ socket.close(options.closeCode, options.closeReason);
174
+ } catch (error) {
175
+ reportError(error, lastContext, {
176
+ droppedEvents: 0,
177
+ operation: "close-socket"
178
+ });
179
+ }
180
+ }
181
+ };
182
+ }
183
+ //#endregion
184
+ export { browserWebSocketTransport };
185
+
186
+ //# sourceMappingURL=websocket-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-transport.js","names":[],"sources":["../src/websocket-transport.ts"],"sourcesContent":["import {\n incrementLoggerMetaCounter,\n safeJsonCodec,\n type Codec,\n type LogEvent,\n type LoggerLevel,\n type Transport,\n type TransportContext,\n} from \"@loggerjs/core\";\n\nexport type BrowserWebSocketDropPolicy = \"drop-oldest\" | \"drop-newest\";\nexport type BrowserWebSocketPayload = string | Uint8Array;\nexport type BrowserWebSocketEventType = \"close\" | \"error\" | \"open\";\ntype BrowserWebSocketSendPayload = Parameters<WebSocket[\"send\"]>[0];\n\nexport interface BrowserWebSocketLike {\n readonly readyState: number;\n send: (data: BrowserWebSocketSendPayload) => void;\n close: (code?: number, reason?: string) => void;\n addEventListener: (type: BrowserWebSocketEventType, listener: (event: Event) => void) => void;\n removeEventListener: (type: BrowserWebSocketEventType, listener: (event: Event) => void) => void;\n}\n\nexport type BrowserWebSocketFactory = (\n url: string,\n protocols?: string | string[],\n) => BrowserWebSocketLike;\n\nexport interface BrowserWebSocketErrorDetail {\n operation: \"close-socket\" | \"create-socket\" | \"send\" | \"socket-error\" | \"on-error\";\n droppedEvents: number;\n}\n\nexport interface BrowserWebSocketTransportOptions {\n url: string;\n name?: string;\n protocols?: string | string[];\n minLevel?: LoggerLevel;\n codec?: Codec<BrowserWebSocketPayload>;\n maxQueueSize?: number;\n dropPolicy?: BrowserWebSocketDropPolicy;\n webSocketFactory?: BrowserWebSocketFactory;\n closeCode?: number;\n closeReason?: string;\n onDrop?: (event: LogEvent, reason: string) => void;\n onError?: (error: unknown, detail: BrowserWebSocketErrorDetail) => void;\n}\n\nexport interface BrowserWebSocketTransport extends Transport {\n queueSize: () => number;\n}\n\ninterface QueueItem {\n events: readonly LogEvent[];\n payload: BrowserWebSocketPayload;\n}\n\ninterface ActiveSocket {\n socket: BrowserWebSocketLike;\n onOpen: (event: Event) => void;\n onClose: (event: Event) => void;\n onError: (event: Event) => void;\n}\n\nconst WS_CONNECTING = 0;\nconst WS_OPEN = 1;\n\nfunction defaultWebSocketFactory(url: string, protocols?: string | string[]): BrowserWebSocketLike {\n if (typeof WebSocket === \"undefined\") {\n throw new Error(\"WebSocket is not available\");\n }\n return new WebSocket(url, protocols);\n}\n\nfunction toSendPayload(payload: BrowserWebSocketPayload): BrowserWebSocketSendPayload {\n if (typeof payload === \"string\") return payload;\n return Uint8Array.from(payload);\n}\n\nexport function browserWebSocketTransport(\n options: BrowserWebSocketTransportOptions,\n): BrowserWebSocketTransport {\n const transportName = options.name ?? \"browser-websocket\";\n const codec = options.codec ?? safeJsonCodec();\n const maxQueueSize = options.maxQueueSize ?? 1000;\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n const webSocketFactory = options.webSocketFactory ?? defaultWebSocketFactory;\n const queue: QueueItem[] = [];\n let active: ActiveSocket | undefined;\n let closed = false;\n let lastContext: TransportContext | undefined;\n\n const reportDrop = (item: QueueItem, reason: string) => {\n incrementLoggerMetaCounter(\"transport.dropped\", item.events.length);\n incrementLoggerMetaCounter(`transport.dropped.${reason}`, item.events.length);\n for (const event of item.events) options.onDrop?.(event, reason);\n };\n\n const reportError = (\n error: unknown,\n context: TransportContext | undefined,\n detail: BrowserWebSocketErrorDetail,\n ) => {\n try {\n options.onError?.(error, detail);\n } catch (onErrorError) {\n context?.reportInternalError(onErrorError, {\n operation: \"on-error\",\n phase: \"transport\",\n transport: transportName,\n });\n }\n\n context?.reportInternalError(error, {\n operation: detail.operation,\n phase: \"transport\",\n transport: transportName,\n });\n };\n\n const detachSocket = () => {\n if (!active) return;\n const current = active;\n current.socket.removeEventListener(\"open\", current.onOpen);\n current.socket.removeEventListener(\"close\", current.onClose);\n current.socket.removeEventListener(\"error\", current.onError);\n active = undefined;\n };\n\n const drainQueue = (context = lastContext) => {\n const socket = active?.socket;\n if (!socket || socket.readyState !== WS_OPEN) return;\n\n while (queue.length > 0) {\n const item = queue.shift();\n if (!item) return;\n try {\n socket.send(toSendPayload(item.payload));\n } catch (error) {\n reportDrop(item, \"send\");\n reportError(error, context, {\n droppedEvents: item.events.length,\n operation: \"send\",\n });\n return;\n }\n }\n };\n\n const createSocket = (context: TransportContext, droppedEvents: number) => {\n if (closed) return undefined;\n if (\n active &&\n (active.socket.readyState === WS_CONNECTING || active.socket.readyState === WS_OPEN)\n ) {\n return active.socket;\n }\n\n detachSocket();\n\n try {\n const socket = webSocketFactory(options.url, options.protocols);\n const nextActive: ActiveSocket = {\n socket,\n onOpen() {\n drainQueue(context);\n },\n onClose() {\n if (active?.socket === socket) detachSocket();\n },\n onError(errorEvent) {\n reportError(errorEvent, context, {\n droppedEvents: 0,\n operation: \"socket-error\",\n });\n },\n };\n active = nextActive;\n socket.addEventListener(\"open\", nextActive.onOpen);\n socket.addEventListener(\"close\", nextActive.onClose);\n socket.addEventListener(\"error\", nextActive.onError);\n return socket;\n } catch (error) {\n reportError(error, context, {\n droppedEvents,\n operation: \"create-socket\",\n });\n return undefined;\n }\n };\n\n const enqueue = (item: QueueItem) => {\n if (queue.length >= maxQueueSize) {\n if (dropPolicy === \"drop-newest\") {\n reportDrop(item, \"queue-full\");\n return false;\n }\n\n const dropped = queue.shift();\n if (dropped) reportDrop(dropped, \"queue-full\");\n }\n\n queue.push(item);\n return true;\n };\n\n const sendOrQueue = (item: QueueItem, context: TransportContext) => {\n lastContext = context;\n if (closed) {\n reportDrop(item, \"closed\");\n return;\n }\n\n const socket = createSocket(context, item.events.length);\n if (!socket) {\n reportDrop(item, \"create-socket\");\n return;\n }\n\n if (socket.readyState === WS_OPEN) {\n try {\n socket.send(toSendPayload(item.payload));\n } catch (error) {\n reportDrop(item, \"send\");\n reportError(error, context, {\n droppedEvents: item.events.length,\n operation: \"send\",\n });\n }\n return;\n }\n\n enqueue(item);\n };\n\n return {\n name: transportName,\n minLevel: options.minLevel,\n queueSize() {\n return queue.length;\n },\n log(event, context) {\n sendOrQueue({ events: [event], payload: codec.encode(event) }, context);\n },\n logBatch(events, context) {\n if (events.length === 0) return;\n sendOrQueue({ events, payload: codec.encode(events) }, context);\n },\n flush() {\n drainQueue();\n },\n async close() {\n closed = true;\n while (queue.length > 0) {\n const item = queue.shift();\n if (item) reportDrop(item, \"closed\");\n }\n\n const socket = active?.socket;\n detachSocket();\n if (!socket) return;\n\n try {\n socket.close(options.closeCode, options.closeReason);\n } catch (error) {\n reportError(error, lastContext, {\n droppedEvents: 0,\n operation: \"close-socket\",\n });\n }\n },\n };\n}\n"],"mappings":"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"}