This commit is contained in:
25
node_modules/search-insights/lib/utils/extractAdditionalParams.ts
generated
vendored
Normal file
25
node_modules/search-insights/lib/utils/extractAdditionalParams.ts
generated
vendored
Normal 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
|
||||
}
|
||||
);
|
||||
}
|
43
node_modules/search-insights/lib/utils/featureDetection.ts
generated
vendored
Normal file
43
node_modules/search-insights/lib/utils/featureDetection.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
};
|
29
node_modules/search-insights/lib/utils/getRequesterForBrowser.ts
generated
vendored
Normal file
29
node_modules/search-insights/lib/utils/getRequesterForBrowser.ts
generated
vendored
Normal 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."
|
||||
);
|
||||
}
|
20
node_modules/search-insights/lib/utils/getRequesterForNode.ts
generated
vendored
Normal file
20
node_modules/search-insights/lib/utils/getRequesterForNode.ts
generated
vendored
Normal 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
19
node_modules/search-insights/lib/utils/index.ts
generated
vendored
Normal 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
64
node_modules/search-insights/lib/utils/localStorage.ts
generated
vendored
Normal 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;
|
||||
}
|
||||
}
|
74
node_modules/search-insights/lib/utils/objectQueryTracker.ts
generated
vendored
Normal file
74
node_modules/search-insights/lib/utils/objectQueryTracker.ts
generated
vendored
Normal 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
97
node_modules/search-insights/lib/utils/request.ts
generated
vendored
Normal 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
15
node_modules/search-insights/lib/utils/uuid.ts
generated
vendored
Normal 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);
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user