@rallycry/conveyor-incidents 0.3.2 → 0.3.3
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/README.md +28 -0
- package/dist/index.d.ts +22 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,6 +91,29 @@ await incidents.report({
|
|
|
91
91
|
});
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
### Reporter metadata (`reporter`)
|
|
95
|
+
|
|
96
|
+
For any **user-initiated** report, send who filed it and what page they were on:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
await incidents.report({
|
|
100
|
+
title: "Riot login fails",
|
|
101
|
+
description: userMessage,
|
|
102
|
+
reporter: {
|
|
103
|
+
name: currentUser.displayName,
|
|
104
|
+
email: currentUser.email,
|
|
105
|
+
url: window.location.href,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Conveyor renders these as `Reported by:` / `URL:` lines in the card description's
|
|
111
|
+
`---` metadata footer, and surfaces them on the card's Slack message. Unlike
|
|
112
|
+
`createdBy`, no project membership is required — this is descriptive metadata,
|
|
113
|
+
so an end-user report keeps an identity instead of reading as an anonymous
|
|
114
|
+
automated alert. Lines your own footer already writes (e.g. your own `URL:`) are
|
|
115
|
+
not duplicated, and `url` must be an absolute `http(s)` URL.
|
|
116
|
+
|
|
94
117
|
### About `source` and `fingerprintKey`
|
|
95
118
|
|
|
96
119
|
Conveyor deduplicates reports by a fingerprint derived from `(projectId, source, fingerprintKey)` — or by `(projectId, source, title)` if `fingerprintKey` is omitted. **Use stable values** for `source` (e.g. `"prod-web"`, not `"node-pid-1234"`) so that repeat reports collapse into a single incident instead of creating noise.
|
|
@@ -161,6 +184,11 @@ client.report({
|
|
|
161
184
|
assigned?: string[]; // project-member emails; first match becomes assignee
|
|
162
185
|
review?: string[]; // project-member emails; every match becomes a reviewer
|
|
163
186
|
createdBy?: string; // project-member email; no match → "Conveyor Incidents" system user
|
|
187
|
+
reporter?: { // who filed it + where; rendered into the card's metadata footer
|
|
188
|
+
name?: string;
|
|
189
|
+
email?: string;
|
|
190
|
+
url?: string; // absolute http(s) URL of the page at report time
|
|
191
|
+
};
|
|
164
192
|
attachments?: Array<{
|
|
165
193
|
fileName: string;
|
|
166
194
|
mimeType: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -45,6 +45,21 @@ interface NetworkLogEntry {
|
|
|
45
45
|
responseSnippet?: string;
|
|
46
46
|
error?: string;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Who filed the report and where they were when they filed it. Unlike
|
|
50
|
+
* `createdBy`, this needs no Conveyor project membership — it's descriptive
|
|
51
|
+
* metadata, rendered into the card's description footer and onto the Slack
|
|
52
|
+
* card. Send it for every user-initiated report: without it a card from an
|
|
53
|
+
* end user is indistinguishable from an automated alert.
|
|
54
|
+
*/
|
|
55
|
+
interface ReportReporter {
|
|
56
|
+
/** Display name / username of the person filing the report. */
|
|
57
|
+
name?: string;
|
|
58
|
+
/** Contact email. Also used for attribution when it matches a member. */
|
|
59
|
+
email?: string;
|
|
60
|
+
/** Absolute http(s) URL of the page the user was on at report time. */
|
|
61
|
+
url?: string;
|
|
62
|
+
}
|
|
48
63
|
interface ReportIncidentInput {
|
|
49
64
|
title: string;
|
|
50
65
|
description?: string;
|
|
@@ -73,6 +88,12 @@ interface ReportIncidentInput {
|
|
|
73
88
|
* is attributed to the "Conveyor Incidents" system user.
|
|
74
89
|
*/
|
|
75
90
|
createdBy?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Reporter identity + the page they were on. Rendered into the card's
|
|
93
|
+
* description metadata footer (and the Slack card) by the server; lines the
|
|
94
|
+
* description already carries are not duplicated.
|
|
95
|
+
*/
|
|
96
|
+
reporter?: ReportReporter;
|
|
76
97
|
}
|
|
77
98
|
interface AttachmentResult {
|
|
78
99
|
fileName: string;
|
|
@@ -101,4 +122,4 @@ declare class ConveyorIncidents {
|
|
|
101
122
|
private uploadAttachment;
|
|
102
123
|
}
|
|
103
124
|
|
|
104
|
-
export { type AttachmentResult, type ConveyorCardType, ConveyorIncidents, ConveyorIncidentsError, type ConveyorIncidentsOptions, type IncidentAttachment, type IncidentSeverity, MAX_ATTACHMENT_BYTES, type NetworkCaptureOptions, type NetworkLogEntry, type ReportIncidentInput, type ReportIncidentResult, buildNetworkLogAttachment, clearNetworkLogEntries, getNetworkLogEntries, installNetworkCapture, uninstallNetworkCapture };
|
|
125
|
+
export { type AttachmentResult, type ConveyorCardType, ConveyorIncidents, ConveyorIncidentsError, type ConveyorIncidentsOptions, type IncidentAttachment, type IncidentSeverity, MAX_ATTACHMENT_BYTES, type NetworkCaptureOptions, type NetworkLogEntry, type ReportIncidentInput, type ReportIncidentResult, type ReportReporter, buildNetworkLogAttachment, clearNetworkLogEntries, getNetworkLogEntries, installNetworkCapture, uninstallNetworkCapture };
|
package/dist/index.js
CHANGED
|
@@ -127,7 +127,8 @@ var ConveyorIncidents = class {
|
|
|
127
127
|
type: input.type,
|
|
128
128
|
assigned: input.assigned,
|
|
129
129
|
review: input.review,
|
|
130
|
-
createdBy: input.createdBy
|
|
130
|
+
createdBy: input.createdBy,
|
|
131
|
+
reporter: normalizeReporter(input.reporter)
|
|
131
132
|
};
|
|
132
133
|
const res = await this.fetchImpl(`${this.baseUrl}/api/incidents/report`, {
|
|
133
134
|
method: "POST",
|
|
@@ -305,6 +306,12 @@ function redactUrl(rawUrl) {
|
|
|
305
306
|
function stripTrailingSlash(url) {
|
|
306
307
|
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
307
308
|
}
|
|
309
|
+
function normalizeReporter(reporter) {
|
|
310
|
+
if (!reporter) return void 0;
|
|
311
|
+
const entries = ["name", "email", "url"].map((key) => [key, reporter[key]?.trim()]).filter((entry) => !!entry[1]);
|
|
312
|
+
if (entries.length === 0) return void 0;
|
|
313
|
+
return Object.fromEntries(entries);
|
|
314
|
+
}
|
|
308
315
|
function extractId(payload, key) {
|
|
309
316
|
if (payload && typeof payload === "object" && key in payload) {
|
|
310
317
|
const value = payload[key];
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/index.ts"],"sourcesContent":["export class ConveyorIncidentsError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"ConveyorIncidentsError\";\n this.status = status;\n this.body = body;\n }\n}\n","import { ConveyorIncidentsError } from \"./errors.js\";\n\nexport { ConveyorIncidentsError } from \"./errors.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.conveyor.rallycryapp.com\";\nconst DEFAULT_SOURCE = \"client\";\nconst DEFAULT_SEVERITY: IncidentSeverity = \"medium\";\n\n/** Server-side per-file ceiling — keep in sync with MAX_ATTACHMENT_BYTES in\n * conveyor's incident-routes.ts. Checked client-side so an oversized file\n * fails with a descriptive `attachmentResults` error instead of an HTTP 413. */\nexport const MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\n\nexport type IncidentSeverity = \"critical\" | \"high\" | \"medium\" | \"low\";\n\nexport type ConveyorCardType = \"incident\" | \"suggestion\" | \"task\";\n\nexport interface ConveyorIncidentsOptions {\n apiKey: string;\n projectId: string;\n baseUrl?: string;\n fetch?: typeof fetch;\n}\n\nexport interface IncidentAttachment {\n fileName: string;\n mimeType: string;\n content: Uint8Array | string;\n}\n\nexport interface NetworkCaptureOptions {\n /** Maximum entries kept in memory. Defaults to 80. */\n maxEntries?: number;\n /** Maximum response body characters kept per captured entry. Defaults to 4000. */\n maxBodyChars?: number;\n /** Capture response snippets for successful tRPC batch calls as well as failures. Defaults to true. */\n captureTrpcResponses?: boolean;\n}\n\nexport interface NetworkLogEntry {\n timestamp: string;\n kind: \"fetch\";\n method: string;\n url: string;\n status: number | null;\n ok: boolean | null;\n durationMs: number;\n responseBytes: number | null;\n contentType?: string;\n requestTraceparent?: string;\n responseTraceparent?: string;\n trpcProcedures?: string[];\n responseSnippet?: string;\n error?: string;\n}\n\nexport interface ReportIncidentInput {\n title: string;\n description?: string;\n severity?: IncidentSeverity;\n source?: string;\n fingerprintKey?: string;\n plan?: string;\n attachments?: IncidentAttachment[];\n /** Card type to create. The server defaults to \"incident\" when omitted. */\n type?: ConveyorCardType;\n /**\n * Project-member emails. The FIRST email that resolves to a member of the\n * target project becomes the card's assignee; the rest are ignored.\n * Non-member emails are silently skipped.\n */\n assigned?: string[];\n /**\n * Project-member emails. EVERY email that resolves to a member of the\n * target project is added as a reviewer. Non-member emails are silently\n * skipped.\n */\n review?: string[];\n /**\n * Project-member email used as the card's creator. When omitted — or when\n * the email doesn't resolve to a member of the target project — the card\n * is attributed to the \"Conveyor Incidents\" system user.\n */\n createdBy?: string;\n}\n\nexport interface AttachmentResult {\n fileName: string;\n ok: boolean;\n fileId?: string;\n error?: string;\n}\n\nexport interface ReportIncidentResult {\n incidentId: string;\n /** Alias of `incidentId` — prefer this when reporting type \"suggestion\" or \"task\". */\n cardId: string;\n attachmentResults: AttachmentResult[];\n}\n\nlet networkCaptureState:\n | {\n installed: true;\n originalFetch: typeof fetch;\n entries: NetworkLogEntry[];\n options: Required<NetworkCaptureOptions>;\n }\n | undefined;\n\nconst DEFAULT_NETWORK_CAPTURE_OPTIONS: Required<NetworkCaptureOptions> = {\n maxEntries: 80,\n maxBodyChars: 4000,\n captureTrpcResponses: true,\n};\n\nexport function installNetworkCapture(options: NetworkCaptureOptions = {}): void {\n if (typeof globalThis.fetch !== \"function\") return;\n // Bind to globalThis: some browsers (notably Safari/WebKit) throw\n // \"Illegal invocation\" when the native fetch is called without its\n // original receiver, which a bare captured reference loses.\n const fetchImpl = globalThis.fetch.bind(globalThis);\n\n const resolvedOptions = {\n ...DEFAULT_NETWORK_CAPTURE_OPTIONS,\n ...options,\n };\n\n if (networkCaptureState) {\n networkCaptureState.options = resolvedOptions;\n return;\n }\n\n const entries: NetworkLogEntry[] = [];\n networkCaptureState = {\n installed: true,\n originalFetch: fetchImpl,\n entries,\n options: resolvedOptions,\n };\n\n globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const state = networkCaptureState;\n const originalFetch = state?.originalFetch ?? fetchImpl;\n const startedAt = Date.now();\n const timestamp = new Date(startedAt).toISOString();\n const request = normalizeRequest(input, init);\n\n try {\n const response = await originalFetch(input, init);\n const entry = await buildNetworkEntry({\n timestamp,\n request,\n response,\n durationMs: Date.now() - startedAt,\n options: state?.options ?? resolvedOptions,\n });\n pushNetworkEntry(entry);\n return response;\n } catch (err) {\n pushNetworkEntry({\n timestamp,\n kind: \"fetch\",\n method: request.method,\n url: redactUrl(request.url),\n status: null,\n ok: null,\n durationMs: Date.now() - startedAt,\n responseBytes: null,\n requestTraceparent: request.traceparent,\n trpcProcedures: extractTrpcProcedures(request.url),\n error: err instanceof Error ? err.message : String(err),\n });\n throw err;\n }\n };\n}\n\nexport function uninstallNetworkCapture(): void {\n if (!networkCaptureState) return;\n globalThis.fetch = networkCaptureState.originalFetch;\n networkCaptureState = undefined;\n}\n\nexport function clearNetworkLogEntries(): void {\n networkCaptureState?.entries.splice(0);\n}\n\nexport function getNetworkLogEntries(): NetworkLogEntry[] {\n return [...(networkCaptureState?.entries ?? [])];\n}\n\nexport function buildNetworkLogAttachment(\n fileName = \"network.log\",\n): IncidentAttachment | undefined {\n const entries = getNetworkLogEntries();\n if (entries.length === 0) return undefined;\n return {\n fileName,\n mimeType: \"application/x-ndjson\",\n content: entries.map((entry) => JSON.stringify(entry)).join(\"\\n\"),\n };\n}\n\nexport class ConveyorIncidents {\n private readonly apiKey: string;\n private readonly projectId: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: ConveyorIncidentsOptions) {\n if (!opts.apiKey) {\n throw new Error(\"ConveyorIncidents: apiKey is required\");\n }\n if (!opts.projectId) {\n throw new Error(\"ConveyorIncidents: projectId is required\");\n }\n const resolvedFetch = opts.fetch ?? globalThis.fetch;\n if (typeof resolvedFetch !== \"function\") {\n throw new Error(\"ConveyorIncidents: no global fetch available — pass opts.fetch on Node <18\");\n }\n this.apiKey = opts.apiKey;\n this.projectId = opts.projectId;\n this.baseUrl = stripTrailingSlash(opts.baseUrl ?? DEFAULT_BASE_URL);\n this.fetchImpl = resolvedFetch;\n }\n\n async report(input: ReportIncidentInput): Promise<ReportIncidentResult> {\n const body = {\n projectId: this.projectId,\n source: input.source ?? DEFAULT_SOURCE,\n severity: input.severity ?? DEFAULT_SEVERITY,\n title: input.title,\n description: input.description,\n plan: input.plan,\n fingerprintKey: input.fingerprintKey,\n type: input.type,\n assigned: input.assigned,\n review: input.review,\n createdBy: input.createdBy,\n };\n\n const res = await this.fetchImpl(`${this.baseUrl}/api/incidents/report`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n const payload = await readJson(res);\n\n if (!res.ok) {\n throw new ConveyorIncidentsError(\n `Failed to report incident: ${res.status} ${res.statusText}`,\n res.status,\n payload,\n );\n }\n\n const incidentId = extractId(payload, \"incidentId\") ?? extractId(payload, \"cardId\");\n\n if (!incidentId) {\n throw new ConveyorIncidentsError(\n \"Invalid response from /api/incidents/report: missing incidentId\",\n res.status,\n payload,\n );\n }\n\n const attachmentResults: AttachmentResult[] = [];\n if (input.attachments && input.attachments.length > 0) {\n for (const attachment of input.attachments) {\n attachmentResults.push(await this.uploadAttachment(incidentId, attachment));\n }\n }\n\n return { incidentId, cardId: incidentId, attachmentResults };\n }\n\n private async uploadAttachment(\n incidentId: string,\n attachment: IncidentAttachment,\n ): Promise<AttachmentResult> {\n try {\n const bytes =\n typeof attachment.content === \"string\"\n ? new TextEncoder().encode(attachment.content)\n : attachment.content;\n if (bytes.byteLength > MAX_ATTACHMENT_BYTES) {\n return {\n fileName: attachment.fileName,\n ok: false,\n error: `Attachment is ${bytes.byteLength} bytes — exceeds the ${MAX_ATTACHMENT_BYTES}-byte (5 MB) server limit; upload skipped`,\n };\n }\n const contentBase64 = bytesToBase64(bytes);\n const res = await this.fetchImpl(\n `${this.baseUrl}/api/incidents/${encodeURIComponent(incidentId)}/files`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({\n fileName: attachment.fileName,\n mimeType: attachment.mimeType,\n contentBase64,\n }),\n },\n );\n\n const payload = await readJson(res);\n\n if (!res.ok) {\n return {\n fileName: attachment.fileName,\n ok: false,\n error: formatErrorBody(res.status, res.statusText, payload),\n };\n }\n\n const fileId =\n payload && typeof payload === \"object\" && \"fileId\" in payload\n ? String((payload as { fileId: unknown }).fileId)\n : undefined;\n\n return { fileName: attachment.fileName, ok: true, fileId };\n } catch (err) {\n return {\n fileName: attachment.fileName,\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\nfunction pushNetworkEntry(entry: NetworkLogEntry): void {\n const state = networkCaptureState;\n if (!state) return;\n state.entries.push(entry);\n const overflow = state.entries.length - state.options.maxEntries;\n if (overflow > 0) state.entries.splice(0, overflow);\n}\n\nfunction normalizeRequest(\n input: RequestInfo | URL,\n init: RequestInit | undefined,\n): { method: string; url: string; traceparent?: string } {\n const request = typeof Request !== \"undefined\" && input instanceof Request ? input : undefined;\n const method = init?.method ?? request?.method ?? \"GET\";\n const url = request?.url ?? String(input);\n const traceparent =\n readHeader(init?.headers, \"traceparent\") ?? readHeader(request?.headers, \"traceparent\");\n return { method: method.toUpperCase(), url, traceparent };\n}\n\nasync function buildNetworkEntry(params: {\n timestamp: string;\n request: { method: string; url: string; traceparent?: string };\n response: Response;\n durationMs: number;\n options: Required<NetworkCaptureOptions>;\n}): Promise<NetworkLogEntry> {\n const { timestamp, request, response, durationMs, options } = params;\n const contentType = response.headers.get(\"content-type\") ?? undefined;\n const trpcProcedures = extractTrpcProcedures(request.url);\n const shouldCaptureBody =\n !response.ok || (options.captureTrpcResponses && trpcProcedures !== undefined);\n const bodyText = shouldCaptureBody\n ? await readResponseSnippet(response, options.maxBodyChars)\n : undefined;\n const responseBytes = readContentLength(response.headers) ?? estimateUtf8Bytes(bodyText);\n\n return {\n timestamp,\n kind: \"fetch\",\n method: request.method,\n url: redactUrl(request.url),\n status: response.status,\n ok: response.ok,\n durationMs,\n responseBytes,\n contentType,\n requestTraceparent: request.traceparent,\n responseTraceparent: response.headers.get(\"traceparent\") ?? undefined,\n trpcProcedures,\n responseSnippet: bodyText,\n };\n}\n\nfunction readHeader(headers: HeadersInit | undefined, name: string): string | undefined {\n if (!headers) return undefined;\n if (headers instanceof Headers) return headers.get(name) ?? undefined;\n const lowerName = name.toLowerCase();\n if (Array.isArray(headers)) {\n const hit = headers.find(([key]) => key.toLowerCase() === lowerName);\n return hit?.[1];\n }\n const value = Object.entries(headers).find(([key]) => key.toLowerCase() === lowerName)?.[1];\n return value;\n}\n\nfunction readContentLength(headers: Headers): number | null {\n const raw = headers.get(\"content-length\");\n if (!raw) return null;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nfunction estimateUtf8Bytes(value: string | undefined): number | null {\n if (value === undefined) return null;\n return new TextEncoder().encode(value).byteLength;\n}\n\nasync function readResponseSnippet(\n response: Response,\n maxChars: number,\n): Promise<string | undefined> {\n try {\n const text = await response.clone().text();\n if (text.length <= maxChars) return text;\n return `${text.slice(0, maxChars)}...[truncated ${String(text.length - maxChars)} chars]`;\n } catch {\n return undefined;\n }\n}\n\nfunction extractTrpcProcedures(rawUrl: string): string[] | undefined {\n let url: URL;\n try {\n url = new URL(rawUrl, \"https://placeholder.invalid\");\n } catch {\n return undefined;\n }\n const marker = \"/trpc/\";\n const index = url.pathname.indexOf(marker);\n if (index === -1) return undefined;\n const path = url.pathname.slice(index + marker.length);\n const procedures = path\n .split(\",\")\n .map((part) => decodeURIComponent(part).trim())\n .filter((part) => part.length > 0);\n return procedures.length > 0 ? procedures : undefined;\n}\n\nfunction redactUrl(rawUrl: string): string {\n let url: URL;\n try {\n url = new URL(rawUrl, \"https://placeholder.invalid\");\n } catch {\n return rawUrl;\n }\n for (const key of [...url.searchParams.keys()]) {\n const lower = key.toLowerCase();\n if (\n lower.includes(\"token\") ||\n lower.includes(\"key\") ||\n lower.includes(\"secret\") ||\n lower.includes(\"signature\") ||\n lower.includes(\"sig\")\n ) {\n url.searchParams.set(key, \"[redacted]\");\n }\n }\n return url.origin === \"https://placeholder.invalid\"\n ? `${url.pathname}${url.search}`\n : url.toString();\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.endsWith(\"/\") ? url.slice(0, -1) : url;\n}\n\nfunction extractId(payload: unknown, key: \"incidentId\" | \"cardId\"): string | undefined {\n if (payload && typeof payload === \"object\" && key in payload) {\n const value = (payload as Record<string, unknown>)[key];\n if (value !== undefined && value !== null) return String(value);\n }\n return undefined;\n}\n\nasync function readJson(res: Response): Promise<unknown> {\n const text = await res.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction formatErrorBody(status: number, statusText: string, body: unknown): string {\n const prefix = `${status} ${statusText}`.trim();\n if (body && typeof body === \"object\" && \"error\" in body) {\n return `${prefix}: ${String((body as { error: unknown }).error)}`;\n }\n if (typeof body === \"string\" && body.length > 0) {\n return `${prefix}: ${body}`;\n }\n return prefix;\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(bytes).toString(\"base64\");\n }\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/index.ts"],"sourcesContent":["export class ConveyorIncidentsError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"ConveyorIncidentsError\";\n this.status = status;\n this.body = body;\n }\n}\n","import { ConveyorIncidentsError } from \"./errors.js\";\n\nexport { ConveyorIncidentsError } from \"./errors.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.conveyor.rallycryapp.com\";\nconst DEFAULT_SOURCE = \"client\";\nconst DEFAULT_SEVERITY: IncidentSeverity = \"medium\";\n\n/** Server-side per-file ceiling — keep in sync with MAX_ATTACHMENT_BYTES in\n * conveyor's incident-routes.ts. Checked client-side so an oversized file\n * fails with a descriptive `attachmentResults` error instead of an HTTP 413. */\nexport const MAX_ATTACHMENT_BYTES = 5 * 1024 * 1024;\n\nexport type IncidentSeverity = \"critical\" | \"high\" | \"medium\" | \"low\";\n\nexport type ConveyorCardType = \"incident\" | \"suggestion\" | \"task\";\n\nexport interface ConveyorIncidentsOptions {\n apiKey: string;\n projectId: string;\n baseUrl?: string;\n fetch?: typeof fetch;\n}\n\nexport interface IncidentAttachment {\n fileName: string;\n mimeType: string;\n content: Uint8Array | string;\n}\n\nexport interface NetworkCaptureOptions {\n /** Maximum entries kept in memory. Defaults to 80. */\n maxEntries?: number;\n /** Maximum response body characters kept per captured entry. Defaults to 4000. */\n maxBodyChars?: number;\n /** Capture response snippets for successful tRPC batch calls as well as failures. Defaults to true. */\n captureTrpcResponses?: boolean;\n}\n\nexport interface NetworkLogEntry {\n timestamp: string;\n kind: \"fetch\";\n method: string;\n url: string;\n status: number | null;\n ok: boolean | null;\n durationMs: number;\n responseBytes: number | null;\n contentType?: string;\n requestTraceparent?: string;\n responseTraceparent?: string;\n trpcProcedures?: string[];\n responseSnippet?: string;\n error?: string;\n}\n\n/**\n * Who filed the report and where they were when they filed it. Unlike\n * `createdBy`, this needs no Conveyor project membership — it's descriptive\n * metadata, rendered into the card's description footer and onto the Slack\n * card. Send it for every user-initiated report: without it a card from an\n * end user is indistinguishable from an automated alert.\n */\nexport interface ReportReporter {\n /** Display name / username of the person filing the report. */\n name?: string;\n /** Contact email. Also used for attribution when it matches a member. */\n email?: string;\n /** Absolute http(s) URL of the page the user was on at report time. */\n url?: string;\n}\n\nexport interface ReportIncidentInput {\n title: string;\n description?: string;\n severity?: IncidentSeverity;\n source?: string;\n fingerprintKey?: string;\n plan?: string;\n attachments?: IncidentAttachment[];\n /** Card type to create. The server defaults to \"incident\" when omitted. */\n type?: ConveyorCardType;\n /**\n * Project-member emails. The FIRST email that resolves to a member of the\n * target project becomes the card's assignee; the rest are ignored.\n * Non-member emails are silently skipped.\n */\n assigned?: string[];\n /**\n * Project-member emails. EVERY email that resolves to a member of the\n * target project is added as a reviewer. Non-member emails are silently\n * skipped.\n */\n review?: string[];\n /**\n * Project-member email used as the card's creator. When omitted — or when\n * the email doesn't resolve to a member of the target project — the card\n * is attributed to the \"Conveyor Incidents\" system user.\n */\n createdBy?: string;\n /**\n * Reporter identity + the page they were on. Rendered into the card's\n * description metadata footer (and the Slack card) by the server; lines the\n * description already carries are not duplicated.\n */\n reporter?: ReportReporter;\n}\n\nexport interface AttachmentResult {\n fileName: string;\n ok: boolean;\n fileId?: string;\n error?: string;\n}\n\nexport interface ReportIncidentResult {\n incidentId: string;\n /** Alias of `incidentId` — prefer this when reporting type \"suggestion\" or \"task\". */\n cardId: string;\n attachmentResults: AttachmentResult[];\n}\n\nlet networkCaptureState:\n | {\n installed: true;\n originalFetch: typeof fetch;\n entries: NetworkLogEntry[];\n options: Required<NetworkCaptureOptions>;\n }\n | undefined;\n\nconst DEFAULT_NETWORK_CAPTURE_OPTIONS: Required<NetworkCaptureOptions> = {\n maxEntries: 80,\n maxBodyChars: 4000,\n captureTrpcResponses: true,\n};\n\nexport function installNetworkCapture(options: NetworkCaptureOptions = {}): void {\n if (typeof globalThis.fetch !== \"function\") return;\n // Bind to globalThis: some browsers (notably Safari/WebKit) throw\n // \"Illegal invocation\" when the native fetch is called without its\n // original receiver, which a bare captured reference loses.\n const fetchImpl = globalThis.fetch.bind(globalThis);\n\n const resolvedOptions = {\n ...DEFAULT_NETWORK_CAPTURE_OPTIONS,\n ...options,\n };\n\n if (networkCaptureState) {\n networkCaptureState.options = resolvedOptions;\n return;\n }\n\n const entries: NetworkLogEntry[] = [];\n networkCaptureState = {\n installed: true,\n originalFetch: fetchImpl,\n entries,\n options: resolvedOptions,\n };\n\n globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const state = networkCaptureState;\n const originalFetch = state?.originalFetch ?? fetchImpl;\n const startedAt = Date.now();\n const timestamp = new Date(startedAt).toISOString();\n const request = normalizeRequest(input, init);\n\n try {\n const response = await originalFetch(input, init);\n const entry = await buildNetworkEntry({\n timestamp,\n request,\n response,\n durationMs: Date.now() - startedAt,\n options: state?.options ?? resolvedOptions,\n });\n pushNetworkEntry(entry);\n return response;\n } catch (err) {\n pushNetworkEntry({\n timestamp,\n kind: \"fetch\",\n method: request.method,\n url: redactUrl(request.url),\n status: null,\n ok: null,\n durationMs: Date.now() - startedAt,\n responseBytes: null,\n requestTraceparent: request.traceparent,\n trpcProcedures: extractTrpcProcedures(request.url),\n error: err instanceof Error ? err.message : String(err),\n });\n throw err;\n }\n };\n}\n\nexport function uninstallNetworkCapture(): void {\n if (!networkCaptureState) return;\n globalThis.fetch = networkCaptureState.originalFetch;\n networkCaptureState = undefined;\n}\n\nexport function clearNetworkLogEntries(): void {\n networkCaptureState?.entries.splice(0);\n}\n\nexport function getNetworkLogEntries(): NetworkLogEntry[] {\n return [...(networkCaptureState?.entries ?? [])];\n}\n\nexport function buildNetworkLogAttachment(\n fileName = \"network.log\",\n): IncidentAttachment | undefined {\n const entries = getNetworkLogEntries();\n if (entries.length === 0) return undefined;\n return {\n fileName,\n mimeType: \"application/x-ndjson\",\n content: entries.map((entry) => JSON.stringify(entry)).join(\"\\n\"),\n };\n}\n\nexport class ConveyorIncidents {\n private readonly apiKey: string;\n private readonly projectId: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: ConveyorIncidentsOptions) {\n if (!opts.apiKey) {\n throw new Error(\"ConveyorIncidents: apiKey is required\");\n }\n if (!opts.projectId) {\n throw new Error(\"ConveyorIncidents: projectId is required\");\n }\n const resolvedFetch = opts.fetch ?? globalThis.fetch;\n if (typeof resolvedFetch !== \"function\") {\n throw new Error(\"ConveyorIncidents: no global fetch available — pass opts.fetch on Node <18\");\n }\n this.apiKey = opts.apiKey;\n this.projectId = opts.projectId;\n this.baseUrl = stripTrailingSlash(opts.baseUrl ?? DEFAULT_BASE_URL);\n this.fetchImpl = resolvedFetch;\n }\n\n async report(input: ReportIncidentInput): Promise<ReportIncidentResult> {\n const body = {\n projectId: this.projectId,\n source: input.source ?? DEFAULT_SOURCE,\n severity: input.severity ?? DEFAULT_SEVERITY,\n title: input.title,\n description: input.description,\n plan: input.plan,\n fingerprintKey: input.fingerprintKey,\n type: input.type,\n assigned: input.assigned,\n review: input.review,\n createdBy: input.createdBy,\n reporter: normalizeReporter(input.reporter),\n };\n\n const res = await this.fetchImpl(`${this.baseUrl}/api/incidents/report`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n const payload = await readJson(res);\n\n if (!res.ok) {\n throw new ConveyorIncidentsError(\n `Failed to report incident: ${res.status} ${res.statusText}`,\n res.status,\n payload,\n );\n }\n\n const incidentId = extractId(payload, \"incidentId\") ?? extractId(payload, \"cardId\");\n\n if (!incidentId) {\n throw new ConveyorIncidentsError(\n \"Invalid response from /api/incidents/report: missing incidentId\",\n res.status,\n payload,\n );\n }\n\n const attachmentResults: AttachmentResult[] = [];\n if (input.attachments && input.attachments.length > 0) {\n for (const attachment of input.attachments) {\n attachmentResults.push(await this.uploadAttachment(incidentId, attachment));\n }\n }\n\n return { incidentId, cardId: incidentId, attachmentResults };\n }\n\n private async uploadAttachment(\n incidentId: string,\n attachment: IncidentAttachment,\n ): Promise<AttachmentResult> {\n try {\n const bytes =\n typeof attachment.content === \"string\"\n ? new TextEncoder().encode(attachment.content)\n : attachment.content;\n if (bytes.byteLength > MAX_ATTACHMENT_BYTES) {\n return {\n fileName: attachment.fileName,\n ok: false,\n error: `Attachment is ${bytes.byteLength} bytes — exceeds the ${MAX_ATTACHMENT_BYTES}-byte (5 MB) server limit; upload skipped`,\n };\n }\n const contentBase64 = bytesToBase64(bytes);\n const res = await this.fetchImpl(\n `${this.baseUrl}/api/incidents/${encodeURIComponent(incidentId)}/files`,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({\n fileName: attachment.fileName,\n mimeType: attachment.mimeType,\n contentBase64,\n }),\n },\n );\n\n const payload = await readJson(res);\n\n if (!res.ok) {\n return {\n fileName: attachment.fileName,\n ok: false,\n error: formatErrorBody(res.status, res.statusText, payload),\n };\n }\n\n const fileId =\n payload && typeof payload === \"object\" && \"fileId\" in payload\n ? String((payload as { fileId: unknown }).fileId)\n : undefined;\n\n return { fileName: attachment.fileName, ok: true, fileId };\n } catch (err) {\n return {\n fileName: attachment.fileName,\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n\nfunction pushNetworkEntry(entry: NetworkLogEntry): void {\n const state = networkCaptureState;\n if (!state) return;\n state.entries.push(entry);\n const overflow = state.entries.length - state.options.maxEntries;\n if (overflow > 0) state.entries.splice(0, overflow);\n}\n\nfunction normalizeRequest(\n input: RequestInfo | URL,\n init: RequestInit | undefined,\n): { method: string; url: string; traceparent?: string } {\n const request = typeof Request !== \"undefined\" && input instanceof Request ? input : undefined;\n const method = init?.method ?? request?.method ?? \"GET\";\n const url = request?.url ?? String(input);\n const traceparent =\n readHeader(init?.headers, \"traceparent\") ?? readHeader(request?.headers, \"traceparent\");\n return { method: method.toUpperCase(), url, traceparent };\n}\n\nasync function buildNetworkEntry(params: {\n timestamp: string;\n request: { method: string; url: string; traceparent?: string };\n response: Response;\n durationMs: number;\n options: Required<NetworkCaptureOptions>;\n}): Promise<NetworkLogEntry> {\n const { timestamp, request, response, durationMs, options } = params;\n const contentType = response.headers.get(\"content-type\") ?? undefined;\n const trpcProcedures = extractTrpcProcedures(request.url);\n const shouldCaptureBody =\n !response.ok || (options.captureTrpcResponses && trpcProcedures !== undefined);\n const bodyText = shouldCaptureBody\n ? await readResponseSnippet(response, options.maxBodyChars)\n : undefined;\n const responseBytes = readContentLength(response.headers) ?? estimateUtf8Bytes(bodyText);\n\n return {\n timestamp,\n kind: \"fetch\",\n method: request.method,\n url: redactUrl(request.url),\n status: response.status,\n ok: response.ok,\n durationMs,\n responseBytes,\n contentType,\n requestTraceparent: request.traceparent,\n responseTraceparent: response.headers.get(\"traceparent\") ?? undefined,\n trpcProcedures,\n responseSnippet: bodyText,\n };\n}\n\nfunction readHeader(headers: HeadersInit | undefined, name: string): string | undefined {\n if (!headers) return undefined;\n if (headers instanceof Headers) return headers.get(name) ?? undefined;\n const lowerName = name.toLowerCase();\n if (Array.isArray(headers)) {\n const hit = headers.find(([key]) => key.toLowerCase() === lowerName);\n return hit?.[1];\n }\n const value = Object.entries(headers).find(([key]) => key.toLowerCase() === lowerName)?.[1];\n return value;\n}\n\nfunction readContentLength(headers: Headers): number | null {\n const raw = headers.get(\"content-length\");\n if (!raw) return null;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nfunction estimateUtf8Bytes(value: string | undefined): number | null {\n if (value === undefined) return null;\n return new TextEncoder().encode(value).byteLength;\n}\n\nasync function readResponseSnippet(\n response: Response,\n maxChars: number,\n): Promise<string | undefined> {\n try {\n const text = await response.clone().text();\n if (text.length <= maxChars) return text;\n return `${text.slice(0, maxChars)}...[truncated ${String(text.length - maxChars)} chars]`;\n } catch {\n return undefined;\n }\n}\n\nfunction extractTrpcProcedures(rawUrl: string): string[] | undefined {\n let url: URL;\n try {\n url = new URL(rawUrl, \"https://placeholder.invalid\");\n } catch {\n return undefined;\n }\n const marker = \"/trpc/\";\n const index = url.pathname.indexOf(marker);\n if (index === -1) return undefined;\n const path = url.pathname.slice(index + marker.length);\n const procedures = path\n .split(\",\")\n .map((part) => decodeURIComponent(part).trim())\n .filter((part) => part.length > 0);\n return procedures.length > 0 ? procedures : undefined;\n}\n\nfunction redactUrl(rawUrl: string): string {\n let url: URL;\n try {\n url = new URL(rawUrl, \"https://placeholder.invalid\");\n } catch {\n return rawUrl;\n }\n for (const key of [...url.searchParams.keys()]) {\n const lower = key.toLowerCase();\n if (\n lower.includes(\"token\") ||\n lower.includes(\"key\") ||\n lower.includes(\"secret\") ||\n lower.includes(\"signature\") ||\n lower.includes(\"sig\")\n ) {\n url.searchParams.set(key, \"[redacted]\");\n }\n }\n return url.origin === \"https://placeholder.invalid\"\n ? `${url.pathname}${url.search}`\n : url.toString();\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.endsWith(\"/\") ? url.slice(0, -1) : url;\n}\n\n/**\n * Trim reporter fields and drop empty ones, so a caller passing\n * `{ name: user.name ?? \"\" }` doesn't send a blank line to the server.\n * Returns undefined when nothing useful survives.\n */\nfunction normalizeReporter(reporter: ReportReporter | undefined): ReportReporter | undefined {\n if (!reporter) return undefined;\n const entries = ([\"name\", \"email\", \"url\"] as const)\n .map((key) => [key, reporter[key]?.trim()] as const)\n .filter((entry): entry is readonly [(typeof entry)[0], string] => !!entry[1]);\n if (entries.length === 0) return undefined;\n return Object.fromEntries(entries) as ReportReporter;\n}\n\nfunction extractId(payload: unknown, key: \"incidentId\" | \"cardId\"): string | undefined {\n if (payload && typeof payload === \"object\" && key in payload) {\n const value = (payload as Record<string, unknown>)[key];\n if (value !== undefined && value !== null) return String(value);\n }\n return undefined;\n}\n\nasync function readJson(res: Response): Promise<unknown> {\n const text = await res.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction formatErrorBody(status: number, statusText: string, body: unknown): string {\n const prefix = `${status} ${statusText}`.trim();\n if (body && typeof body === \"object\" && \"error\" in body) {\n return `${prefix}: ${String((body as { error: unknown }).error)}`;\n }\n if (typeof body === \"string\" && body.length > 0) {\n return `${prefix}: ${body}`;\n }\n return prefix;\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n if (typeof Buffer !== \"undefined\") {\n return Buffer.from(bytes).toString(\"base64\");\n }\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary);\n}\n"],"mappings":"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","names":[]}
|