@sailfish-ai/recorder 1.7.35 → 1.7.41

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.
Binary file
Binary file
@@ -0,0 +1,150 @@
1
+ import { identify, trackingEvent } from "./sendSailfishMessages";
2
+ function safeParseJson(text) {
3
+ if (typeof text !== "string" || !text.trim())
4
+ return undefined;
5
+ try {
6
+ return JSON.parse(text);
7
+ }
8
+ catch {
9
+ return undefined;
10
+ }
11
+ }
12
+ function isSegmentHost(url) {
13
+ try {
14
+ const u = new URL(url, window.location.href);
15
+ // Recognize any Segment ingestion domain (US default or regional)
16
+ return (u.hostname === "api.segment.io" || u.hostname.endsWith(".segmentapis.com"));
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ function pathType(url) {
23
+ try {
24
+ const u = new URL(url, window.location.href);
25
+ const path = u.pathname;
26
+ // Segment ingestion endpoints for identify and track (short and full paths)
27
+ if (path.endsWith("/v1/i") ||
28
+ path === "/i" ||
29
+ path.endsWith("/v1/identify")) {
30
+ return "identify";
31
+ }
32
+ if (path.endsWith("/v1/t") || path === "/t" || path.endsWith("/v1/track")) {
33
+ return "track";
34
+ }
35
+ }
36
+ catch {
37
+ // URL parsing failed
38
+ }
39
+ return null;
40
+ }
41
+ /**
42
+ * Try to capture Segment payloads and mirror them to our identify/trackingEvent calls.
43
+ * - For /v1/i: call identify(userId, traits)
44
+ * - For /v1/t: call trackingEvent({ event, properties })
45
+ */
46
+ export async function maybeCaptureSegment(url, bodyCandidate) {
47
+ if (!isSegmentHost(url))
48
+ return;
49
+ const type = pathType(url);
50
+ if (!type)
51
+ return;
52
+ // Body is usually text/plain JSON from browsers
53
+ let json;
54
+ if (typeof bodyCandidate === "string") {
55
+ json = safeParseJson(bodyCandidate);
56
+ }
57
+ else if (bodyCandidate &&
58
+ typeof bodyCandidate.text === "function") {
59
+ // BodyInit like Request/Blob/Response with .text(): read safely
60
+ try {
61
+ const txt = await bodyCandidate.text();
62
+ json = safeParseJson(txt);
63
+ }
64
+ catch {
65
+ /* ignore */
66
+ }
67
+ }
68
+ else if (bodyCandidate instanceof URLSearchParams) {
69
+ // Segment won't send this way normally, but no harm
70
+ json = safeParseJson(bodyCandidate.toString());
71
+ }
72
+ if (!json || typeof json !== "object")
73
+ return;
74
+ if (type === "identify") {
75
+ const userId = json.userId ?? json.user_id;
76
+ const traits = (json.traits ?? {});
77
+ if (typeof userId === "string" && userId) {
78
+ identify(userId, traits, false);
79
+ }
80
+ }
81
+ else if (type === "track") {
82
+ const event = json.event ?? json.name;
83
+ const properties = (json.properties ?? {});
84
+ if (typeof event === "string" && event) {
85
+ trackingEvent({ event, properties });
86
+ }
87
+ }
88
+ }
89
+ // ─────────────────────────────────────────────────────────────────────────────
90
+ // Convenience wrappers used by interceptors (centralized here to avoid dupes)
91
+ // ─────────────────────────────────────────────────────────────────────────────
92
+ export function maybeMirrorSegmentFromXhr(url, sendArg) {
93
+ try {
94
+ if (!url)
95
+ return;
96
+ if (typeof sendArg === "string") {
97
+ void maybeCaptureSegment(url, sendArg);
98
+ }
99
+ }
100
+ catch {
101
+ /* noop */
102
+ }
103
+ }
104
+ export async function maybeMirrorSegmentFromFetchRequest(req) {
105
+ try {
106
+ const url = req?.url;
107
+ if (!url)
108
+ return;
109
+ // Clone so we don't consume the original body
110
+ const preview = req.clone();
111
+ const text = await preview.text().catch(() => undefined);
112
+ if (typeof text === "string") {
113
+ await maybeCaptureSegment(url, text);
114
+ }
115
+ }
116
+ catch {
117
+ /* noop */
118
+ }
119
+ }
120
+ export async function maybeMirrorSegmentFromFetchUrlAndInit(input, init) {
121
+ try {
122
+ const url = String(input);
123
+ const body = init?.body;
124
+ if (typeof body === "string") {
125
+ await maybeCaptureSegment(url, body);
126
+ }
127
+ else if (body && typeof body.text === "function") {
128
+ const txt = await body.text().catch(() => undefined);
129
+ if (typeof txt === "string") {
130
+ await maybeCaptureSegment(url, txt);
131
+ }
132
+ }
133
+ }
134
+ catch {
135
+ /* noop */
136
+ }
137
+ }
138
+ export async function maybeMirrorSegmentFromFetchArgs(input, init) {
139
+ try {
140
+ if (input instanceof Request) {
141
+ await maybeMirrorSegmentFromFetchRequest(input);
142
+ }
143
+ else if (typeof input === "string" || input instanceof URL) {
144
+ await maybeMirrorSegmentFromFetchUrlAndInit(input, init);
145
+ }
146
+ }
147
+ catch {
148
+ /* noop */
149
+ }
150
+ }
@@ -1,3 +1,4 @@
1
+ import { nowTimestamp } from "./utils";
1
2
  import { sendMessage } from "./websocket";
2
3
  // Internal state to track the last sent messages
3
4
  let lastIdentifyMessage = null;
@@ -35,3 +36,12 @@ export function addOrUpdateMetadata(metadata) {
35
36
  lastMetadataMessage = metadata;
36
37
  sendMessage(message);
37
38
  }
39
+ // Function to send trackingEvent message
40
+ export function trackingEvent(trackingData) {
41
+ const message = {
42
+ type: "trackingEvent",
43
+ trackingData,
44
+ timestamp: nowTimestamp(),
45
+ };
46
+ sendMessage(message);
47
+ }
@@ -1,3 +1,9 @@
1
+ export declare const ReportIssueContext: {
2
+ resolveSessionId: (() => string) | null;
3
+ apiKey: string | null;
4
+ backendApi: string | null;
5
+ triageBaseUrl: string;
6
+ };
1
7
  export declare function setupIssueReporting(options: {
2
8
  apiKey: string;
3
9
  backendApi: string;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Try to capture Segment payloads and mirror them to our identify/trackingEvent calls.
3
+ * - For /v1/i: call identify(userId, traits)
4
+ * - For /v1/t: call trackingEvent({ event, properties })
5
+ */
6
+ export declare function maybeCaptureSegment(url: string, bodyCandidate?: unknown): Promise<void>;
7
+ export declare function maybeMirrorSegmentFromXhr(url: string | null, sendArg: unknown): void;
8
+ export declare function maybeMirrorSegmentFromFetchRequest(req: Request): Promise<void>;
9
+ export declare function maybeMirrorSegmentFromFetchUrlAndInit(input: string | URL, init?: RequestInit): Promise<void>;
10
+ export declare function maybeMirrorSegmentFromFetchArgs(input: Request | string | URL, init?: RequestInit): Promise<void>;
@@ -1,2 +1,3 @@
1
1
  export declare function identify(userId: string, traits?: Record<string, any>, overwrite?: boolean): void;
2
2
  export declare function addOrUpdateMetadata(metadata: Record<string, any>): void;
3
+ export declare function trackingEvent(trackingData: Record<string, any>): void;
@@ -4,3 +4,5 @@ export declare function buildBatches<T extends {
4
4
  };
5
5
  }>(queue: T[], getSize: (item: T) => number, maxBytes: number): T[][];
6
6
  export declare function eventSize(event: any): number;
7
+ declare let nowTimestamp: () => number;
8
+ export { nowTimestamp };
package/dist/utils.js CHANGED
@@ -39,3 +39,10 @@ export function buildBatches(queue, getSize, maxBytes) {
39
39
  export function eventSize(event) {
40
40
  return new Blob([JSON.stringify(event)]).size;
41
41
  }
42
+ // guard against old third party libraries which redefine Date.now
43
+ let nowTimestamp = Date.now;
44
+ if (!( /*@__PURE__*//[1-9][0-9]{12}/.test(Date.now().toString()))) {
45
+ // they have already redefined it! use a fallback
46
+ nowTimestamp = () => new Date().getTime();
47
+ }
48
+ export { nowTimestamp };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailfish-ai/recorder",
3
- "version": "1.7.35",
3
+ "version": "1.7.41",
4
4
  "publishPublicly": true,
5
5
  "main": "dist/sailfish-recorder.umd.js",
6
6
  "types": "dist/types/index.d.ts",