@findlane/browser 0.7.0 → 0.8.0
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.d.ts +42 -1
- package/dist/index.js +46 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -123,17 +123,57 @@ export declare class FindlaneError extends Error {
|
|
|
123
123
|
status: number;
|
|
124
124
|
constructor(status: number, message: string);
|
|
125
125
|
}
|
|
126
|
+
/** A product in a cart or an order. */
|
|
127
|
+
export type EventItem = {
|
|
128
|
+
objectID: string;
|
|
129
|
+
quantity?: number;
|
|
130
|
+
price?: number;
|
|
131
|
+
};
|
|
132
|
+
/** Where a clicked product was: its place in the results (from 1), and the search's query or the
|
|
133
|
+
* recommendation list (and the product whose list it was). */
|
|
134
|
+
export type ClickDetails = {
|
|
135
|
+
position?: number;
|
|
136
|
+
query?: string;
|
|
137
|
+
list?: "goesWith" | "similar" | "together";
|
|
138
|
+
from?: string;
|
|
139
|
+
};
|
|
140
|
+
/** Events from the page, sent in the background: a batch a second, and what's left when the page is
|
|
141
|
+
* hidden or closed. They carry no cookie or ID. */
|
|
142
|
+
export type BrowserEvents = {
|
|
143
|
+
view(objectID: string): void;
|
|
144
|
+
click(objectID: string, details?: ClickDetails): void;
|
|
145
|
+
addToCart(items: EventItem[], options?: {
|
|
146
|
+
currency?: string;
|
|
147
|
+
}): void;
|
|
148
|
+
/** Counts for Bought together only when the index allows orders from browsers (usually a server
|
|
149
|
+
* sends them); always counts on the index's Events page. */
|
|
150
|
+
purchase(order: {
|
|
151
|
+
orderID: string;
|
|
152
|
+
items: EventItem[];
|
|
153
|
+
currency?: string;
|
|
154
|
+
}): void;
|
|
155
|
+
/** With `events: "afterConsent"`: true sends the events held so far and those after; false drops them. */
|
|
156
|
+
consent(granted: boolean): void;
|
|
157
|
+
/** Sends the events waiting now. */
|
|
158
|
+
flush(): void;
|
|
159
|
+
};
|
|
160
|
+
type Fetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
126
161
|
/** The hosted Findlane API, used unless `baseUrl` says otherwise. */
|
|
127
162
|
export declare const defaultBaseUrl = "https://api.findlane.dev";
|
|
128
163
|
export type BrowserClientOptions = {
|
|
129
164
|
/** The API to call; defaults to the hosted Findlane API. */
|
|
130
165
|
baseUrl?: string;
|
|
131
166
|
publicId: string;
|
|
132
|
-
fetch?:
|
|
167
|
+
fetch?: Fetch;
|
|
168
|
+
/** Whether `events` sends anything: true (the default), false, or "afterConsent" to hold events until
|
|
169
|
+
* `events.consent(true)`. */
|
|
170
|
+
events?: boolean | "afterConsent";
|
|
133
171
|
};
|
|
134
172
|
export declare class SearchBrowserClient {
|
|
135
173
|
private readonly url;
|
|
136
174
|
private readonly fetcher;
|
|
175
|
+
/** Views, clicks, add-to-carts and orders, for the index's Events page and Bought together. */
|
|
176
|
+
readonly events: BrowserEvents;
|
|
137
177
|
constructor(options: BrowserClientOptions);
|
|
138
178
|
private post;
|
|
139
179
|
search<T = Record<string, unknown>>(request: SearchRequest, options?: {
|
|
@@ -151,3 +191,4 @@ export declare class SearchBrowserClient {
|
|
|
151
191
|
signal?: AbortSignal;
|
|
152
192
|
}): Promise<RecommendationsResponse<T>>;
|
|
153
193
|
}
|
|
194
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -8,14 +8,60 @@ export class FindlaneError extends Error {
|
|
|
8
8
|
}
|
|
9
9
|
/** The hosted Findlane API, used unless `baseUrl` says otherwise. */
|
|
10
10
|
export const defaultBaseUrl = "https://api.findlane.dev";
|
|
11
|
+
function eventSender(url, fetcher, mode) {
|
|
12
|
+
let queue = [];
|
|
13
|
+
let allowed = mode === "afterConsent" ? undefined : mode;
|
|
14
|
+
let timer;
|
|
15
|
+
let listening = false;
|
|
16
|
+
const flush = () => {
|
|
17
|
+
clearTimeout(timer);
|
|
18
|
+
timer = undefined;
|
|
19
|
+
while (allowed && queue.length) {
|
|
20
|
+
const body = JSON.stringify({ events: queue.splice(0, 100) });
|
|
21
|
+
// A beacon outlives the page; text/plain needs no CORS preflight.
|
|
22
|
+
if (fetcher || !(typeof navigator == "object" && navigator.sendBeacon?.(url, new Blob([body], { type: "text/plain" }))))
|
|
23
|
+
(fetcher ?? fetch)(url, { method: "POST", headers: { "content-type": "text/plain" }, body, keepalive: true })
|
|
24
|
+
.catch(() => undefined);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const add = (event) => {
|
|
28
|
+
if (allowed === false)
|
|
29
|
+
return;
|
|
30
|
+
if (queue.push(event) > 500)
|
|
31
|
+
queue.shift();
|
|
32
|
+
if (!listening && typeof document == "object") {
|
|
33
|
+
listening = true;
|
|
34
|
+
addEventListener("pagehide", flush);
|
|
35
|
+
document.addEventListener("visibilitychange", () => document.hidden && flush());
|
|
36
|
+
}
|
|
37
|
+
timer ??= setTimeout(flush, 1000);
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
view: (objectID) => add({ type: "view", objectID }),
|
|
41
|
+
click: (objectID, details) => add({ type: "click", objectID, ...details }),
|
|
42
|
+
addToCart: (items, options) => add({ type: "addToCart", items, ...options }),
|
|
43
|
+
purchase: (order) => add({ type: "purchase", ...order }),
|
|
44
|
+
consent(granted) {
|
|
45
|
+
allowed = granted;
|
|
46
|
+
if (granted)
|
|
47
|
+
flush();
|
|
48
|
+
else
|
|
49
|
+
queue = [];
|
|
50
|
+
},
|
|
51
|
+
flush,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
11
54
|
export class SearchBrowserClient {
|
|
12
55
|
url;
|
|
13
56
|
fetcher;
|
|
57
|
+
/** Views, clicks, add-to-carts and orders, for the index's Events page and Bought together. */
|
|
58
|
+
events;
|
|
14
59
|
constructor(options) {
|
|
15
60
|
if (!/^pub_[a-f0-9]{32}$/.test(options.publicId))
|
|
16
61
|
throw new Error("A valid public index ID (pub_…) is required");
|
|
17
62
|
this.url = `${(options.baseUrl ?? defaultBaseUrl).replace(/\/$/, "")}/api/public/indexes/${options.publicId}`;
|
|
18
63
|
this.fetcher = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
64
|
+
this.events = eventSender(`${this.url}/events`, options.fetch, options.events ?? true);
|
|
19
65
|
}
|
|
20
66
|
async post(operation, request, signal) {
|
|
21
67
|
const response = await this.fetcher(`${this.url}/${operation}`, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@findlane/browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Findlane browser SDK: fast, typo-tolerant public search from the browser with an index's public ID, plus React hooks and components.",
|
|
5
5
|
"keywords": ["findlane", "search", "full-text search", "typo tolerance", "instant search", "ecommerce search", "browser", "react", "autocomplete"],
|
|
6
6
|
"license": "MIT",
|