PageSourceSearch

https://sites.boompix.com/js/apiv2.js?v=582

js boompix.com collected 2026-10-09 18:03:07 UTC 30,586 bytes, 755 lines download raw bytes

1var api = {
2    apitoken: null,
3    refreshtoken: null,
4    path: "/api/v2",
5    version: "2.0",
6    sessionkey: null,
7    brandsessionkey: null,
8	url: function (path) { return `${api.path}/${path}`; },
9    /*
10     * Run fn with the signed-in USER's session (sent as `sid`) instead of whatever the page set up.
11     * A brand session (`bsid`) authenticates as the brand - the API takes its user id from the brand row,
12     * i.e. the brand owner - which suits brand-scoped calls such as the Titus pre-auths, but not a call that
13     * acts for the shopper (adding their card). Pages like the cart set brandsessionkey for those brand-scoped
14     * calls, so the user session is swapped in only for the duration of fn and then put back.
15     */
16    asUser: async function (userSessionKey, fn) {
17        var prev = api.sessionkey;
18        api.sessionkey = userSessionKey;
19        try {
20            return await fn();
21        } finally {
22            api.sessionkey = prev;
23        }
24    },
25    sqlannotation: s => {
26        if (s.database?.length ?? false) {
27            var grid = $(".adminlogin-grid tbody tr");
28            if (grid.length) {
29                s.database.forEach(row => {
30                    var idx = grid.length - 1;
31                    var newRow = $(`<tr><td>${row.duration / 1000}</td><td>API</td><td></td><td>${row.sql}</td></tr>`);
32                    newRow.insertBefore($(`.adminlogin-grid tbody tr:nth(${idx})`));
33                });
34            }
35        }
36    },
37	authorize: async function (sessionkey) {
38		if (api.apitoken) { return api.apitoken; }
39
40        var response = await fetch(api.url("Auth/authorize"), {
41            method: "POST",
42            headers: {
43                "Accept": "application/json",
44                "Content-Type": "application/json",
45				"hd-version": "2.0"
46            },
47            body: JSON.stringify({ "sessionKey": sessionkey })
48        });
49        if (response.ok) {
50            var data = await response.json();
51            api.apitoken = data.accessToken;
52            api.refreshtoken = data.refreshToken;
53            return api.apitoken;
54        } else {
55            return false;
56        }
57    },
58	refreshToken: async function () {
59		var response = await fetch(api.url('auth/refresh-token'), {
60				method: "POST",
61				headers: {
62					"Accept": "application/json",
63					"Content-Type": "application/json",
64					"hd-version": "2.0"
65				},
66				body: JSON.stringify({
67					accessToken: api.apitoken,
68					refreshToken: api.refreshtoken
69				})
70		});
71        if (response.ok) {
72            var j = await response.json();
73            api.apitoken = j.accessToken;
74            api.refreshtoken = j.refreshToken;
75            return true;
76        }
77
78        return false;
79    },
80	/*
81	 * `version` selects the API version via the hd-version header, and DEFAULTS TO "2.0" so every
82	 * existing caller behaves exactly as before — the argument is opt-in, added for the v3 UX routes
83	 * (ApiVersions.THREE_UX) that legacy pages now need to reach.
84	 *
85	 * Note the path segment /api/v2/ is NOT the version. It is the ARR reverse-proxy prefix; the
86	 * version is this header and nothing else, which is why a v3 route still lives under /api/v2/.
87	 */
88	get: async function (url, data, skipauth, version) {
89		if (skipauth == undefined) { skipauth = false; }
90
91        if (!skipauth) {
92            if (!api.sessionkey && !api.brandsessionkey) {
93			    var auth = await api.authorize();
94			    if (!auth) {
95				    console.error("could not authorize api");
96				    return false;
97			    }
98            }
99		}
100		var url2 = api.url(url);
101		//fetch does not support "body" when "getting"
102		var esc = encodeURIComponent;
103		if (data) {
104			var query = Object.keys(data)
105				.map(k => `${esc(k)}=${esc(data[k])}`)
106				.join('&');
107			if (query.length)
108				url2 = `${url2}?${query}`;
109		}
110
111        var headers = {
112            "Accept": "application/json",
113			"Content-Type": "application/json",
114			"hd-version": version || "2.0"
115        };
116
117        if (!skipauth) {
118            var response;
119            if (api.sessionkey) {
120                headers.sid = api.sessionkey;
121            } else if (api.brandsessionkey) {
122                headers.bsid = api.brandsessionkey;
123            } else {
124                headers.Authorization = `Bearer ${api.apitoken}`;
125            }
126		}
127        var response = await fetch(url2, { headers });
128
129		if (response.ok) {
130			return response;
131		} else {
132			if (response.status == 401) {
133				if (!skipauth) {
134					var r2 = await api.refreshToken();
135					if (r2) {   //re-run the command now that we are re-auth'd — carrying the version,
136						        //or the retry would silently drop to 2.0 and 404 a v3 route
137						return await api.get(url, data, skipauth, version);
138					}
139				}
140			}
141		}
142
143		console.error("could not authorize api");
144		return false;
145	},
146    /** `version` defaults to "2.0" — see the note on `get`. */
147    post: async function (url, data, skipauth, version) {
148        if (skipauth == undefined) { skipauth = false; }
149
150        if (!skipauth) {
151            if (!api.sessionkey && !api.brandsessionkey) {
152                var auth = await api.authorize();
153                if (!auth) {
154                    console.error("could not authorize api");
155                    return false;
156                }
157            }
158        }
159
160        var headers = {
161            "Accept": "application/json",
162            "Content-Type": "application/json",
163            "hd-version": version || "2.0"
164        };
165
166        if (!skipauth) {
167            var response;
168            if (api.sessionkey) {
169                headers.sid = api.sessionkey;
170            } else if (api.brandsessionkey) {
171                headers.bsid = api.brandsessionkey;
172            } else {
173                headers.Authorization = `Bearer ${api.apitoken}`;
174            }
175        }
176        var response = await fetch(api.url(url), {
177            method: "POST",
178            headers,
179            body: data
180        });
181
182        if (response.ok) {
183            return response;
184        } else {
185            if (response.status == 401) {
186                var r2 = await api.refreshToken();
187                if (r2) {
188                    // Version carried through, or the retry drops to 2.0 and 404s a v3 route.
189                    return await api.post(url, data, skipauth, version);
190                }
191            }
192        }
193        return false;
194    },
195    delete: async function (url, data, skipauth) {
196        if (skipauth == undefined) { skipauth = false; }
197
198        if (!skipauth) {
199            if (!api.sessionkey && !api.brandsessionkey) {
200                var auth = await api.authorize();
201                if (!auth) {
202                    console.error("could not authorize api");
203                    return false;
204                }
205            }
206        }
207
208        var headers = {
209            "Accept": "application/json",
210            "Content-Type": "application/json",
211            "hd-version": "2.0"
212        };
213
214        if (!skipauth) {
215            var response;
216            if (api.sessionkey) {
217                headers.sid = api.sessionkey;
218            } else if (api.brandsessionkey) {
219                headers.bsid = api.brandsessionkey;
220            } else {
221                headers.Authorization = `Bearer ${api.apitoken}`;
222            }
223        }
224        var response = await fetch(api.url(url), {
225            method: "DELETE",
226            headers,
227            body: data
228        });
229
230        if (response.ok) {
231            return response;
232        } else {
233            if (response.status == 401) {
234                var r2 = await api.refreshToken();
235                if (r2) {
236                    return await api.post(url, data);
237                }
238            }
239        }
240        return false;
241    },
242    put: async function (url, data, skipauth) {
243		if (skipauth == undefined) { skipauth = false; }
244
245        if (!skipauth) {
246            if (!api.sessionkey && !api.brandsessionkey) {
247                var auth = await api.authorize();
248                if (!auth) {
249                    console.error("could not authorize api");
250                    return false;
251                }
252            }
253        }
254
255        var headers = {
256            "Accept": "application/json",
257            "Content-Type": "application/json",
258			"hd-version": "2.0"
259        };
260
261        if (!skipauth) {
262            var response;
263            if (api.sessionkey) {
264                headers.sid = api.sessionkey;
265            } else if (api.brandsessionkey) {
266                headers.bsid = api.brandsessionkey;
267            } else {
268                headers.Authorization = `Bearer ${api.apitoken}`;
269            }
270        }
271        var response = await fetch(api.url(url), {
272            method: "PUT",
273            headers,
274            body: data
275        });
276
277        if (response.ok) {
278            return response;
279        } else {
280            if (response.status == 401) {
281                var r2 = await api.refreshToken();
282                if (r2) {
283                    return await api.post(url, data);
284                }
285            }
286        }
287        return false;
288    },
289    /** `version` is passed straight through to `get` — defaults to "2.0" there. */
290    getAnnotatedSql: async function (url, data, skipauth, version) {
291        var response = await api.get(url, data, skipauth, version);
292        if (response) {
293            if (response.ok) {
294                var j = await response.json();
295                api.sqlannotation(j);
296                return j.data;
297            }
298        }
299
300        console.error("getAnnotatedSql could not retrieve results");
301        throw new Error("getAnnotatedSql could not retrieve results");
302    },
303    /** `version` is passed straight through to `post` — defaults to "2.0" there. */
304    postAnnotatedSql: async function (url, data, skipauth, version) {
305        var response = await api.post(url, data, skipauth, version);
306        if (response) {
307            if (response.ok) {
308                var j = await response.json();
309                api.sqlannotation(j);
310                return j.data;
311            }
312        }
313
314        console.error("postAnnotatedSql could not retrieve results");
315        throw new Error("postAnnotatedSql could not retrieve results");
316    },
317    deleteAnnotatedSql: async function (url, data, skipauth) {
318        var response = await api.delete(url, data, skipauth);
319        if (response) {
320            if (response.ok) {
321                var j = await response.json();
322                api.sqlannotation(j);
323                return j.data;
324            }
325        }
326
327        console.error("deleteAnnotatedSql could not retrieve results");
328        throw new Error("deleteAnnotatedSql could not retrieve results");
329    },
330    putAnnotatedSql: async function (url, data, skipauth) {
331        var response = await api.put(url, data, skipauth);
332        if (response) {
333            if (response.ok) {
334                var j = await response.json();
335                api.sqlannotation(j);
336                return j.data;
337            }
338        }
339
340        //BUG-168: this alone of the four annotated helpers used to fall off the end and
341        //return undefined on a failed request, so callers ran their success path anyway.
342        console.error("putAnnotatedSql could not retrieve results");
343        throw new Error("putAnnotatedSql could not retrieve results");
344	},
345	getBrand: async function (bid) {
346		return await api.getAnnotatedSql(`brands/${bid}`);
347	},
348    getDashClientWelcome: async function () {
349		return await api.getAnnotatedSql("Dash/clientwelcome");
350    },
351    getDashUnpaidOrders: async function () {
352		return await api.getAnnotatedSql("dashboards/unpaid-orders");
353    },
354    getDashExpiringDomains: async function () {
355		return await api.getAnnotatedSql("Dash/expiringdomains"); //used in MOBILE APP too
356    },
357    getDashScheduledOrders: async function (qty, offset) {
358		return await api.postAnnotatedSql("dashboards/scheduled-orders", JSON.stringify({ "quantity": qty, "offset": offset }));
359    },
360    getDashExpiringSites: async function () {
361		return await api.getAnnotatedSql("dashboards/expiring-sites");
362    },
363    getDashClientReferralCode: async function () {
364		return await api.getAnnotatedSql("dashboards/client-referral-codes");
365    },
366    getDashListOfBusinesses: async function (qty, offset) {
367		return await api.postAnnotatedSql("dashboards/businesses", JSON.stringify({ "quantity": qty, "offset": offset }));
368    },
369    getDashPreparingSites: async function (qty, offset) {
370		return await api.postAnnotatedSql("dashboards/preparing-sites", JSON.stringify({ "quantity": qty, "offset": offset }));
371    },
372    getDashActiveProperties: async function (qty, offset) {
373		return await api.postAnnotatedSql("dashboards/active-properties", JSON.stringify({ "quantity": qty, "offset": offset }));
374    },
375    getDashSuccessWeek: async function (dtstart, dtend) {
376		return await api.postAnnotatedSql("dashboards/success-week", JSON.stringify({ first: dtstart, end: dtend }));
377    },
378  //  getDashTickler: async function (qty, offset) {
379		//return await api.postAnnotatedSql("dashboards/ticklers", JSON.stringify({"quantity": qty, "offset": offset}));
380  //  }, 
381    getDashNewAccountGoals: async function (dtstart) {
382		return await api.getAnnotatedSql("dashboards/new-account-goals", { from: dtstart });
383    },
384    getDashTopBrands: async function (qty) {
385		return await api.getAnnotatedSql(`dashboards/top-brands/${qty}`);
386    },
387    getDashMonthlyGoals: async function () {
388		return await api.getAnnotatedSql("dashboards/monthly-goals");
389    },
390    getDashTopClients: async function () {
391		return await api.getAnnotatedSql("dashboards/top-clients");
392    },
393    getDashNewClients: async function () {
394		return await api.getAnnotatedSql("dashboards/new-clients");
395    },
396    getDashNeedsToBeCompleted: async function (markets, qty, offset, sort) {
397		return await api.postAnnotatedSql("dashboards/needs-completion", JSON.stringify({ "markets": markets, "quantity": qty, "offset": offset, "sort": sort }));
398    },
399    getDashAssignedSitesToBeCompleted: async function (markets, qty, offset, sort) {
400		return await api.postAnnotatedSql("dashboards/assigned-sites-to-complete", JSON.stringify({ "markets": markets, "quantity": qty, "offset": offset, "sort": sort }));
401    },
402    getDashNeedsToBeScheduled: async function (markets, qty, offset, sort) {
403		return await api.postAnnotatedSql("dashboards/needs-scheduling", JSON.stringify({ "markets": markets, "quantity": qty, "offset": offset, "sort": sort }));
404    },
405    getDashNeedsToBeActivated: async function (qty, offset, sort) {
406		return await api.postAnnotatedSql("dashboards/needs-activation", JSON.stringify({ "quantity": qty, "offset": offset, "sort": sort }));
407    },
408    getDashQuickStart: async function () {
409		return await api.getAnnotatedSql("dashboards/quick-start");
410    },
411    getDashThisWeek: async function (dtstart, dtend, markets) {
412		return await api.postAnnotatedSql("dashboards/this-week", JSON.stringify({ "firstDate": dtstart, "lastDate": dtend, "markets": markets }));
413    },
414    getDashQuestion: async function () {
415		return await api.getAnnotatedSql("dashboards/questions");
416    },
417    getDashUserMessages: async function () {
418		return await api.getAnnotatedSql("dashboards/user-messages");
419    },
420    UserMessageDismiss: async function (nusermessageid) {
421		return await api.deleteAnnotatedSql(`dashboards/user-messages/${nusermessageid}`);
422    },
423    getDashLearningCenter: async function () {
424		return await api.getAnnotatedSql("dashboards/learning-center");
425	},
426	getPricingCommodities: async function () {
427		return await api.getAnnotatedSql("pricing/admin/commodities");
428	},
429	getPricingCommodity: async function (cid) {
430		return await api.getAnnotatedSql("pricing/admin/commodities/" + cid);
431	},
432	getPricingPrices: async function (cid, bid, cuid, onlyGlobal, includeDeleted) {
433		var qs = null;
434		if (cid != undefined && cid != null) {
435			qs = (qs ? qs + "&" : "") + "commodityId=" + cid;
436		}
437		if (cuid != undefined && cuid != null) {
438			qs = (qs ? qs + "&" : "") + "currencyId=" + cuid;
439		}
440		if (bid != undefined && bid != null) {
441			qs = (qs ? qs + "&" : "") + "brandId=" + bid;
442		}
443		if (onlyGlobal != undefined && onlyGlobal != null) {
444			qs = (qs ? qs + "&" : "") + "onlyGlobal=" + onlyGlobal;
445		}
446		if (includeDeleted != undefined) {
447			qs = (qs ? qs + "&" : "") + "includeDeleted=" + includeDeleted;
448		}
449		return await api.getAnnotatedSql("pricing/admin/prices" + ((qs != null) ? "?" + qs : ""));
450	},
451	getPricingCurrencies: async function () {
452		return await api.getAnnotatedSql("pricing/currencies");
453	},
454	addRewards: async function (rewards) {
455		return await api.putAnnotatedSql(`pricing/admin/rewards/`, JSON.stringify({
456			"brandId": rewards.brandId,
457			"commodityId": rewards.commodityId,
458			"userId": rewards.userId,
459			"quantity": rewards.quantity,
460			"note": rewards.note
461		}));
462	},
463    getRewards: async function (bid) {
464		return await api.getAnnotatedSql(`activations/rewards/${bid ?? ""}`);
465    },
466    searchBrands: async function (search, sort, qty, offset) {
467        return await this.postAnnotatedSql(`business/search`, JSON.stringify({
468            "search": search.search,
469            "sort": sort,
470            "quantity": qty,
471            "offset": offset
472        }));
473    },
474    searchClients: async function (search, sort, qty, offset) {
475        return await api.postAnnotatedSql(`search/clients`, JSON.stringify({
476            "username": search.username,
477            "email": search.email,
478            "phone": search.phone,
479            "groupname": search.groupname,
480            "userid": search.userid,
481            "usertype": search.usertype,
482            "statusid": search.statusid,
483            "sort": sort,
484            "quantity": qty,
485            "offset": offset
486        }));
487    },
488    searchGroups: async function (search, sort, qty, offset) {
489        return await api.postAnnotatedSql(`search/groups`, JSON.stringify({
490            "groupname": search.groupname,
491            "address": search.address,
492            "city": search.city,
493            "region": search.state,
494            "postalCode": search.postalcode,
495            "phone": search.phone,
496            "groupid": search.groupid,
497            "statusid": search.statusid,
498            "sort": sort,
499            "quantity": qty,
500            "offset": offset
501        }));
502	},
503    searchSitesFuzzy: async function (search, sort, qty, offset) {
504        return await api.postAnnotatedSql(`search/FuzzySites`, JSON.stringify({
505    	    "search": search.searchfuzzy,
506    	    "sort": sort,
507    	    "quantity": qty,
508    	    "offset": offset,
509    	    "statusname": search.statusname
510        }));
511    },
512	// API-461 changed /sites/search's `shoot` entries from date-only ("2025-02-11") to a brand-local
513	// wall clock with time ("2025-02-11 08:30:00") - offset-less, never UTC. Naively appending
514	// "T00:00" (the old assumption) turns the new shape into "2025-02-11 08:30:00T00:00", which
515	// `new Date(...)` can't parse, producing NaN/NaN in hd.shortDate. Hand-parse both shapes into
516	// explicit date parts instead of leaning on the platform's string parsing.
517	parseWallClock: function (v) {
518		var m = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2}))?)?/.exec(String(v || '').trim());
519		if (!m) { return null; }
520		return new Date(+m[1], +m[2] - 1, +m[3], +(m[4] || 0), +(m[5] || 0), +(m[6] || 0));
521	},
522	searchSitesNew: function (search, sort, qty, offset) {
523        //strip off the first sort, only one allowed in this endpoint
524        sort = sort.split(',')[0];
525        const map = new Map();
526        map.set("cr", "created");
527        map.set("cr desc", "created desc")
528        map.set("br", "bought");
529        map.set("br desc", "bought desc")
530        map.set("a", "address");
531        map.set("a desc", "address desc")
532        map.set("st", "city");
533        map.set("st desc", "city desc")
534        map.set("lp", "price");
535        map.set("lp desc", "price desc")
536        map.set("un", "client");
537        map.set("un desc", "client desc")
538
539        //if (sort == "cr") sort = "created";
540        //if (sort == "cr desc") sort = "created desc";
541
542        if (sort) {
543            sort = map.get(sort);
544        }
545
546        var encode = function (sizes) {
547            if (sizes?.images) {
548                return sizes.images.map((s, i) => {
549                    if (s.url) {
550                        var parsed = s.url.split('/').slice(-2).join("/");
551                        var parts = parsed.split(".");
552                        parts.pop(); //remove last item;
553                        var r = parts.join(".");
554
555                        r += `:${s.width}`;
556                        r += `:${s.height}`;
557                        if (sizes.faces?.length) {
558                            r += ":";
559                            r += sizes.faces.map((s, i) => {
560                                return s.join('`');
561                            }).join("|");
562                        }
563
564                        return r;
565                    }
566
567                    return null;
568                }).join(',');
569            }
570
571            return null;
572        };
573
574        var statmap = new Map();
575        statmap.set("deleted", -1);
576        statmap.set("inactive", 0);
577        statmap.set("active", 1);
578        statmap.set("complete", 5);
579        statmap.set("reserved", 10);
580        return api.getAnnotatedSql(`sites/search?sort=${sort}&quantity=${qty}&offset=${offset}${(search.searchfuzzy ? `&search=${search.searchfuzzy}` : '')}`)
581            .then(data => {
582                if (data) {
583                    return data.map(d => {
584                        d.id = d.siteId;
585                        d.sid = statmap.get(d.status);
586                        d.b = d.brandId;
587                        d.bid = d.bannerId;
588                        d.dc = d.downloadCreditId;
589                        d.lc = d.liveCreditId;
590                        d.dp = d.downloadPurchaseId;
591                        d.lp2 = d.livePurchaseId;
592                        d.ac = d.activated;
593                        d.lp = d.listPrice;
594                        d.a = d.location?.address;
595                        d.a2 = d.location?.address2;
596                        d.ci = d.location?.city;
597                        d.st = d.location?.region;
598                        d.zc = d.location?.postalCode;
599                        d.m = d.mls;
600                        d.n = d.notes;
601                        d.cd = d.created;
602                        //d.cr = 
603                        d.bd = d.bought;
604                        //d.br =
605                        d.osl = d.shootList;
606                        d.o = d.oids;
607                        d.sIDList = d.idlist;
608                        d.sList = d.list;
609                        d.sh = d.shoot?.map(sd => hd.shortDate(api.parseWallClock(sd))).join(", ");
610                        d.aun = d.assigned;
611                        //d.s = 
612                        d.age = d.daysActive;
613                        d.uid = d.clientId;
614                        d.cuid = d.coclientId;
615                        d.un = d.clientName;
616                        d.cun = d.coclientName;
617                        d.gn = d.groupName;
618                        //d.uids;
619                        //d.ss;
620                        d.us = encode(d.clientMedia);
621                        //d.ue;
622                        d.cus = encode(d.coclientMedia);
623                        //d.cue;
624                        d.s1 = encode(d.media1);
625                        //d.s2;
626                        //d.s3;
627                        //d.s4;
628                        //d.e1;
629                        //d.e2;
630                        //d.e3;
631                        //d.e4;
632
633                        return d;
634                    });
635                }
636
637                return [];
638            });
639	},
640    searchSites: async function (search, sort, qty, offset) {
641        return await api.postAnnotatedSql(`search/sites`, JSON.stringify({
642            "address": search.address,
643            "city": search.city,
644            "region": search.state,
645            "postalCode": search.postalcode,
646            "username": search.username,
647            "photographername": search.photographername,
648            "siteid": search.siteid,
649            "orderid": search.orderid,
650            "userid": search.userid,
651            "sort": sort,
652            "quantity": qty,
653            "offset": offset,
654            "statusname": search.statusname
655        }));
656    },
657	iguideMediaUpload: async function (nIGuideOrderID) {
658		return await api.putAnnotatedSql(`vendors/iguide/orders/${nIGuideOrderID}/uploads`);
659	},
660	iguidePlaceOrder: async function (nOrderProductID) {
661		return await api.postAnnotatedSql(`vendors/iguide/orders/placements`, JSON.stringify({ orderProductId: nOrderProductID }));
662    },
663    iguideUsers: async function (nBrandID) {
664        return await api.getAnnotatedSql(`vendors/iguide/users`);
665    },
666    iguideAgentBanners: async function (nAgentUserID, nBrandID) {
667        var qs = `${nAgentUserID ? `?agentId=${nAgentUserID}` : null}`;
668        if (nBrandID)
669            qs += (qs ? '&' : '?') + `brandId=${nBrandID}`;
670
671        return await api.getAnnotatedSql(`vendors/iguide/banners${qs}`);
672    },
673    iguideUpdateAgentBanner: async function (nUserID, bannerId, bannerName) {
674		return await api.postAnnotatedSql(`vendors/iguide/clients/${nUserID}/banners`, JSON.stringify({ bannerId, bannerName }));
675    },
676    iguideUpdateOrderBanner: async function (nIguideOrderID, bannerId, bannerName) {
677        return await api.postAnnotatedSql(`vendors/iguide/orders/${nIguideOrderID}/banners`, JSON.stringify({ bannerId, bannerName }));
678    },
679    //iguideUpdateOrderBanner: async function (nIGuideOrderID, bannerId) {
680    //    return await api.postAnnotatedSql(`vendors/iguide/orderbanner/${nIGuideOrderID}`, JSON.stringify({ bannerId }));
681    //},
682    iguideUpdateHDUser: async function (nUserID, sIGuideUserID) {
683		return await api.postAnnotatedSql(`vendors/iguide/users`, JSON.stringify({ iguideUserId: nUserID, iguideUserExternalId: sIGuideUserID}))
684    },
685    cubiCasaUsers: async function (nBrandID) {
686        return await api.getAnnotatedSql(`vendors/cubicasa/users`);
687    },
688    createLocalCCOrder: async function (nSiteID) {
689		return await api.postAnnotatedSql(`vendors/cubicasa/orders`, JSON.stringify({ siteId: nSiteID }));
690    },
691    cubiCasaActivityLog: async function (nCubiCasaOrderID) {
692        return await api.getAnnotatedSql(`vendors/cubicasa/orders/${nCubiCasaOrderID}/activity-logs`);
693	},
694	getMarketingKitSiteData: async function (nSiteID, bIncludeV1) {
695		return await api.getAnnotatedSql(`marketing-kits/sites/${nSiteID}?includeV1=${bIncludeV1}`, null, true);
696    },
697    getESoftProducts(nBrandID) {
698        return api.getAnnotatedSql(`vendors/esoft/products${(nBrandID ? `?brandId=${nBrandID}` : '')}`)
699            .then(data => {
700                if (data) {
701                    return data.map(d => {
702                        if (d.editMethod == 'AI_ONLY') {
703                            d.ainame = `${d.name} (AI Powered)`;
704                            d.isai = true;
705                        }
706                        delete d.editMethod;
707                        return d;
708                    });
709                }
710                return [];
711            });
712    },
713    titusPreAuth: async function(orderStartGuid, orderId, successUrl, cancelUrl, estimatedAmount, brandId) {
714            return api.postAnnotatedSql(`vendors/titus/pre-auths/${brandId ?? ""}`, JSON.stringify({ orderStartGuid, orderId, successUrl, cancelUrl, estimatedAmount }));
715    },
716    titusAuthorization: async function (id) {
717        return api.getAnnotatedSql(`vendors/titus/authorizations/${id}`, JSON.stringify({}));
718    },
719    titusCancelAuthorization: async function (id) {
720        return api.deleteAnnotatedSql(`vendors/titus/authorizations/${id}`, JSON.stringify({}));
721    },
722    //the payments/* routes below are v3-only (ApiVersions.THREE_UX), so they must ask for "3.0" - the "2.0" default is refused
723    titusPaymentMethodsUrl: async function () {
724        return api.getAnnotatedSql(`payments/paymentsmanagesurl`, undefined, false, "3.0");
725    },
726    //API-510: signed URL for Titus's add-card popup (titus.addPaymentMethod). clientId: the client the card is for
727    //when a team member orders on their behalf; omit for the signed-in user. Both this and paymentCards act for a
728    //user, so call them inside api.asUser(...) - with only a brand session the API sees the brand owner.
729    titusAddPaymentMethodUrl: async function (clientId) {
730        return api.postAnnotatedSql(clientId ? `payments/clients/${clientId}/cards/add-card-url` : `payments/clients/cards/add-card-url`, JSON.stringify({}), false, "3.0");
731    },
732    //{ paymentCustomerId, defaultId, found, cards: [{ id, brand, last4, expM
732onth, expYear, name, type, label }] }
733    paymentCards: async function (clientId) {
734        return api.getAnnotatedSql(clientId ? `payments/clients/${clientId}/cards` : `payments/clients/cards`, undefined, false, "3.0");
735    },
736    //API-613: Titus saves every card its popup adds, even one already on file. { cardId, deduplicated }: the card the
737    //page should use - the original when the new one was an exact duplicate (and has been removed). Inside api.asUser.
738    titusDeduplicateCard: async function (clientId, cardId) {
739        return api.postAnnotatedSql(clientId ? `payments/clients/${clientId}/cards/${encodeURIComponent(cardId)}/deduplications` : `payments/clients/cards/${encodeURIComponent(cardId)}/deduplications`, JSON.stringify({}), false, "3.0");
740    },
741    //API-613: a logged-out cart has no user to save a Titus card to, so it makes a guest Titus customer under the brand
742    //session (bsid) - never the brand owner's own customer. order.asp claims it for the user it creates or matches.
743    //{ customerId, url }: the guest customer and the signed URL for Titus's add-card popup
744    titusGuestCustomer: async function (name, email) {
745        return api.postAnnotatedSql(`vendors/titus/guest-customers`, JSON.stringify({ name, email }));
746    },
747    //{ url }: another add-card popup on the same guest customer (a second card, or a retry)
748    titusGuestAddCardUrl: async function (customerId) {
749        return api.postAnnotatedSql(`vendors/titus/guest-customers/${encodeURIComponent(customerId)}/add-card-url`, JSON.stringify({}));
750    },
751    //the guest customer's cards, the same shape as paymentCards
752    titusGuestCards: async function (customerId) {
753        return api.getAnnotatedSql(`vendors/titus/guest-customers/${encodeURIComponent(customerId)}/cards`);
754    }
755};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.