@sailfish-ai/recorder 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -82,38 +82,103 @@ function storeCredentialsAndConnection({ apiKey, backendApi, }) {
82
82
  sessionStorage.setItem("sailfishApiKey", apiKey);
83
83
  sessionStorage.setItem("sailfishBackendApi", backendApi);
84
84
  }
85
- // Intercepting XMLHttpRequest
85
+ // Utility function to match domains or paths with wildcard support
86
+ export function matchUrlWithWildcard(url, patterns) {
87
+ // Strip any protocol (e.g., http, https, ws, wss, ftp, etc.) from the URL
88
+ const strippedUrl = url.replace(/^[a-zA-Z]+:\/\//, "");
89
+ const parsedUrl = new URL("http://" + strippedUrl); // Add a dummy protocol for URL parsing
90
+ const { hostname, pathname } = parsedUrl;
91
+ const domain = hostname.toLowerCase();
92
+ return patterns.some((pattern) => {
93
+ // Strip any protocol from the pattern
94
+ const strippedPattern = pattern.replace(/^[a-zA-Z]+:\/\//, "");
95
+ let [patternDomain, patternPath] = strippedPattern.split("/", 2);
96
+ // Handle domain wildcards
97
+ const normalizedPatternDomain = patternDomain
98
+ .replace(/\./g, "\\.") // Escape dots for regex
99
+ .replace(/\*/g, ".*"); // Replace '*' with regex to match any characters
100
+ // Create regex for the domain pattern
101
+ const domainRegex = new RegExp(`^${normalizedPatternDomain}$`, "i");
102
+ // Strip 'www.' from both the input domain and the pattern domain for comparison
103
+ const strippedDomain = domain.startsWith("www.") ? domain.slice(4) : domain;
104
+ // Handle subdomain wildcard (*.) to match both base and subdomains
105
+ if (patternDomain.startsWith("*.") &&
106
+ (domain === patternDomain.slice(2) ||
107
+ strippedDomain === patternDomain.slice(2))) {
108
+ // Check for path match if the pattern includes a path
109
+ if (patternPath) {
110
+ const normalizedPatternPath = patternPath
111
+ .replace(/\*/g, ".*") // Replace '*' with regex to match any characters
112
+ .replace(/\/$/, ""); // Remove trailing slashes from pattern
113
+ const pathRegex = new RegExp(`^/${normalizedPatternPath}`, "i");
114
+ return pathRegex.test(pathname); // Match the path
115
+ }
116
+ return true; // Domain matched, no path required
117
+ }
118
+ // Check if the domain matches (include check for base domain without www)
119
+ if (!domainRegex.test(strippedDomain) && !domainRegex.test(domain)) {
120
+ return false;
121
+ }
122
+ // If there's a path in the pattern, match the path
123
+ if (patternPath) {
124
+ const normalizedPatternPath = patternPath
125
+ .replace(/\*/g, ".*") // Replace '*' with regex to match any characters
126
+ .replace(/\/$/, ""); // Remove trailing slashes from pattern
127
+ const pathRegex = new RegExp(`^/${normalizedPatternPath}`, "i");
128
+ return pathRegex.test(pathname); // Match the path
129
+ }
130
+ // If no path pattern, only the domain needs to match
131
+ return true;
132
+ });
133
+ }
134
+ // Updated XMLHttpRequest interceptor with single check function
86
135
  function setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo) {
87
136
  const originalOpen = XMLHttpRequest.prototype.open;
88
137
  const originalSend = XMLHttpRequest.prototype.send;
89
138
  const sessionId = getOrSetSessionId();
139
+ // Combine default and passed domains
90
140
  const combinedIgnoreDomains = [
91
141
  ...DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT,
92
142
  ...domainsToNotPropagateHeaderTo,
93
- ].map((domain) => (domain.startsWith("www.") ? domain.slice(4) : domain)); // Remove 'www.' from ignore domains
143
+ ];
94
144
  XMLHttpRequest.prototype.open = function (...args) {
95
145
  this._url = args[1]; // Store the request URL
96
146
  originalOpen.apply(this, args);
97
147
  };
98
148
  XMLHttpRequest.prototype.send = function (...args) {
99
- const domain = getEffectiveDomain(this._url); // Use tldts to get the domain
100
- if (sessionId && !combinedIgnoreDomains.includes(domain)) {
149
+ const url = this._url;
150
+ // Check if URL matches any domain or path pattern
151
+ const shouldSkipHeader = matchUrlWithWildcard(url, combinedIgnoreDomains);
152
+ if (sessionId && !shouldSkipHeader) {
101
153
  this.setRequestHeader("X-Sf3-Rid", sessionId);
102
154
  }
103
155
  originalSend.apply(this, args);
104
156
  };
105
157
  }
106
- // Intercepting fetch API
158
+ // Updated fetch interceptor with single check function
107
159
  function setupFetchInterceptor(domainsToNotPropagateHeaderTo) {
108
160
  const originalFetch = window.fetch;
109
161
  const sessionId = getOrSetSessionId();
162
+ // Combine default and passed domains
110
163
  const combinedIgnoreDomains = [
111
164
  ...DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT,
112
165
  ...domainsToNotPropagateHeaderTo,
113
- ].map((domain) => (domain.startsWith("www.") ? domain.slice(4) : domain)); // Remove 'www.' from ignore domains
166
+ ];
114
167
  window.fetch = async function (input, init = {}) {
115
- const domain = typeof input === "string" ? getEffectiveDomain(input) : "";
116
- if (sessionId && !combinedIgnoreDomains.includes(domain)) {
168
+ let url;
169
+ // Check if input is a string (URL) or a Request object
170
+ if (typeof input === "string") {
171
+ url = input;
172
+ }
173
+ else if (input instanceof Request) {
174
+ url = input.url;
175
+ }
176
+ else {
177
+ throw new Error("Invalid input type for fetch");
178
+ }
179
+ // Check if URL matches any domain or path pattern
180
+ const shouldSkipHeader = matchUrlWithWildcard(url, combinedIgnoreDomains);
181
+ if (sessionId && !shouldSkipHeader) {
117
182
  init.headers = {
118
183
  ...init.headers,
119
184
  "X-Sf3-Rid": sessionId,
@@ -1002,7 +1002,7 @@ var ReconnectingWebSocket = (
1002
1002
  return ReconnectingWebSocket2;
1003
1003
  }()
1004
1004
  );
1005
- const version = "1.0.5";
1005
+ const version = "1.0.6";
1006
1006
  let webSocket = null;
1007
1007
  const messageQueue = [];
1008
1008
  function flushMessageQueue() {
@@ -21107,6 +21107,36 @@ function storeCredentialsAndConnection({
21107
21107
  sessionStorage.setItem("sailfishApiKey", apiKey);
21108
21108
  sessionStorage.setItem("sailfishBackendApi", backendApi);
21109
21109
  }
21110
+ function matchUrlWithWildcard(url, patterns) {
21111
+ const strippedUrl = url.replace(/^[a-zA-Z]+:\/\//, "");
21112
+ const parsedUrl = new URL("http://" + strippedUrl);
21113
+ const { hostname, pathname } = parsedUrl;
21114
+ const domain = hostname.toLowerCase();
21115
+ return patterns.some((pattern) => {
21116
+ const strippedPattern = pattern.replace(/^[a-zA-Z]+:\/\//, "");
21117
+ let [patternDomain, patternPath] = strippedPattern.split("/", 2);
21118
+ const normalizedPatternDomain = patternDomain.replace(/\./g, "\\.").replace(/\*/g, ".*");
21119
+ const domainRegex = new RegExp(`^${normalizedPatternDomain}$`, "i");
21120
+ const strippedDomain = domain.startsWith("www.") ? domain.slice(4) : domain;
21121
+ if (patternDomain.startsWith("*.") && (domain === patternDomain.slice(2) || strippedDomain === patternDomain.slice(2))) {
21122
+ if (patternPath) {
21123
+ const normalizedPatternPath = patternPath.replace(/\*/g, ".*").replace(/\/$/, "");
21124
+ const pathRegex = new RegExp(`^/${normalizedPatternPath}`, "i");
21125
+ return pathRegex.test(pathname);
21126
+ }
21127
+ return true;
21128
+ }
21129
+ if (!domainRegex.test(strippedDomain) && !domainRegex.test(domain)) {
21130
+ return false;
21131
+ }
21132
+ if (patternPath) {
21133
+ const normalizedPatternPath = patternPath.replace(/\*/g, ".*").replace(/\/$/, "");
21134
+ const pathRegex = new RegExp(`^/${normalizedPatternPath}`, "i");
21135
+ return pathRegex.test(pathname);
21136
+ }
21137
+ return true;
21138
+ });
21139
+ }
21110
21140
  function setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo) {
21111
21141
  const originalOpen = XMLHttpRequest.prototype.open;
21112
21142
  const originalSend = XMLHttpRequest.prototype.send;
@@ -21114,14 +21144,15 @@ function setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo) {
21114
21144
  const combinedIgnoreDomains = [
21115
21145
  ...DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT,
21116
21146
  ...domainsToNotPropagateHeaderTo
21117
- ].map((domain) => domain.startsWith("www.") ? domain.slice(4) : domain);
21147
+ ];
21118
21148
  XMLHttpRequest.prototype.open = function(...args) {
21119
21149
  this._url = args[1];
21120
21150
  originalOpen.apply(this, args);
21121
21151
  };
21122
21152
  XMLHttpRequest.prototype.send = function(...args) {
21123
- const domain = getEffectiveDomain(this._url);
21124
- if (sessionId && !combinedIgnoreDomains.includes(domain)) {
21153
+ const url = this._url;
21154
+ const shouldSkipHeader = matchUrlWithWildcard(url, combinedIgnoreDomains);
21155
+ if (sessionId && !shouldSkipHeader) {
21125
21156
  this.setRequestHeader("X-Sf3-Rid", sessionId);
21126
21157
  }
21127
21158
  originalSend.apply(this, args);
@@ -21133,10 +21164,18 @@ function setupFetchInterceptor(domainsToNotPropagateHeaderTo) {
21133
21164
  const combinedIgnoreDomains = [
21134
21165
  ...DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT,
21135
21166
  ...domainsToNotPropagateHeaderTo
21136
- ].map((domain) => domain.startsWith("www.") ? domain.slice(4) : domain);
21167
+ ];
21137
21168
  window.fetch = async function(input2, init = {}) {
21138
- const domain = typeof input2 === "string" ? getEffectiveDomain(input2) : "";
21139
- if (sessionId && !combinedIgnoreDomains.includes(domain)) {
21169
+ let url;
21170
+ if (typeof input2 === "string") {
21171
+ url = input2;
21172
+ } else if (input2 instanceof Request) {
21173
+ url = input2.url;
21174
+ } else {
21175
+ throw new Error("Invalid input type for fetch");
21176
+ }
21177
+ const shouldSkipHeader = matchUrlWithWildcard(url, combinedIgnoreDomains);
21178
+ if (sessionId && !shouldSkipHeader) {
21140
21179
  init.headers = {
21141
21180
  ...init.headers,
21142
21181
  "X-Sf3-Rid": sessionId
@@ -21213,6 +21252,7 @@ export {
21213
21252
  identify,
21214
21253
  initializeRecording,
21215
21254
  initializeWebSocket,
21255
+ matchUrlWithWildcard,
21216
21256
  sendDomainsToNotPropagateHeaderTo,
21217
21257
  sendGraphQLRequest,
21218
21258
  sendMessage,
@@ -1006,7 +1006,7 @@
1006
1006
  return ReconnectingWebSocket2;
1007
1007
  }()
1008
1008
  );
1009
- const version = "1.0.5";
1009
+ const version = "1.0.6";
1010
1010
  let webSocket = null;
1011
1011
  const messageQueue = [];
1012
1012
  function flushMessageQueue() {
@@ -21111,6 +21111,36 @@
21111
21111
  sessionStorage.setItem("sailfishApiKey", apiKey);
21112
21112
  sessionStorage.setItem("sailfishBackendApi", backendApi);
21113
21113
  }
21114
+ function matchUrlWithWildcard(url, patterns) {
21115
+ const strippedUrl = url.replace(/^[a-zA-Z]+:\/\//, "");
21116
+ const parsedUrl = new URL("http://" + strippedUrl);
21117
+ const { hostname, pathname } = parsedUrl;
21118
+ const domain = hostname.toLowerCase();
21119
+ return patterns.some((pattern) => {
21120
+ const strippedPattern = pattern.replace(/^[a-zA-Z]+:\/\//, "");
21121
+ let [patternDomain, patternPath] = strippedPattern.split("/", 2);
21122
+ const normalizedPatternDomain = patternDomain.replace(/\./g, "\\.").replace(/\*/g, ".*");
21123
+ const domainRegex = new RegExp(`^${normalizedPatternDomain}$`, "i");
21124
+ const strippedDomain = domain.startsWith("www.") ? domain.slice(4) : domain;
21125
+ if (patternDomain.startsWith("*.") && (domain === patternDomain.slice(2) || strippedDomain === patternDomain.slice(2))) {
21126
+ if (patternPath) {
21127
+ const normalizedPatternPath = patternPath.replace(/\*/g, ".*").replace(/\/$/, "");
21128
+ const pathRegex = new RegExp(`^/${normalizedPatternPath}`, "i");
21129
+ return pathRegex.test(pathname);
21130
+ }
21131
+ return true;
21132
+ }
21133
+ if (!domainRegex.test(strippedDomain) && !domainRegex.test(domain)) {
21134
+ return false;
21135
+ }
21136
+ if (patternPath) {
21137
+ const normalizedPatternPath = patternPath.replace(/\*/g, ".*").replace(/\/$/, "");
21138
+ const pathRegex = new RegExp(`^/${normalizedPatternPath}`, "i");
21139
+ return pathRegex.test(pathname);
21140
+ }
21141
+ return true;
21142
+ });
21143
+ }
21114
21144
  function setupXMLHttpRequestInterceptor(domainsToNotPropagateHeaderTo) {
21115
21145
  const originalOpen = XMLHttpRequest.prototype.open;
21116
21146
  const originalSend = XMLHttpRequest.prototype.send;
@@ -21118,14 +21148,15 @@
21118
21148
  const combinedIgnoreDomains = [
21119
21149
  ...DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT,
21120
21150
  ...domainsToNotPropagateHeaderTo
21121
- ].map((domain) => domain.startsWith("www.") ? domain.slice(4) : domain);
21151
+ ];
21122
21152
  XMLHttpRequest.prototype.open = function(...args) {
21123
21153
  this._url = args[1];
21124
21154
  originalOpen.apply(this, args);
21125
21155
  };
21126
21156
  XMLHttpRequest.prototype.send = function(...args) {
21127
- const domain = getEffectiveDomain(this._url);
21128
- if (sessionId && !combinedIgnoreDomains.includes(domain)) {
21157
+ const url = this._url;
21158
+ const shouldSkipHeader = matchUrlWithWildcard(url, combinedIgnoreDomains);
21159
+ if (sessionId && !shouldSkipHeader) {
21129
21160
  this.setRequestHeader("X-Sf3-Rid", sessionId);
21130
21161
  }
21131
21162
  originalSend.apply(this, args);
@@ -21137,10 +21168,18 @@
21137
21168
  const combinedIgnoreDomains = [
21138
21169
  ...DOMAINS_TO_NOT_PROPAGATE_HEADER_TO_DEFAULT,
21139
21170
  ...domainsToNotPropagateHeaderTo
21140
- ].map((domain) => domain.startsWith("www.") ? domain.slice(4) : domain);
21171
+ ];
21141
21172
  window.fetch = async function(input2, init = {}) {
21142
- const domain = typeof input2 === "string" ? getEffectiveDomain(input2) : "";
21143
- if (sessionId && !combinedIgnoreDomains.includes(domain)) {
21173
+ let url;
21174
+ if (typeof input2 === "string") {
21175
+ url = input2;
21176
+ } else if (input2 instanceof Request) {
21177
+ url = input2.url;
21178
+ } else {
21179
+ throw new Error("Invalid input type for fetch");
21180
+ }
21181
+ const shouldSkipHeader = matchUrlWithWildcard(url, combinedIgnoreDomains);
21182
+ if (sessionId && !shouldSkipHeader) {
21144
21183
  init.headers = {
21145
21184
  ...init.headers,
21146
21185
  "X-Sf3-Rid": sessionId
@@ -21216,6 +21255,7 @@
21216
21255
  exports2.identify = identify;
21217
21256
  exports2.initializeRecording = initializeRecording;
21218
21257
  exports2.initializeWebSocket = initializeWebSocket;
21258
+ exports2.matchUrlWithWildcard = matchUrlWithWildcard;
21219
21259
  exports2.sendDomainsToNotPropagateHeaderTo = sendDomainsToNotPropagateHeaderTo;
21220
21260
  exports2.sendGraphQLRequest = sendGraphQLRequest;
21221
21261
  exports2.sendMessage = sendMessage;
@@ -4,6 +4,7 @@ import { CaptureSettings } from "./types";
4
4
  export declare const DEFAULT_CAPTURE_SETTINGS: CaptureSettings;
5
5
  export declare const DEFAULT_CONSOLE_RECORDING_SETTINGS: LogRecordOptions;
6
6
  export declare const DEFAULT_NETWORK_CAPTURE_SETTINGS: NetworkRecordOptions;
7
+ export declare function matchUrlWithWildcard(url: string, patterns: string[]): boolean;
7
8
  export declare function startRecording({ apiKey, backendApi, domainsToNotPropagateHeaderTo, }: {
8
9
  apiKey: string;
9
10
  backendApi: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sailfish-ai/recorder",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "publishPublicly": true,
5
5
  "main": "dist/sailfish-recorder.umd.js",
6
6
  "types": "dist/types/index.d.ts",
@@ -38,8 +38,10 @@
38
38
  "@vitejs/plugin-vue": "^5.1.3",
39
39
  "jest": "^29.7.0",
40
40
  "jest-transform-stub": "^2.0.0",
41
+ "jsdom": "^25.0.0",
41
42
  "ts-jest": "^29.2.3",
42
43
  "typescript": "^5.0.0",
44
+ "vitest": "^2.1.1",
43
45
  "vue": "^3.4.33"
44
46
  }
45
47
  }