@thg-agentic/hyper-personalisation-web 0.0.0-stage → 3.18.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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +860 -2
  3. package/dist/hp.min.js +3 -0
  4. package/dist/snippet.min.js +1 -0
  5. package/package.json +83 -3
  6. package/src/assignment.js +75 -0
  7. package/src/auto/attributes.js +16 -0
  8. package/src/auto/exposures.js +45 -0
  9. package/src/auto/lists.js +136 -0
  10. package/src/auto/promotions.js +104 -0
  11. package/src/auto/rails.js +217 -0
  12. package/src/auto/viewability.js +150 -0
  13. package/src/auto.js +13 -0
  14. package/src/bootstrap.js +504 -0
  15. package/src/carry.js +74 -0
  16. package/src/conformance.js +530 -0
  17. package/src/consent-gate.js +324 -0
  18. package/src/consent.js +604 -0
  19. package/src/context.js +439 -0
  20. package/src/datalayer.js +411 -0
  21. package/src/debug.js +45 -0
  22. package/src/decisions.js +90 -0
  23. package/src/device-storage.js +213 -0
  24. package/src/experiments.js +220 -0
  25. package/src/ga4.js +124 -0
  26. package/src/hash.js +138 -0
  27. package/src/hp.js +135 -0
  28. package/src/index.js +840 -0
  29. package/src/preview.js +44 -0
  30. package/src/recommendation-context.js +101 -0
  31. package/src/sampling.js +19 -0
  32. package/src/segment.js +388 -0
  33. package/src/session/category.js +40 -0
  34. package/src/session/record.js +176 -0
  35. package/src/session/rules.js +195 -0
  36. package/src/session/signals.js +150 -0
  37. package/src/session-id.js +62 -0
  38. package/src/session.js +28 -0
  39. package/src/settings.js +230 -0
  40. package/src/shape.js +120 -0
  41. package/src/social-proof.js +139 -0
  42. package/src/spec-version.js +4 -0
  43. package/src/storage-scope.js +164 -0
  44. package/src/transport.js +451 -0
  45. package/src/version.js +2 -0
  46. package/types/auto.d.ts +51 -0
  47. package/types/bootstrap.d.ts +165 -0
  48. package/types/consent.d.ts +126 -0
  49. package/types/datalayer.d.ts +76 -0
  50. package/types/events.d.ts +713 -0
  51. package/types/hp.d.ts +64 -0
  52. package/types/index.d.ts +617 -0
  53. package/types/segment.d.ts +68 -0
