@spotlightjs/sidecar
Advanced tools
Comparing version 1.9.2 to 1.9.3
@@ -33,3 +33,3 @@ import { type SidecarLogger } from './logger.js'; | ||
export declare function clearBuffer(): void; | ||
export declare function shutdown(): void; | ||
export declare const shutdown: () => void; | ||
export {}; |
794
dist/main.js
@@ -1,475 +0,365 @@ | ||
var __defProp = Object.defineProperty; | ||
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; | ||
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); | ||
import launchEditor from "launch-editor"; | ||
import { readFileSync, createWriteStream } from "node:fs"; | ||
import { createServer, get } from "node:http"; | ||
import * as path from "node:path"; | ||
import { join, resolve, extname } from "node:path"; | ||
import { createGunzip, createInflate } from "node:zlib"; | ||
import * as os from "node:os"; | ||
import * as SourceMap from "source-map"; | ||
const DEFAULT_PORT = 8969; | ||
const SERVER_IDENTIFIER = "spotlight-by-sentry"; | ||
const CONTEXT_LINES_ENDPOINT = "/contextlines"; | ||
async function getGeneratedCodeFromServer(filename) { | ||
var B = Object.defineProperty; | ||
var z = (e, t, n) => t in e ? B(e, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : e[t] = n; | ||
var h = (e, t, n) => z(e, typeof t != "symbol" ? t + "" : t, n); | ||
import { startSpan as _, getTraceData as V, captureException as J } from "@sentry/node"; | ||
import W from "launch-editor"; | ||
import { readFileSync as $, createWriteStream as X } from "node:fs"; | ||
import { createServer as K, get as Y } from "node:http"; | ||
import * as E from "node:path"; | ||
import { join as L, resolve as Q, extname as Z } from "node:path"; | ||
import { createGunzip as q, createInflate as ee } from "node:zlib"; | ||
import { CONTEXT_LINES_ENDPOINT as te, DEFAULT_PORT as ne, SERVER_IDENTIFIER as oe } from "./constants.js"; | ||
import * as ie from "node:os"; | ||
import * as O from "source-map"; | ||
async function re(e) { | ||
try { | ||
const generatedCodeResponse = await fetch(filename); | ||
return generatedCodeResponse.text(); | ||
return (await fetch(e)).text(); | ||
} catch { | ||
return void 0; | ||
return; | ||
} | ||
} | ||
function parseStackTrace(requestBody) { | ||
function se(e) { | ||
try { | ||
return JSON.parse(requestBody); | ||
return JSON.parse(e); | ||
} catch { | ||
return void 0; | ||
return; | ||
} | ||
} | ||
async function applySourceContextToFrame(sourceMapContent, frame) { | ||
const consumer = await new SourceMap.SourceMapConsumer(sourceMapContent); | ||
const originalPosition = consumer.originalPositionFor({ | ||
line: frame.lineno, | ||
column: frame.colno, | ||
bias: SourceMap.SourceMapConsumer.LEAST_UPPER_BOUND | ||
async function ae(e, t) { | ||
const n = await new O.SourceMapConsumer(e), o = n.originalPositionFor({ | ||
line: t.lineno, | ||
column: t.colno, | ||
bias: O.SourceMapConsumer.LEAST_UPPER_BOUND | ||
}); | ||
if (originalPosition.source && originalPosition.line && originalPosition.column) { | ||
frame.lineno = originalPosition.line; | ||
frame.colno = originalPosition.column; | ||
const filePath = new URL(frame.filename).pathname.slice(1); | ||
frame.filename = path.resolve(path.join(path.dirname(filePath), originalPosition.source)); | ||
const content = consumer.sourceContentFor(originalPosition.source); | ||
const lines = (content == null ? void 0 : content.split(os.EOL)) ?? []; | ||
addContextLinesToFrame(lines, frame); | ||
if (o.source && o.line && o.column) { | ||
t.lineno = o.line, t.colno = o.column; | ||
const i = new URL(t.filename).pathname.slice(1); | ||
t.filename = E.resolve(E.join(E.dirname(i), o.source)); | ||
const r = n.sourceContentFor(o.source), s = (r == null ? void 0 : r.split(ie.EOL)) ?? []; | ||
A(s, t); | ||
} | ||
return originalPosition; | ||
return o; | ||
} | ||
function addContextLinesToFrame(lines, frame, linesOfContext = 5) { | ||
const maxLines = lines.length; | ||
const sourceLine = Math.max(Math.min(maxLines - 1, frame.lineno - 1), 0); | ||
frame.pre_context = lines.slice(Math.max(0, sourceLine - linesOfContext), sourceLine).map((line) => snipLine(line, 0)); | ||
frame.context_line = snipLine(lines[Math.min(maxLines - 1, sourceLine)], frame.colno || 0); | ||
frame.post_context = lines.slice(Math.min(sourceLine + 1, maxLines), sourceLine + 1 + linesOfContext).map((line) => snipLine(line, 0)); | ||
function A(e, t, n = 5) { | ||
const o = e.length, i = Math.max(Math.min(o - 1, t.lineno - 1), 0); | ||
t.pre_context = e.slice(Math.max(0, i - n), i).map((r) => v(r, 0)), t.context_line = v(e[Math.min(o - 1, i)], t.colno || 0), t.post_context = e.slice(Math.min(i + 1, o), i + 1 + n).map((r) => v(r, 0)); | ||
} | ||
function snipLine(line, colno) { | ||
let newLine = line; | ||
const lineLength = newLine.length; | ||
if (lineLength <= 150) { | ||
return newLine; | ||
} | ||
if (colno > lineLength) { | ||
colno = lineLength; | ||
} | ||
let start = Math.max(colno - 60, 0); | ||
if (start < 5) { | ||
start = 0; | ||
} | ||
let end = Math.min(start + 140, lineLength); | ||
if (end > lineLength - 5) { | ||
end = lineLength; | ||
} | ||
if (end === lineLength) { | ||
start = Math.max(end - 140, 0); | ||
} | ||
newLine = newLine.slice(start, end); | ||
if (start > 0) { | ||
newLine = `'{snip} ${newLine}`; | ||
} | ||
if (end < lineLength) { | ||
newLine += " {snip}"; | ||
} | ||
return newLine; | ||
function v(e, t) { | ||
let n = e; | ||
const o = n.length; | ||
if (o <= 150) | ||
return n; | ||
t > o && (t = o); | ||
let i = Math.max(t - 60, 0); | ||
i < 5 && (i = 0); | ||
let r = Math.min(i + 140, o); | ||
return r > o - 5 && (r = o), r === o && (i = Math.max(r - 140, 0)), n = n.slice(i, r), i > 0 && (n = `'{snip} ${n}`), r < o && (n += " {snip}"), n; | ||
} | ||
function isValidSentryStackFrame(frame) { | ||
return !!frame.filename && !!frame.lineno && !!frame.colno; | ||
function ce(e) { | ||
return !!e.filename && !!e.lineno && !!e.colno; | ||
} | ||
function contextLinesHandler(req, res) { | ||
if (req.method !== "PUT") { | ||
res.writeHead(405); | ||
res.end(); | ||
function le(e, t) { | ||
if (e.method !== "PUT") { | ||
t.writeHead(405), t.end(); | ||
return; | ||
} | ||
let requestBody = ""; | ||
req.on("data", (chunk) => { | ||
requestBody += chunk; | ||
}); | ||
req.on("end", async () => { | ||
const stacktrace = parseStackTrace(requestBody); | ||
if (!stacktrace) { | ||
res.writeHead(500); | ||
res.end(); | ||
let n = ""; | ||
e.on("data", (o) => { | ||
n += o; | ||
}), e.on("end", async () => { | ||
const o = se(n); | ||
if (!o) { | ||
t.writeHead(500), t.end(); | ||
return; | ||
} | ||
for (const frame of stacktrace.frames ?? []) { | ||
if (!isValidSentryStackFrame(frame) || // let's ignore dependencies for now with this naive check | ||
frame.filename.includes("/node_modules/")) { | ||
for (const r of o.frames ?? []) { | ||
if (!ce(r) || // let's ignore dependencies for now with this naive check | ||
r.filename.includes("/node_modules/")) | ||
continue; | ||
} | ||
const { filename } = frame; | ||
if (filename.includes("://")) { | ||
const generatedCode = await getGeneratedCodeFromServer(frame.filename); | ||
if (!generatedCode) { | ||
const { filename: s } = r; | ||
if (s.includes("://")) { | ||
const a = await re(r.filename); | ||
if (!a) | ||
continue; | ||
const u = a.match(/\/\/# sourceMappingURL=data:application\/json;base64,(.*)/); | ||
if (u && u[1]) { | ||
const c = u[1], l = Buffer.from(c, "base64").toString("utf-8"); | ||
await ae(l, r); | ||
} | ||
const inlineSourceMapMatch = generatedCode.match(/\/\/# sourceMappingURL=data:application\/json;base64,(.*)/); | ||
if (inlineSourceMapMatch && inlineSourceMapMatch[1]) { | ||
const sourceMapBase64 = inlineSourceMapMatch[1]; | ||
const sourceMapContent = Buffer.from(sourceMapBase64, "base64").toString("utf-8"); | ||
await applySourceContextToFrame(sourceMapContent, frame); | ||
} | ||
} else if (!filename.includes(":")) { | ||
} else if (!s.includes(":")) | ||
try { | ||
const lines = readFileSync(filename, { encoding: "utf-8" }).split(/\r?\n/); | ||
addContextLinesToFrame(lines, frame); | ||
} catch (err) { | ||
if (err.code !== "ENOENT") { | ||
throw err; | ||
} | ||
const a = $(s, { encoding: "utf-8" }).split(/\r?\n/); | ||
A(a, r); | ||
} catch (a) { | ||
if (a.code !== "ENOENT") | ||
throw a; | ||
} | ||
} | ||
} | ||
const responseJson = JSON.stringify(stacktrace); | ||
res.writeHead(200, { "Content-Type": "application/json" }); | ||
res.end(responseJson); | ||
const i = JSON.stringify(o); | ||
t.writeHead(200, { "Content-Type": "application/json" }), t.end(i); | ||
}); | ||
} | ||
const defaultLogger = { | ||
info: (message) => console.log("🔎 [Spotlight]", message), | ||
warn: (message) => console.warn("🔎 [Spotlight]", message), | ||
error: (message) => console.error("🔎 [Spotlight]", message), | ||
debug: (message) => debugEnabled && console.debug("🔎 [Spotlight]", message) | ||
const T = { | ||
info: (e) => console.log("🔎 [Spotlight]", e), | ||
warn: (e) => console.warn("🔎 [Spotlight]", e), | ||
error: (e) => console.error("🔎 [Spotlight]", e), | ||
debug: (e) => N && console.debug("🔎 [Spotlight]", e) | ||
}; | ||
let injectedLogger = void 0; | ||
let debugEnabled = false; | ||
function activateLogger(logger2) { | ||
injectedLogger = logger2; | ||
let S, N = !1; | ||
function de(e) { | ||
S = e; | ||
} | ||
function enableDebugLogging(debug) { | ||
debugEnabled = debug; | ||
function ue(e) { | ||
N = e; | ||
} | ||
const logger = { | ||
info: (message) => (injectedLogger || defaultLogger).info(message), | ||
warn: (message) => (injectedLogger || defaultLogger).warn(message), | ||
error: (message) => (injectedLogger || defaultLogger).error(message), | ||
debug: (message) => (injectedLogger || defaultLogger).debug(message) | ||
const d = { | ||
info: (e) => (S || T).info(e), | ||
warn: (e) => (S || T).warn(e), | ||
error: (e) => (S || T).error(e), | ||
debug: (e) => (S || T).debug(e) | ||
}; | ||
class MessageBuffer { | ||
constructor(size = 100) { | ||
__publicField(this, "size"); | ||
__publicField(this, "items"); | ||
__publicField(this, "writePos", 0); | ||
__publicField(this, "head", 0); | ||
__publicField(this, "timeout", 10); | ||
__publicField(this, "readers", /* @__PURE__ */ new Map()); | ||
this.size = size; | ||
this.items = new Array(size); | ||
class he { | ||
constructor(t = 100) { | ||
h(this, "size"); | ||
h(this, "items"); | ||
h(this, "writePos", 0); | ||
h(this, "head", 0); | ||
h(this, "timeout", 10); | ||
h(this, "readers", /* @__PURE__ */ new Map()); | ||
this.size = t, this.items = new Array(t); | ||
} | ||
put(item) { | ||
const curTime = (/* @__PURE__ */ new Date()).getTime(); | ||
this.items[this.writePos % this.size] = [curTime, item]; | ||
this.writePos += 1; | ||
if (this.head === this.writePos) { | ||
put(t) { | ||
const n = (/* @__PURE__ */ new Date()).getTime(); | ||
this.items[this.writePos % this.size] = [n, t], this.writePos += 1, this.head === this.writePos && (this.head += 1); | ||
const o = n - this.timeout * 1e3; | ||
let i; | ||
for (; this.head < this.writePos && (i = this.items[this.head % this.size], !(i === void 0 || i[0] > o)); ) | ||
this.head += 1; | ||
} | ||
const minTime = curTime - this.timeout * 1e3; | ||
let atItem; | ||
while (this.head < this.writePos) { | ||
atItem = this.items[this.head % this.size]; | ||
if (atItem === void 0) break; | ||
if (atItem[0] > minTime) break; | ||
this.head += 1; | ||
} | ||
} | ||
subscribe(callback) { | ||
const readerId = generateUuidv4(); | ||
this.readers.set(readerId, callback); | ||
setTimeout(() => this.stream(readerId)); | ||
return readerId; | ||
subscribe(t) { | ||
const n = pe(); | ||
return this.readers.set(n, t), setTimeout(() => this.stream(n)), n; | ||
} | ||
unsubscribe(readerId) { | ||
this.readers.delete(readerId); | ||
unsubscribe(t) { | ||
this.readers.delete(t); | ||
} | ||
stream(readerId, readPos = this.head) { | ||
const cb = this.readers.get(readerId); | ||
if (!cb) return; | ||
let atReadPos = readPos; | ||
let item; | ||
while (true) { | ||
item = this.items[atReadPos % this.size]; | ||
if (typeof item === "undefined" || atReadPos >= this.writePos) { | ||
break; | ||
} | ||
cb(item[1]); | ||
atReadPos += 1; | ||
} | ||
setTimeout(() => this.stream(readerId, atReadPos), 500); | ||
stream(t, n = this.head) { | ||
const o = this.readers.get(t); | ||
if (!o) return; | ||
let i = n, r; | ||
for (; r = this.items[i % this.size], !(typeof r > "u" || i >= this.writePos); ) | ||
o(r[1]), i += 1; | ||
setTimeout(() => this.stream(t, i), 500); | ||
} | ||
clear() { | ||
this.items = new Array(this.size); | ||
this.writePos = 0; | ||
this.head = 0; | ||
this.readers = /* @__PURE__ */ new Map(); | ||
this.items = new Array(this.size), this.writePos = 0, this.head = 0, this.readers = /* @__PURE__ */ new Map(); | ||
} | ||
} | ||
function generateUuidv4() { | ||
let dt = (/* @__PURE__ */ new Date()).getTime(); | ||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { | ||
let rnd = Math.random() * 16; | ||
rnd = (dt + rnd) % 16 | 0; | ||
dt = Math.floor(dt / 16); | ||
return (c === "x" ? rnd : rnd & 3 | 8).toString(16); | ||
function pe() { | ||
let e = (/* @__PURE__ */ new Date()).getTime(); | ||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(t) { | ||
let n = Math.random() * 16; | ||
return n = (e + n) % 16 | 0, e = Math.floor(e / 16), (t === "x" ? n : n & 3 | 8).toString(16); | ||
}); | ||
} | ||
function getCorsHeader() { | ||
return { | ||
"Access-Control-Allow-Origin": "*", | ||
"Access-Control-Allow-Credentials": "true", | ||
"Access-Control-Allow-Headers": "*", | ||
"Access-Control-Allow-Methods": "GET,POST,PUT,OPTIONS,DELETE,PATCH" | ||
}; | ||
} | ||
function enableCORS(handler) { | ||
return function corsMiddleware(req, res, pathname, searchParams) { | ||
const headers = { | ||
...getCorsHeader(), | ||
...getSpotlightHeader() | ||
const b = (e, t = {}) => (...n) => _({ name: e.name, ...t }, () => e(...n)), I = { | ||
"Access-Control-Allow-Origin": "*", | ||
"Access-Control-Allow-Credentials": "true", | ||
"Access-Control-Allow-Headers": "*", | ||
"Access-Control-Allow-Methods": "GET,POST,PUT,OPTIONS,DELETE,PATCH" | ||
}, D = { | ||
"X-Powered-by": oe | ||
}, x = (e) => b( | ||
(t, n, o, i) => { | ||
const r = { | ||
...I, | ||
...D | ||
}; | ||
for (const [header, value] of Object.entries(headers)) { | ||
res.setHeader(header, value); | ||
} | ||
if (req.method === "OPTIONS") { | ||
res.writeHead(204, { | ||
for (const [s, a] of Object.entries(r)) | ||
n.setHeader(s, a); | ||
if (t.method === "OPTIONS") { | ||
n.writeHead(204, { | ||
"Cache-Control": "no-cache" | ||
}); | ||
res.end(); | ||
}), n.end(); | ||
return; | ||
} | ||
return handler(req, res, pathname, searchParams); | ||
}; | ||
} | ||
function getSpotlightHeader() { | ||
return { | ||
"X-Powered-by": SERVER_IDENTIFIER | ||
}; | ||
} | ||
function streamRequestHandler(buffer2, incomingPayload) { | ||
return function handleStreamRequest(req, res, pathname, searchParams) { | ||
if (req.method === "GET" && req.headers.accept && req.headers.accept === "text/event-stream" && pathname === "/stream") { | ||
res.writeHead(200, { | ||
"Content-Type": "text/event-stream", | ||
"Cache-Control": "no-cache", | ||
Connection: "keep-alive" | ||
}); | ||
res.flushHeaders(); | ||
res.write("\n"); | ||
const sub = buffer2.subscribe(([payloadType, data]) => { | ||
logger.debug("🕊️ sending to Spotlight"); | ||
res.write(`event:${payloadType} | ||
return e(t, n, o, i); | ||
}, | ||
{ name: "enableCORS", op: "sidecar.http.middleware.cors" } | ||
), fe = (e, t) => function(o, i, r, s) { | ||
if (o.method === "GET" && o.headers.accept && o.headers.accept === "text/event-stream" && r === "/stream") { | ||
i.writeHead(200, { | ||
"Content-Type": "text/event-stream", | ||
"Cache-Control": "no-cache", | ||
Connection: "keep-alive" | ||
}), i.flushHeaders(), i.write(` | ||
`); | ||
for (const line of data.split("\n")) { | ||
res.write(`data:${line} | ||
const a = e.subscribe(([u, c]) => { | ||
d.debug("🕊️ sending to Spotlight"), i.write(`event:${u} | ||
`); | ||
} | ||
res.write("\n"); | ||
}); | ||
req.on("close", () => { | ||
buffer2.unsubscribe(sub); | ||
res.end(); | ||
}); | ||
} else if (req.method === "POST") { | ||
logger.debug(`📩 Received event`); | ||
let body = ""; | ||
let stream = req; | ||
const encoding = req.headers["content-encoding"]; | ||
if (encoding === "gzip") { | ||
stream = req.pipe(createGunzip()); | ||
} else if (encoding === "deflate") { | ||
stream = req.pipe(createInflate()); | ||
for (const l of c.split(` | ||
`)) | ||
i.write(`data:${l} | ||
`); | ||
i.write(` | ||
`); | ||
}); | ||
o.on("close", () => { | ||
e.unsubscribe(a), i.end(); | ||
}); | ||
} else if (o.method === "POST") { | ||
d.debug("📩 Received event"); | ||
let a = "", u = o; | ||
const c = o.headers["content-encoding"]; | ||
c === "gzip" ? u = o.pipe(q()) : c === "deflate" && (u = o.pipe(ee())), u.on("readable", () => { | ||
let l; | ||
for (; (l = u.read()) !== null; ) | ||
a += l; | ||
}), u.on("end", () => { | ||
var p, m; | ||
let l = (p = o.headers["content-type"]) == null ? void 0 : p.split(";")[0].toLocaleLowerCase(); | ||
if ((m = s == null ? void 0 : s.get("sentry_client")) != null && m.startsWith("sentry.javascript.browser") && o.headers.origin && (l = "application/x-sentry-envelope"), l ? e.put([l, a]) : d.warn("No content type, skipping payload..."), process.env.SPOTLIGHT_CAPTURE || t) { | ||
const g = (/* @__PURE__ */ new Date()).getTime(), w = `${(l == null ? void 0 : l.replace(/[^a-z0-9]/gi, "_")) || "no_content_type"}-${g}.txt`; | ||
t ? t(a) : (X(w).write(a), d.info(`🗃️ Saved data to ${w}`)); | ||
} | ||
stream.on("readable", () => { | ||
let chunk; | ||
while ((chunk = stream.read()) !== null) { | ||
body += chunk; | ||
} | ||
}); | ||
stream.on("end", () => { | ||
var _a, _b; | ||
let contentType = (_a = req.headers["content-type"]) == null ? void 0 : _a.split(";")[0].toLocaleLowerCase(); | ||
if (((_b = searchParams == null ? void 0 : searchParams.get("sentry_client")) == null ? void 0 : _b.startsWith("sentry.javascript.browser")) && req.headers.origin) { | ||
contentType = "application/x-sentry-envelope"; | ||
} | ||
if (!contentType) { | ||
logger.warn("No content type, skipping payload..."); | ||
} else { | ||
buffer2.put([contentType, body]); | ||
} | ||
if (process.env.SPOTLIGHT_CAPTURE || incomingPayload) { | ||
const timestamp = (/* @__PURE__ */ new Date()).getTime(); | ||
const filename = `${(contentType == null ? void 0 : contentType.replace(/[^a-z0-9]/gi, "_")) || "no_content_type"}-${timestamp}.txt`; | ||
if (incomingPayload) { | ||
incomingPayload(body); | ||
} else { | ||
createWriteStream(filename).write(body); | ||
logger.info(`🗃️ Saved data to ${filename}`); | ||
} | ||
} | ||
res.writeHead(200, { | ||
"Cache-Control": "no-cache", | ||
Connection: "keep-alive" | ||
}); | ||
res.end(); | ||
}); | ||
} else { | ||
error405(req, res); | ||
return; | ||
} | ||
}; | ||
} | ||
function fileServer(filesToServe) { | ||
return function serveFile(req, res, pathname) { | ||
let filePath = `${pathname || req.url}`; | ||
if (filePath === "/") { | ||
filePath = "/src/index.html"; | ||
} | ||
filePath = filePath.slice(1); | ||
const extName = extname(filePath); | ||
let contentType = "text/html"; | ||
switch (extName) { | ||
case ".js": | ||
contentType = "text/javascript"; | ||
break; | ||
case ".css": | ||
contentType = "text/css"; | ||
break; | ||
case ".json": | ||
contentType = "application/json"; | ||
break; | ||
} | ||
if (!Object.prototype.hasOwnProperty.call(filesToServe, filePath)) { | ||
error404(req, res); | ||
} else { | ||
res.writeHead(200, { "Content-Type": contentType }); | ||
res.end(filesToServe[filePath]); | ||
} | ||
}; | ||
} | ||
function handleHealthRequest(_req, res) { | ||
res.writeHead(200, { | ||
"Content-Type": "text/plain", | ||
...getCorsHeader(), | ||
...getSpotlightHeader() | ||
}); | ||
res.end("OK"); | ||
} | ||
function handleClearRequest(req, res) { | ||
if (req.method === "DELETE") { | ||
res.writeHead(200, { | ||
"Content-Type": "text/plain" | ||
i.writeHead(200, { | ||
"Cache-Control": "no-cache", | ||
Connection: "keep-alive" | ||
}), i.end(); | ||
}); | ||
clearBuffer(); | ||
res.end("Cleared"); | ||
} else { | ||
error405(req, res); | ||
j(o, i); | ||
return; | ||
} | ||
}, me = (e) => function(n, o, i) { | ||
let r = `${i || n.url}`; | ||
r === "/" && (r = "/src/index.html"), r = r.slice(1); | ||
const s = Z(r); | ||
let a = "text/html"; | ||
switch (s) { | ||
case ".js": | ||
a = "text/javascript"; | ||
break; | ||
case ".css": | ||
a = "text/css"; | ||
break; | ||
case ".json": | ||
a = "application/json"; | ||
break; | ||
} | ||
Object.hasOwn(e, r) ? (o.writeHead(200, { "Content-Type": a }), o.end(e[r])) : y(n, o); | ||
}; | ||
function ge(e, t) { | ||
t.writeHead(200, { | ||
"Content-Type": "text/plain", | ||
...I, | ||
...D | ||
}), t.end("OK"); | ||
} | ||
function openRequestHandler(basePath = process.cwd()) { | ||
return (req, res) => { | ||
if (req.method !== "POST") { | ||
res.writeHead(405); | ||
res.end(); | ||
function we(e, t) { | ||
e.method === "DELETE" ? (t.writeHead(200, { | ||
"Content-Type": "text/plain" | ||
}), be(), t.end("Cleared")) : j(e, t); | ||
} | ||
function xe(e = process.cwd()) { | ||
return (t, n) => { | ||
if (t.method !== "POST") { | ||
n.writeHead(405), n.end(); | ||
return; | ||
} | ||
let requestBody = ""; | ||
req.on("data", (chunk) => { | ||
requestBody += chunk; | ||
}); | ||
req.on("end", () => { | ||
const targetPath = resolve(basePath, requestBody); | ||
logger.debug(`Launching editor for ${targetPath}`); | ||
launchEditor( | ||
let o = ""; | ||
t.on("data", (i) => { | ||
o += i; | ||
}), t.on("end", () => { | ||
const i = Q(e, o); | ||
d.debug(`Launching editor for ${i}`), W( | ||
// filename:line:column | ||
// both line and column are optional | ||
targetPath, | ||
i, | ||
// callback if failed to launch (optional) | ||
(fileName, errorMsg) => { | ||
logger.error(`Failed to launch editor for ${fileName}: ${errorMsg}`); | ||
(r, s) => { | ||
d.error(`Failed to launch editor for ${r}: ${s}`); | ||
} | ||
); | ||
res.writeHead(204); | ||
res.end(); | ||
), n.writeHead(204), n.end(); | ||
}); | ||
}; | ||
} | ||
function errorResponse(code) { | ||
return (_req, res) => { | ||
res.writeHead(code); | ||
res.end(); | ||
}; | ||
function k(e) { | ||
return b( | ||
(t, n) => { | ||
n.writeHead(e), n.end(); | ||
}, | ||
{ name: `HTTP ${e}`, op: `sidecar.http.error.${e}`, attributes: { "http.response.status_code": e } } | ||
); | ||
} | ||
const error404 = errorResponse(404); | ||
const error405 = errorResponse(405); | ||
function startServer(buffer2, port, basePath, filesToServe, incomingPayload) { | ||
if (basePath && !filesToServe) { | ||
filesToServe = { | ||
"/src/index.html": readFileSync(join(basePath, "src/index.html")), | ||
"/assets/main.js": readFileSync(join(basePath, "assets/main.js")) | ||
}; | ||
} | ||
const ROUTES = [ | ||
[/^\/health$/, handleHealthRequest], | ||
[/^\/clear$/, enableCORS(handleClearRequest)], | ||
[/^\/stream$|^\/api\/\d+\/envelope\/?$/, enableCORS(streamRequestHandler(buffer2, incomingPayload))], | ||
[/^\/open$/, enableCORS(openRequestHandler(basePath))], | ||
[RegExp(`^${CONTEXT_LINES_ENDPOINT}$`), enableCORS(contextLinesHandler)], | ||
[/^.+$/, filesToServe != null ? enableCORS(fileServer(filesToServe)) : error404] | ||
]; | ||
const server = createServer((req, res) => { | ||
const url = req.url; | ||
if (!url) { | ||
return error404(req, res); | ||
} | ||
const { pathname, searchParams } = new URL(url, `http://${req.headers.host || "localhost"}`); | ||
const route = ROUTES.find((route2) => route2[0].test(pathname)); | ||
if (!route) { | ||
return error404(req, res); | ||
} | ||
return route[1](req, res, pathname, searchParams); | ||
const y = k(404), j = k(405); | ||
function Se(e, t, n, o, i) { | ||
n && !o && (o = { | ||
"/src/index.html": $(L(n, "src/index.html")), | ||
"/assets/main.js": $(L(n, "assets/main.js")) | ||
}); | ||
server.on("error", handleServerError); | ||
server.listen(port, () => { | ||
handleServerListen(port, basePath); | ||
const r = [ | ||
[/^\/health$/, ge], | ||
[/^\/clear$/, x(we)], | ||
[/^\/stream$|^\/api\/\d+\/envelope\/?$/, x(fe(e, i))], | ||
[/^\/open$/, x(xe(n))], | ||
[RegExp(`^${te}$`), x(le)], | ||
[/^.+$/, o != null ? x(me(o)) : y] | ||
], s = K((c, l) => { | ||
var R; | ||
const p = c.url; | ||
if (!p) | ||
return y(c, l); | ||
const m = c.headers.host || "localhost", { pathname: g, searchParams: w } = new URL(p, `http://${m}`), P = r.find((C) => C[0].test(g)); | ||
return P ? _( | ||
{ | ||
name: `HTTP ${c.method} ${g}`, | ||
op: `sidecar.http.${(R = c.method) == null ? void 0 : R.toLowerCase()}`, | ||
forceTransaction: !0, | ||
attributes: { | ||
"http.request.method": c.method, | ||
"http.request.url": p, | ||
"http.request.query": w.toString(), | ||
"server.address": m, | ||
"server.port": c.socket.localPort | ||
} | ||
}, | ||
(C) => { | ||
const H = V(); | ||
l.appendHeader( | ||
"server-timing", | ||
`sentryTrace;desc="${H["sentry-trace"]}", baggage;desc="${H.baggage}"` | ||
); | ||
const G = P[1](c, l, g, w); | ||
return C.setAttribute("http.response.status_code", l.statusCode), G; | ||
} | ||
) : y(c, l); | ||
}); | ||
return server; | ||
function handleServerError(e) { | ||
if ("code" in e && e.code === "EADDRINUSE") { | ||
logger.info(`Port ${port} in use, retrying...`); | ||
setTimeout(() => { | ||
server.close(); | ||
server.listen(port); | ||
logger.info(`Port ${port} in use, retrying...`); | ||
}, 5e3); | ||
} | ||
return s.on("error", a), s.listen(t, () => { | ||
u(t, n); | ||
}), s; | ||
function a(c) { | ||
"code" in c && c.code === "EADDRINUSE" ? (d.info(`Port ${t} in use, retrying...`), setTimeout(() => { | ||
s.close(), s.listen(t), d.info(`Port ${t} in use, retrying...`); | ||
}, 5e3)) : J(c); | ||
} | ||
function handleServerListen(port2, basePath2) { | ||
logger.info(`Sidecar listening on ${port2}`); | ||
if (basePath2) { | ||
logger.info(`You can open: http://localhost:${port2} to see the Spotlight overlay directly`); | ||
} | ||
function u(c, l) { | ||
d.info(`Sidecar listening on ${c}`), l && d.info(`You can open: http://localhost:${c} to see the Spotlight overlay directly`); | ||
} | ||
} | ||
let serverInstance; | ||
const buffer = new MessageBuffer(); | ||
const isValidPort = (value) => { | ||
if (typeof value === "string") { | ||
const portNumber = Number(value); | ||
return /^\d+$/.test(value) && portNumber > 0 && portNumber <= 65535; | ||
} | ||
return value > 0 && value <= 65535; | ||
}; | ||
function isSidecarRunning(port) { | ||
return new Promise((resolve2) => { | ||
const options = { | ||
let f; | ||
const U = new he(), Te = b( | ||
(e) => { | ||
if (typeof e == "string") { | ||
const t = Number(e); | ||
return /^\d+$/.test(e) && t > 0 && t <= 65535; | ||
} | ||
return e > 0 && e <= 65535; | ||
}, | ||
{ name: "isValidPort", op: "sidecar.server.portCheck" } | ||
), ye = b( | ||
(e) => new Promise((t) => { | ||
const o = Y({ | ||
hostname: "localhost", | ||
port, | ||
port: e, | ||
path: "/health", | ||
@@ -479,71 +369,39 @@ method: "GET", | ||
headers: { Connection: "close" } | ||
}; | ||
const healthReq = get(options, (res) => { | ||
const serverIdentifier = res.headers["x-powered-by"]; | ||
if (serverIdentifier === "spotlight-by-sentry") { | ||
resolve2(true); | ||
} else { | ||
resolve2(false); | ||
} | ||
}, (i) => { | ||
const r = i.headers["x-powered-by"]; | ||
t(r === "spotlight-by-sentry"); | ||
}); | ||
healthReq.on("error", () => { | ||
resolve2(false); | ||
}); | ||
healthReq.end(); | ||
}); | ||
} | ||
function setupSidecar({ | ||
port, | ||
logger: customLogger, | ||
basePath, | ||
filesToServe, | ||
debug, | ||
incomingPayload | ||
o.on("error", () => { | ||
t(!1); | ||
}), o.end(); | ||
}), | ||
{ name: "isSidecarRunning", op: "sidecar.server.collideCheck" } | ||
); | ||
function Oe({ | ||
port: e, | ||
logger: t, | ||
basePath: n, | ||
filesToServe: o, | ||
debug: i, | ||
incomingPayload: r | ||
} = {}) { | ||
let sidecarPort = DEFAULT_PORT; | ||
if (customLogger) { | ||
activateLogger(customLogger); | ||
} | ||
if (debug || process.env.SPOTLIGHT_DEBUG) { | ||
enableDebugLogging(true); | ||
} | ||
if (port && !isValidPort(port)) { | ||
logger.info("Please provide a valid port."); | ||
process.exit(1); | ||
} else if (port) { | ||
sidecarPort = typeof port === "string" ? Number(port) : port; | ||
} | ||
isSidecarRunning(sidecarPort).then((isRunning) => { | ||
if (isRunning) { | ||
logger.info(`Sidecar is already running on port ${sidecarPort}`); | ||
} else { | ||
if (!serverInstance) { | ||
serverInstance = startServer(buffer, sidecarPort, basePath, filesToServe, incomingPayload); | ||
} | ||
} | ||
let s = ne; | ||
t && de(t), (i || process.env.SPOTLIGHT_DEBUG) && ue(!0), e && !Te(e) ? (d.info("Please provide a valid port."), process.exit(1)) : e && (s = typeof e == "string" ? Number(e) : e), ye(s).then((a) => { | ||
a ? d.info(`Sidecar is already running on port ${s}`) : f || (f = Se(U, s, n, o, r)); | ||
}); | ||
} | ||
function clearBuffer() { | ||
buffer.clear(); | ||
function be() { | ||
U.clear(); | ||
} | ||
let forceShutdown = false; | ||
function shutdown() { | ||
if (forceShutdown || !serverInstance) { | ||
logger.info("Bye."); | ||
process.exit(0); | ||
} | ||
if (serverInstance) { | ||
forceShutdown = true; | ||
logger.info("Shutting down server gracefully..."); | ||
serverInstance.close(); | ||
serverInstance.closeAllConnections(); | ||
} | ||
} | ||
process.on("SIGINT", shutdown); | ||
process.on("SIGTERM", shutdown); | ||
let M = !1; | ||
const F = () => { | ||
(M || !f) && (d.info("Bye."), process.exit(0)), f && (M = !0, d.info("Shutting down server gracefully..."), f.close(), f.closeAllConnections()); | ||
}; | ||
process.on("SIGINT", F); | ||
process.on("SIGTERM", F); | ||
export { | ||
clearBuffer, | ||
setupSidecar, | ||
shutdown | ||
be as clearBuffer, | ||
Oe as setupSidecar, | ||
F as shutdown | ||
}; | ||
//# sourceMappingURL=main.js.map |
{ | ||
"name": "@spotlightjs/sidecar", | ||
"description": "A small proxy server to capture and forward data from backend services to Spotlight.", | ||
"version": "1.9.2", | ||
"version": "1.9.3", | ||
"license": "Apache-2.0", | ||
"type": "module", | ||
"files": [ | ||
"dist", | ||
"src" | ||
"dist" | ||
], | ||
"bin": { | ||
"spotlight-sidecar": "./server.js" | ||
"spotlight-sidecar": "dist/server.js" | ||
}, | ||
@@ -23,3 +22,4 @@ "main": "dist/main.js", | ||
"./vite-plugin": { | ||
"import": "./src/vite-plugin.js" | ||
"import": "./dist/vite-plugin.js", | ||
"types": "./dist/vite-plugin.d.ts" | ||
}, | ||
@@ -29,8 +29,6 @@ "./constants": { | ||
"types": "./dist/constants.d.ts" | ||
}, | ||
"./run": { | ||
"import": "./src/run.js" | ||
} | ||
}, | ||
"dependencies": { | ||
"@sentry/node": "^8.42.0", | ||
"kleur": "^4.1.5", | ||
@@ -37,0 +35,0 @@ "launch-editor": "^2.9.1", |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
56810
4
17
502
2
+ Added@sentry/node@^8.42.0
+ Added@opentelemetry/api@1.9.0(transitive)
+ Added@opentelemetry/api-logs@0.53.00.57.10.57.2(transitive)
+ Added@opentelemetry/context-async-hooks@1.30.1(transitive)
+ Added@opentelemetry/core@1.30.1(transitive)
+ Added@opentelemetry/instrumentation@0.53.00.57.10.57.2(transitive)
+ Added@opentelemetry/instrumentation-amqplib@0.46.1(transitive)
+ Added@opentelemetry/instrumentation-connect@0.43.0(transitive)
+ Added@opentelemetry/instrumentation-dataloader@0.16.0(transitive)
+ Added@opentelemetry/instrumentation-express@0.47.0(transitive)
+ Added@opentelemetry/instrumentation-fastify@0.44.1(transitive)
+ Added@opentelemetry/instrumentation-fs@0.19.0(transitive)
+ Added@opentelemetry/instrumentation-generic-pool@0.43.0(transitive)
+ Added@opentelemetry/instrumentation-graphql@0.47.0(transitive)
+ Added@opentelemetry/instrumentation-hapi@0.45.1(transitive)
+ Added@opentelemetry/instrumentation-http@0.57.1(transitive)
+ Added@opentelemetry/instrumentation-ioredis@0.47.0(transitive)
+ Added@opentelemetry/instrumentation-kafkajs@0.7.0(transitive)
+ Added@opentelemetry/instrumentation-knex@0.44.0(transitive)
+ Added@opentelemetry/instrumentation-koa@0.47.0(transitive)
+ Added@opentelemetry/instrumentation-lru-memoizer@0.44.0(transitive)
+ Added@opentelemetry/instrumentation-mongodb@0.51.0(transitive)
+ Added@opentelemetry/instrumentation-mongoose@0.46.0(transitive)
+ Added@opentelemetry/instrumentation-mysql@0.45.0(transitive)
+ Added@opentelemetry/instrumentation-mysql2@0.45.0(transitive)
+ Added@opentelemetry/instrumentation-nestjs-core@0.44.0(transitive)
+ Added@opentelemetry/instrumentation-pg@0.50.0(transitive)
+ Added@opentelemetry/instrumentation-redis-4@0.46.0(transitive)
+ Added@opentelemetry/instrumentation-tedious@0.18.0(transitive)
+ Added@opentelemetry/instrumentation-undici@0.10.0(transitive)
+ Added@opentelemetry/redis-common@0.36.2(transitive)
+ Added@opentelemetry/resources@1.30.1(transitive)
+ Added@opentelemetry/sdk-trace-base@1.30.1(transitive)
+ Added@opentelemetry/semantic-conventions@1.27.01.28.01.30.0(transitive)
+ Added@opentelemetry/sql-common@0.40.1(transitive)
+ Added@prisma/instrumentation@5.22.0(transitive)
+ Added@sentry/core@8.55.0(transitive)
+ Added@sentry/node@8.55.0(transitive)
+ Added@sentry/opentelemetry@8.55.0(transitive)
+ Added@types/connect@3.4.36(transitive)
+ Added@types/mysql@2.15.26(transitive)
+ Added@types/node@22.13.9(transitive)
+ Added@types/pg@8.6.1(transitive)
+ Added@types/pg-pool@2.0.6(transitive)
+ Added@types/shimmer@1.2.0(transitive)
+ Added@types/tedious@4.0.14(transitive)
+ Addedacorn@8.14.0(transitive)
+ Addedacorn-import-attributes@1.9.5(transitive)
+ Addedcjs-module-lexer@1.4.3(transitive)
+ Addeddebug@4.4.0(transitive)
+ Addedforwarded-parse@2.1.2(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedimport-in-the-middle@1.13.1(transitive)
+ Addedis-core-module@2.16.1(transitive)
+ Addedmodule-details-from-path@1.0.3(transitive)
+ Addedms@2.1.3(transitive)
+ Addedpath-parse@1.0.7(transitive)
+ Addedpg-int8@1.0.1(transitive)
+ Addedpg-protocol@1.7.1(transitive)
+ Addedpg-types@2.2.0(transitive)
+ Addedpostgres-array@2.0.0(transitive)
+ Addedpostgres-bytea@1.0.0(transitive)
+ Addedpostgres-date@1.0.7(transitive)
+ Addedpostgres-interval@1.2.0(transitive)
+ Addedrequire-in-the-middle@7.5.2(transitive)
+ Addedresolve@1.22.10(transitive)
+ Addedsemver@7.7.1(transitive)
+ Addedshimmer@1.2.1(transitive)
+ Addedsupports-preserve-symlinks-flag@1.0.0(transitive)
+ Addedundici-types@6.20.0(transitive)
+ Addedxtend@4.0.2(transitive)