first-commit
Some checks failed
CI Pipeline / build (push) Failing after 3m23s

This commit is contained in:
2025-08-27 14:05:33 +08:00
commit 9e1b8bdc9d
5159 changed files with 1081326 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
import type { InsightsAdditionalEventParams } from "../types";
export type WithAdditionalParams<TEventType> =
| InsightsAdditionalEventParams
| TEventType;
export function extractAdditionalParams<TEventType extends { index: string }>(
params: Array<InsightsAdditionalEventParams | TEventType>
): { events: TEventType[]; additionalParams?: InsightsAdditionalEventParams } {
return params.reduce(
({ events, additionalParams }, param) => {
// Real events all have `index` as a mandatory parameter, which we
// can rely on to distinguish them from additional parameters
if ("index" in param) {
return { additionalParams, events: [...events, param] };
}
return { events, additionalParams: param };
},
{
events: [] as TEventType[],
additionalParams: undefined as InsightsAdditionalEventParams | undefined
}
);
}

View File

@@ -0,0 +1,43 @@
export const supportsCookies = (): boolean => {
try {
return Boolean(navigator.cookieEnabled);
} catch (e) {
return false;
}
};
export const supportsSendBeacon = (): boolean => {
try {
return Boolean(navigator.sendBeacon);
} catch (e) {
return false;
}
};
export const supportsXMLHttpRequest = (): boolean => {
try {
return Boolean(XMLHttpRequest);
} catch (e) {
return false;
}
};
export const supportsNodeHttpModule = (): boolean => {
try {
/* eslint-disable @typescript-eslint/no-var-requires */
const { request: nodeHttpRequest } = require("http");
const { request: nodeHttpsRequest } = require("https");
/* eslint-enable */
return Boolean(nodeHttpRequest) && Boolean(nodeHttpsRequest);
} catch (e) {
return false;
}
};
export const supportsNativeFetch = (): boolean => {
try {
return fetch !== undefined;
} catch (e) {
return false;
}
};

View File

@@ -0,0 +1,29 @@
import {
supportsNativeFetch,
supportsSendBeacon,
supportsXMLHttpRequest
} from "./featureDetection";
import type { RequestFnType } from "./request";
import {
requestWithNativeFetch,
requestWithSendBeacon,
requestWithXMLHttpRequest
} from "./request";
export function getRequesterForBrowser(): RequestFnType {
if (supportsSendBeacon()) {
return requestWithSendBeacon;
}
if (supportsXMLHttpRequest()) {
return requestWithXMLHttpRequest;
}
if (supportsNativeFetch()) {
return requestWithNativeFetch;
}
throw new Error(
"Could not find a supported HTTP request client in this environment."
);
}

View File

@@ -0,0 +1,20 @@
import {
supportsNodeHttpModule,
supportsNativeFetch
} from "./featureDetection";
import type { RequestFnType } from "./request";
import { requestWithNodeHttpModule, requestWithNativeFetch } from "./request";
export function getRequesterForNode(): RequestFnType {
if (supportsNodeHttpModule()) {
return requestWithNodeHttpModule;
}
if (supportsNativeFetch()) {
return requestWithNativeFetch;
}
throw new Error(
"Could not find a supported HTTP request client in this environment."
);
}

19
node_modules/search-insights/lib/utils/index.ts generated vendored Normal file
View File

@@ -0,0 +1,19 @@
// use theses type checking helpers to avoid mistyping "undefind", I mean "undfined"
export const isUndefined = (value: any): value is undefined =>
typeof value === "undefined";
export const isString = (value: any): value is string =>
typeof value === "string";
export const isNumber = (value: any): value is number =>
typeof value === "number";
/* eslint-disable @typescript-eslint/ban-types */
export const isFunction = (value: any): value is Function =>
typeof value === "function";
/* eslint-enable */
export const isPromise = <T>(value: Promise<T> | T): value is Promise<T> =>
typeof (value as Promise<T> | undefined)?.then === "function";
export * from "./extractAdditionalParams";
export * from "./featureDetection";
export * from "./objectQueryTracker";

64
node_modules/search-insights/lib/utils/localStorage.ts generated vendored Normal file
View File

@@ -0,0 +1,64 @@
/**
* A utility class for safely interacting with localStorage.
*/
export class LocalStorage {
static store: Storage | undefined = ensureLocalStorage();
/**
* Safely get a value from localStorage.
* If the value is not able to be parsed as JSON, this method will return null.
*
* @param key - String value of the key.
* @returns Null if the key is not found or unable to be parsed, the value otherwise.
*/
static get<T>(key: string): T | null {
const val = this.store?.getItem(key);
if (!val) {
return null;
}
try {
return JSON.parse(val) as T;
} catch {
return null;
}
}
/**
* Safely set a value in localStorage.
* If the storage is full, this method will catch the error and log a warning.
*
* @param key - String value of the key.
* @param value - Any value to store.
*/
static set(key: string, value: any): void {
try {
this.store?.setItem(key, JSON.stringify(value));
} catch {
// eslint-disable-next-line no-console
console.error(
`Unable to set ${key} in localStorage, storage may be full.`
);
}
}
/**
* Remove a value from localStorage.
*
* @param key - String value of the key.
*/
static remove(key: string): void {
this.store?.removeItem(key);
}
}
function ensureLocalStorage(): Storage | undefined {
try {
const testKey = "__test_localStorage__";
globalThis.localStorage.setItem(testKey, testKey);
globalThis.localStorage.removeItem(testKey);
return globalThis.localStorage;
} catch {
return undefined;
}
}