package/src/hp.js ADDED
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The page's client, callable from anywhere before it exists: `hp.productAdded({ … })` in any
3
+ * component, with no reference to pass around and nothing to await.
4
+ *
5
+ * Calls made before `bootstrap` has started the client wait here, each with the messageId and time
6
+ * it was made at, and replay in order once it has; calls after it go straight to the client. When
7
+ * the client never starts they wait for the page to go: a page that could not reach the API, or that
8
+ * navigated before it answered, carries them to the next page, whose bootstrap replays them (3.9,
9
+ * ./carry.js); a page that is off (no key, disabled, invalid config) drops them.
10
+ * Nothing here ever throws into the caller, and the module is tiny, so a component can use it on a
11
+ * site that never loads the SDK.
12
+ */
13
+
14
+ import { keptVisit } from './recommendation-context.js';
15
+ import { uuid } from './shape.js';
16
+
17
+ const INSTANCE = Symbol.for('@thg-agentic/hyper-personalisation-web.bootstrap');
18
+ /** How bootstrap hands this queue its client, without the queue being part of the public API. */
19
+ const DRAIN = Symbol.for('@thg-agentic/hyper-personalisation-web.drain');
20
+ /** Where bootstrap leaves the store that carries this queue across a navigation. */
21
+ const CARRY = Symbol.for('@thg-agentic/hyper-personalisation-web.carry');
22
+ const REPLAY = Symbol.for('@thg-agentic/hyper-personalisation-web.replay');
23
+ const ASSIGNMENT = Symbol.for(
24
+ '@thg-agentic/hyper-personalisation-web.assignment',
25
+ );
26
+ const RECOMMENDATION_CONTEXT = Symbol.for(
27
+ '@thg-agentic/hyper-personalisation-web.recommendation-context',
28
+ );
29
+
30
+ /** Enough for a page's worth of interactions before the SDK loads; later calls are dropped. */
31
+ const MAX_QUEUED = 200;
32
+
33
+ /** The document all of this belongs to: one per module in a browser, several in a test. */
34
+ let page = null;
35
+ let queue = [];
36
+ /** The bootstrap the queue drains into, and what it resolved to: undefined until then. */
37
+ let bound = null;
38
+ let client;
39
+
40
+ function invoke(target, [method, args, made]) {
41
+ try {
42
+ if (made && target[REPLAY]) target[REPLAY](method, args, made);
43
+ else if (typeof target[method] === 'function') target[method](...args);
44
+ } catch (err) {
45
+ console.warn(`[hp] ${method} failed`, err);
46
+ }
47
+ }
48
+
49
+ function bind(pending) {
50
+ if (pending === bound) return;
51
+ bound = pending;
52
+ client = undefined;
53
+ const drain = (started) => {
54
+ if (bound !== pending) return;
55
+ client = started ?? null;
56
+ if (client) for (const call of queue.splice(0)) invoke(client, call);
57
+ };
58
+ Promise.resolve(pending).then(drain, () => drain(null));
59
+ }
60
+
61
+ /** The page is going before the client started: what it queued goes with it, if it may. */
62
+ function carry(event) {
63
+ if (queue.length > 0 && event.currentTarget[CARRY]?.save(queue)) queue = [];
64
+ }
65
+
66
+ function call(method, args) {
67
+ if (typeof window === 'undefined') return;
68
+ if (page !== window) {
69
+ page = window;
70
+ queue = [];
71
+ bound = null;
72
+ client = undefined;
73
+ window.addEventListener('pagehide', carry);
74
+ }
75
+ const pending = window[INSTANCE];
76
+ if (pending) bind(pending);
77
+ else {
78
+ bound = null;
79
+ client = undefined;
80
+ // On the window bootstrap will look at when it runs.
81
+ window[DRAIN] = bind;
82
+ }
83
+ if (client) return invoke(client, [method, args]);
84
+ if (queue.length >= MAX_QUEUED) return;
85
+ queue.push([
86
+ method,
87
+ args,
88
+ { messageId: uuid(), timestamp: new Date().toISOString() },
89
+ ]);
90
+ }
91
+
92
+ /**
93
+ * The `decision_id` HP logged for assigning `arm` to `slot` in this visitor's session, now: read
94
+ * from the identity cookies bootstrap was given, so it answers once bootstrap has been called,
95
+ * started or not. Null before that, and without a session or visitor id.
96
+ */
97
+ function assignmentDecisionId(assignment) {
98
+ try {
99
+ return (
100
+ (typeof window !== 'undefined' && window[ASSIGNMENT]?.(assignment)) ||
101
+ null
102
+ );
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * This visit as the recommendations API's session context (`viewed`, `basket`, `q`, `impressed`),
110
+ * now: from the started client, or, before it has started, as an earlier page of the visit kept it.
111
+ * `{}` without personalisation consent: a request that appends it then carries no visit.
112
+ */
113
+ function recommendationContext() {
114
+ if (typeof window === 'undefined') return {};
115
+ try {
116
+ const reader = window[RECOMMENDATION_CONTEXT];
117
+ const context = reader ? reader() : keptVisit();
118
+ return context && typeof context === 'object' ? context : {};
119
+ } catch {
120
+ return {};
121
+ }
122
+ }
123
+
124
+ /** Every client method, callable now. `then` is not one, so `await hp` is not a call. */
125
+ export const hp = new Proxy(
126
+ {},
127
+ {
128
+ get(_, method) {
129
+ if (typeof method !== 'string' || method === 'then') return undefined;
130
+ if (method === 'assignmentDecisionId') return assignmentDecisionId;
131
+ if (method === 'recommendationContext') return recommendationContext;
132
+ return (...args) => call(method, args);
133
+ },
134
+ },
135
+ );