View File

@@ -0,0 +1,74 @@
import { LocalStorage } from "./localStorage";
interface ObjectQueryMap {
[indexAndObjectId: string]: [queryId: string, timestamp: number];
}
const STORE = "AlgoliaObjectQueryCache";
const LIMIT = 5000; // 1 entry is typically no more than 100 bytes, so this is ~500kB worth of data - most browsers allow at least 5MB per origin
const FREE = 1000;
function getCache(): ObjectQueryMap {
return LocalStorage.get(STORE) ?? {};
}
function setCache(objectQueryMap: ObjectQueryMap): void {
LocalStorage.set(STORE, limited(objectQueryMap));
}
function limited(objectQueryMap: ObjectQueryMap): ObjectQueryMap {
return Object.keys(objectQueryMap).length > LIMIT
? purgeOldest(objectQueryMap)
: objectQueryMap;
}
function purgeOldest(objectQueryMap: ObjectQueryMap): ObjectQueryMap {
const sorted = Object.entries(objectQueryMap).sort(
([, [, aTimestamp]], [, [, bTimestamp]]) => bTimestamp - aTimestamp
);
const newObjectQueryMap = sorted
.slice(0, sorted.length - FREE - 1)
.reduce<ObjectQueryMap>(
(acc, [key, value]) => ({
...acc,
[key]: value
}),
{}
);
return newObjectQueryMap;
}
function makeKey(index: string, objectID: string): string {
return `${index}_${objectID}`;
}
export function storeQueryForObject(
index: string,
objectID: string,
queryID: string
): void {
const objectQueryMap = getCache();
objectQueryMap[makeKey(index, objectID)] = [queryID, Date.now()];
setCache(objectQueryMap);
}
export function getQueryForObject(
index: string,
objectID: string
): [queryId: string, timestamp: number] | undefined {
return getCache()[makeKey(index, objectID)];
}
export function removeQueryForObjects(
index: string,
objectIDs: string[]
): void {
const objectQueryMap = getCache();
objectIDs.forEach((objectID) => {
delete objectQueryMap[makeKey(index, objectID)];
});
setCache(objectQueryMap);
}

97
node_modules/search-insights/lib/utils/request.ts generated vendored Normal file
View File

@@ -0,0 +1,97 @@
import type { request as nodeRequest } from "http";
import type { UrlWithStringQuery } from "url";
export type RequestFnType = (
url: string,
data: Record<string, unknown>
) => Promise<boolean>;
export const requestWithXMLHttpRequest: RequestFnType = (url, data) => {
return new Promise((resolve, reject) => {
const serializedData = JSON.stringify(data);
const req = new XMLHttpRequest();
req.addEventListener("readystatechange", () => {
if (req.readyState === 4 && req.status === 200) {
resolve(true);
} else if (req.readyState === 4) {
resolve(false);
}
});
/* eslint-disable prefer-promise-reject-errors */
req.addEventListener("error", () => reject());
/* eslint-enable */
req.addEventListener("timeout", () => resolve(false));
req.open("POST", url);
req.setRequestHeader("Content-Type", "application/json");
req.send(serializedData);
});
};
export const requestWithSendBeacon: RequestFnType = (url, data) => {
const serializedData = JSON.stringify(data);
const beacon = navigator.sendBeacon(url, serializedData);
return Promise.resolve(beacon ? true : requestWithXMLHttpRequest(url, data));
};
export const requestWithNodeHttpModule: RequestFnType = (url, data) => {
return new Promise((resolve, reject) => {
const serializedData = JSON.stringify(data);
/* eslint-disable @typescript-eslint/no-var-requires */
const { protocol, host, path }: UrlWithStringQuery =
require("url").parse(url);
/* eslint-enable */
const options = {
protocol,
host,
path,
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": serializedData.length
}
};
const { request }: { request: typeof nodeRequest } = url.startsWith(
"https://"
)
? require("https")
: require("http");
const req = request(options, ({ statusCode }) => {
if (statusCode === 200) {
resolve(true);
} else {
resolve(false);
}
});
req.on("error", (error) => {
/* eslint-disable no-console */
console.error(error);
/* eslint-enable */
reject(error);
});
req.on("timeout", () => resolve(false));
req.write(serializedData);
req.end();
});
};
export const requestWithNativeFetch: RequestFnType = (url, data) => {
return new Promise((resolve, reject) => {
fetch(url, {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json"
}
})
.then((response) => {
resolve(response.status === 200);
})
.catch((e: Error) => {
reject(e);
});
});
};

15
node_modules/search-insights/lib/utils/uuid.ts generated vendored Normal file
View File

@@ -0,0 +1,15 @@
/**
* Create UUID according to
* https://www.ietf.org/rfc/rfc4122.txt.
*
* @returns Generated UUID.
*/
export function createUUID(): string {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
/* eslint-disable no-bitwise */
const r = (Math.random() * 16) | 0;
const v = c === "x" ? r : (r & 0x3) | 0x8;
/* eslint-enable */
return v.toString(16);
});
}