@nommos/core 0.0.40 → 0.0.43

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.
@@ -0,0 +1,397 @@
1
+ import { POPUP_AUTO_OPEN_KEY, POPUP_INTERRUPTIVE_PRIORITIES, POPUP_PUSH_OPEN_COOLDOWN_MS, resolveEndpoints, POPUP_INBOX_CACHE_KEY, POPUP_LAUNCHER_HIDDEN_KEY, POPUP_MAX_ITEMS, POPUP_TOPIC_PREFIX, } from './constants';
2
+ import { ANALYTICS_ACTION_STATE } from './enums';
3
+ import { PopupViewer } from './popup-viewer';
4
+ const EVENT_STATE = {
5
+ // Not engagement at all — a push landed while the viewer was closed.
6
+ arrival_nudge: ANALYTICS_ACTION_STATE.INIT,
7
+ // Span opens, and everything that happens while it is open.
8
+ auto_open: ANALYTICS_ACTION_STATE.SELECT,
9
+ manual_open: ANALYTICS_ACTION_STATE.SELECT,
10
+ viewed: ANALYTICS_ACTION_STATE.SELECT,
11
+ carousel_next: ANALYTICS_ACTION_STATE.SELECT,
12
+ carousel_prev: ANALYTICS_ACTION_STATE.SELECT,
13
+ link_click: ANALYTICS_ACTION_STATE.SELECT,
14
+ // The conversion. SUBMIT leaves the span open, which is correct — a CTA opens in a new tab.
15
+ cta_click: ANALYTICS_ACTION_STATE.SUBMIT,
16
+ // Span closes. `dismissed` is terminal, `abandoned` means they left with it still open.
17
+ closed: ANALYTICS_ACTION_STATE.UNSELECT,
18
+ dismissed: ANALYTICS_ACTION_STATE.UNSELECT,
19
+ abandoned: ANALYTICS_ACTION_STATE.UNSELECT,
20
+ render_failed: ANALYTICS_ACTION_STATE.FAILURE,
21
+ };
22
+ /**
23
+ * The visitor-facing half of CRM web popups: fetches the inbox, decides what to show, and reports
24
+ * what happened.
25
+ *
26
+ * <p>Implements {@link ConsentAwareModule} with `id: 'marketing'`, so the consent manager starts
27
+ * and stops it exactly as it does ad tracking. Nothing here runs — no fetch, no subscription, no
28
+ * DOM — until marketing consent is granted.
29
+ */
30
+ export class PopupInbox {
31
+ constructor(deps) {
32
+ this.deps = deps;
33
+ this.id = 'marketing';
34
+ this.supportsRuntimeShutdown = true;
35
+ this.initialized = false;
36
+ this.items = [];
37
+ this.etag = null;
38
+ /** In-memory only: popup bodies are large and stale ones must not outlive the page. */
39
+ this.contentCache = new Map();
40
+ this.topic = null;
41
+ /** Ids the visitor has already been shown a launcher for, so an arrival can be told from a re-read. */
42
+ this.knownIds = new Set();
43
+ /** False until the first inbox read of this page load has landed — see {@link refresh}. */
44
+ this.settled = false;
45
+ /** Epoch ms of the last push that opened itself; 0 means none this page. */
46
+ this.lastPushOpenAt = 0;
47
+ this.popupEndpoint = resolveEndpoints(deps.cfg.mode, deps.cfg.baseUrl).popup;
48
+ this.viewer = new PopupViewer({
49
+ loadContent: (id) => this.content(id),
50
+ onOpened: (item, trigger) => this.handleOpened(item, trigger),
51
+ onClosed: (item) => this.emit('closed', this.meta(item)),
52
+ onAbandoned: (item) => this.emit('abandoned', this.meta(item)),
53
+ onDismissed: (item) => void this.dismiss(item),
54
+ onCtaClick: (item, buttonIndex, url) => this.handleCtaClick(item, buttonIndex, url),
55
+ onLinkClick: (item, url) => this.emit('link_click', { ...this.meta(item), url }),
56
+ onCarouselMove: (item, direction) => this.emit(direction === 'next' ? 'carousel_next' : 'carousel_prev', this.meta(item)),
57
+ onRenderFailed: (item, reason) => this.emit('render_failed', { ...this.meta(item), reason }),
58
+ });
59
+ }
60
+ // ── ConsentAwareModule ─────────────────────────────────────────────────────
61
+ async initialize() {
62
+ if (this.initialized || typeof window === 'undefined')
63
+ return;
64
+ if (!this.deps.hasMarketingConsent())
65
+ return;
66
+ this.initialized = true;
67
+ this.restoreCache();
68
+ this.listen();
69
+ await this.refresh();
70
+ await this.autoOpen();
71
+ }
72
+ /** Revocation must leave nothing behind: no viewer, no cache, no subscription. */
73
+ async shutdown() {
74
+ if (!this.initialized)
75
+ return;
76
+ this.initialized = false;
77
+ if (this.topic) {
78
+ this.deps.unsubscribeTopic(this.topic);
79
+ this.topic = null;
80
+ }
81
+ this.viewer.teardown();
82
+ await this.purge();
83
+ }
84
+ async purge() {
85
+ this.items = [];
86
+ this.etag = null;
87
+ this.knownIds.clear();
88
+ this.settled = false;
89
+ this.lastPushOpenAt = 0;
90
+ this.contentCache.clear();
91
+ try {
92
+ sessionStorage.removeItem(POPUP_INBOX_CACHE_KEY);
93
+ sessionStorage.removeItem(POPUP_AUTO_OPEN_KEY);
94
+ sessionStorage.removeItem(POPUP_LAUNCHER_HIDDEN_KEY);
95
+ }
96
+ catch {
97
+ /* storage unavailable — nothing cached to clear */
98
+ }
99
+ }
100
+ isInitialized() {
101
+ return this.initialized;
102
+ }
103
+ // ── Public surface ─────────────────────────────────────────────────────────
104
+ getInbox() {
105
+ return {
106
+ items: [...this.items],
107
+ unreadCount: this.items.filter((item) => item.status !== 'VIEWED').length,
108
+ };
109
+ }
110
+ async openViewer(id) {
111
+ if (!this.initialized)
112
+ return;
113
+ await this.viewer.open(id, 'manual');
114
+ }
115
+ // ── Inbox ──────────────────────────────────────────────────────────────────
116
+ /**
117
+ * Revalidates against the server with `If-None-Match`. A 304 keeps whatever is already cached,
118
+ * which is what stops a long-lived tab from sitting on a popup that was dismissed elsewhere
119
+ * while still avoiding a full payload on every SPA route change.
120
+ */
121
+ async refresh() {
122
+ if (!this.initialized)
123
+ return;
124
+ try {
125
+ const response = await fetch(`${this.popupEndpoint}/inbox`, { headers: this.headers() });
126
+ if (response.status === 304)
127
+ return;
128
+ if (!response.ok) {
129
+ this.warn(`inbox fetch failed: ${response.status}`);
130
+ return;
131
+ }
132
+ this.etag = response.headers.get('ETag');
133
+ const payload = await response.json();
134
+ this.items = Array.isArray(payload?.popups) ? payload.popups.slice(0, POPUP_MAX_ITEMS) : [];
135
+ this.persistCache();
136
+ this.viewer.setItems(this.items);
137
+ this.nudgeIfArrived();
138
+ // Prefetch only the one that is about to open. Fetching all three would put the whole
139
+ // payload back on the critical path, which is the reason /inbox is metadata-only.
140
+ if (this.items.length > 0)
141
+ void this.content(this.items[0].id);
142
+ }
143
+ catch (error) {
144
+ this.warn(`inbox fetch failed: ${String(error)}`);
145
+ }
146
+ }
147
+ /**
148
+ * Reacts to popups that landed mid-session — by opening one if it is urgent enough, otherwise
149
+ * by shaking the launcher and chiming.
150
+ *
151
+ * <p>Deliberately silent on the first read of a page load and on the cache restore: those are
152
+ * the same inbox the visitor already has, and buzzing on every page view — or every SPA route
153
+ * change — would train them to ignore it. It also stays quiet while the viewer is open, since
154
+ * the popup on screen is already the notification. In practice that leaves the WebSocket push,
155
+ * which is exactly the case this exists for.
156
+ *
157
+ * <p><b>Whether an arrival takes over the screen is the author's call, not ours.</b> Seizing the
158
+ * screen of someone mid-task on the tenant's own site is expensive, so only HIGH and CRITICAL do
159
+ * it; everything else waits behind the launcher. The per-page-visit cap that governs
160
+ * {@link autoOpen} does not apply here — a push is a new event, not a replay of an inbox the
161
+ * visitor already had — so {@link POPUP_PUSH_OPEN_COOLDOWN_MS} is what stops a burst of sends
162
+ * from repeatedly taking over.
163
+ */
164
+ nudgeIfArrived() {
165
+ const incoming = new Set(this.items.map((item) => item.id));
166
+ const arrived = this.items.filter((item) => !this.knownIds.has(item.id));
167
+ const firstRead = !this.settled;
168
+ this.knownIds = incoming;
169
+ this.settled = true;
170
+ if (firstRead || arrived.length === 0 || this.viewer.isVisible())
171
+ return;
172
+ const urgent = arrived.find((item) => POPUP_INTERRUPTIVE_PRIORITIES.includes((item.priority ?? '').toUpperCase()));
173
+ if (urgent && this.pushOpenAllowed()) {
174
+ this.lastPushOpenAt = Date.now();
175
+ // No arrival_nudge here: the popup opening in front of the visitor *is* the notification,
176
+ // and reporting both would double-count one arrival. The open reports itself as `auto_open`.
177
+ void this.viewer.open(urgent.id, 'auto');
178
+ return;
179
+ }
180
+ // Carry the top-ranked arrival's identity, not just a bare marker: a nudge you cannot join to
181
+ // a popup answers "how often do we buzz" but not "did buzzing get it opened", which is the
182
+ // only reason to measure it.
183
+ this.emit('arrival_nudge', { ...this.meta(arrived[0]), arrived_count: arrived.length });
184
+ this.viewer.notifyArrival();
185
+ }
186
+ pushOpenAllowed() {
187
+ return Date.now() - this.lastPushOpenAt >= POPUP_PUSH_OPEN_COOLDOWN_MS;
188
+ }
189
+ async content(id) {
190
+ const cached = this.contentCache.get(id);
191
+ if (cached)
192
+ return cached;
193
+ try {
194
+ const response = await fetch(`${this.popupEndpoint}/${id}/content`, { headers: this.headers() });
195
+ if (response.status === 404) {
196
+ // Listed in the inbox but no longer showable — the inbox we are working from is stale.
197
+ this.dropItem(id);
198
+ return null;
199
+ }
200
+ if (!response.ok)
201
+ return null;
202
+ const content = (await response.json());
203
+ this.contentCache.set(id, content);
204
+ return content;
205
+ }
206
+ catch (error) {
207
+ this.warn(`content fetch failed: ${String(error)}`);
208
+ return null;
209
+ }
210
+ }
211
+ /** Exactly one popup opens by itself per page visit; the rest wait behind the launcher. */
212
+ async autoOpen() {
213
+ if (this.items.length === 0 || this.alreadyAutoOpened())
214
+ return;
215
+ this.markAutoOpened();
216
+ await this.viewer.open(this.items[0].id, 'auto');
217
+ }
218
+ // ── Lifecycle reporting ────────────────────────────────────────────────────
219
+ handleOpened(item, trigger) {
220
+ this.emit(trigger === 'auto' ? 'auto_open' : 'manual_open', this.meta(item));
221
+ if (item.status === 'VIEWED')
222
+ return;
223
+ this.emit('viewed', this.meta(item));
224
+ // Optimistic: the badge should drop the moment it is opened, not a round trip later.
225
+ const previous = item.status;
226
+ item.status = 'VIEWED';
227
+ this.persistCache();
228
+ this.viewer.setItems(this.items);
229
+ // ...but optimism has to be revocable, and the two ways it can fail need opposite answers.
230
+ // A 404 means the popup is gone for this visitor, so restoring "unread" would strand a phantom
231
+ // nothing can ever clear. Anything else is transient, so put the status back and let the next
232
+ // open retry it.
233
+ void this.post(`${item.id}/view`).then((status) => {
234
+ if (status === 404) {
235
+ this.dropItem(item.id);
236
+ return;
237
+ }
238
+ if (status >= 200 && status < 300)
239
+ return;
240
+ if (item.status !== 'VIEWED')
241
+ return;
242
+ item.status = previous;
243
+ this.persistCache();
244
+ this.viewer.setItems(this.items);
245
+ });
246
+ }
247
+ handleCtaClick(item, buttonIndex, url) {
248
+ this.emit('cta_click', { ...this.meta(item), button_index: buttonIndex, url });
249
+ void this.post(`${item.id}/cta-click`, { buttonIndex });
250
+ }
251
+ async dismiss(item) {
252
+ this.emit('dismissed', this.meta(item));
253
+ await this.post(`${item.id}/dismiss`);
254
+ this.items = this.items.filter((candidate) => candidate.id !== item.id);
255
+ this.contentCache.delete(item.id);
256
+ this.persistCache();
257
+ this.viewer.setItems(this.items);
258
+ if (this.items.length === 0)
259
+ this.viewer.teardown();
260
+ }
261
+ /**
262
+ * @returns whether the server accepted the transition.
263
+ *
264
+ * <p>The status is checked, not just the absence of a throw. `fetch` rejects only on a network
265
+ * failure — a 401 or a 404 resolves normally — so a bare try/catch here reported every rejected
266
+ * lifecycle call as a success. Since these calls are made optimistically, that turned a server
267
+ * refusal into a local state that quietly disagreed with the server until the next reload put it
268
+ * back: the visitor marks everything read, reloads, and the unread badge returns.
269
+ */
270
+ async post(path, body) {
271
+ try {
272
+ const response = await fetch(`${this.popupEndpoint}/${path}`, {
273
+ method: 'POST',
274
+ headers: { ...this.headers(), 'Content-Type': 'application/json' },
275
+ body: JSON.stringify(body ?? {}),
276
+ // A CTA that navigates in the current tab unloads the page, and the browser cancels
277
+ // in-flight requests when it does — losing exactly the click being recorded. `keepalive`
278
+ // lets the request outlive the document.
279
+ keepalive: true,
280
+ });
281
+ if (!response.ok) {
282
+ this.warn(`lifecycle call ${path} rejected: ${response.status}`);
283
+ }
284
+ return response.status;
285
+ }
286
+ catch (error) {
287
+ this.warn(`lifecycle call ${path} failed: ${String(error)}`);
288
+ return 0; // network failure, not a verdict from the server
289
+ }
290
+ }
291
+ /**
292
+ * Forgets a popup the server will no longer serve.
293
+ *
294
+ * <p>A 404 from a lifecycle or content call is a verdict, not a hiccup: the entry is gone,
295
+ * expired, or dismissed elsewhere, and every future call for it will fail the same way. Holding
296
+ * on to it strands a phantom in the inbox that can never be read — it cannot be marked viewed,
297
+ * because the call that would mark it is the one returning 404 — so the unread badge survives
298
+ * every reload with no way for the visitor to clear it.
299
+ */
300
+ dropItem(id) {
301
+ if (!this.items.some((item) => item.id === id))
302
+ return;
303
+ this.warn(`dropping popup ${id}: the server no longer serves it`);
304
+ this.items = this.items.filter((item) => item.id !== id);
305
+ this.knownIds.delete(id);
306
+ this.contentCache.delete(id);
307
+ this.persistCache();
308
+ this.viewer.setItems(this.items);
309
+ }
310
+ // ── Push ───────────────────────────────────────────────────────────────────
311
+ listen() {
312
+ if (!this.deps.cfg.brand_id || !this.deps.browserId)
313
+ return;
314
+ this.topic = `${POPUP_TOPIC_PREFIX}.${this.deps.cfg.brand_id}.${this.deps.browserId}`;
315
+ this.deps.subscribeTopic(this.topic, () => {
316
+ // The push is a nudge, not a payload: it says "your inbox changed", and the fetch that
317
+ // follows re-applies the server's ordering and cap rather than trusting the message.
318
+ void this.refresh();
319
+ });
320
+ }
321
+ // ── Plumbing ───────────────────────────────────────────────────────────────
322
+ headers() {
323
+ const headers = {};
324
+ if (this.deps.cfg.wsToken)
325
+ headers['Authorization'] = `Bearer ${this.deps.cfg.wsToken}`;
326
+ if (this.deps.cfg.consentRef)
327
+ headers['x-nommos-consent-ref'] = this.deps.cfg.consentRef;
328
+ if (this.deps.cfg.consentReceiptId)
329
+ headers['x-nommos-consent-receipt-id'] = this.deps.cfg.consentReceiptId;
330
+ if (this.etag)
331
+ headers['If-None-Match'] = this.etag;
332
+ return headers;
333
+ }
334
+ /** Single exit for analytics, so no call site can forget the state that goes with its event. */
335
+ emit(event, data) {
336
+ this.deps.emit(event, data, EVENT_STATE[event]);
337
+ }
338
+ meta(item) {
339
+ return {
340
+ notification_type: 'WEB_POPUP',
341
+ popup_id: item.id,
342
+ template_id: item.templateId,
343
+ // Attribution: without these the dashboard can say a popup was opened but not which
344
+ // broadcast or journey sent it.
345
+ campaign_id: item.sourceCampaignId,
346
+ popup_source: item.source,
347
+ priority: item.priority,
348
+ };
349
+ }
350
+ restoreCache() {
351
+ try {
352
+ const raw = sessionStorage.getItem(POPUP_INBOX_CACHE_KEY);
353
+ if (!raw)
354
+ return;
355
+ const cached = JSON.parse(raw);
356
+ this.items = Array.isArray(cached.items) ? cached.items : [];
357
+ this.etag = cached.etag ?? null;
358
+ this.knownIds = new Set(this.items.map((item) => item.id));
359
+ if (this.items.length > 0)
360
+ this.viewer.setItems(this.items);
361
+ }
362
+ catch {
363
+ /* unreadable cache — a fresh fetch replaces it */
364
+ }
365
+ }
366
+ persistCache() {
367
+ try {
368
+ const payload = { etag: this.etag, items: this.items };
369
+ sessionStorage.setItem(POPUP_INBOX_CACHE_KEY, JSON.stringify(payload));
370
+ }
371
+ catch {
372
+ /* storage full or blocked — the cache is an optimisation, not a requirement */
373
+ }
374
+ }
375
+ alreadyAutoOpened() {
376
+ try {
377
+ return sessionStorage.getItem(POPUP_AUTO_OPEN_KEY) === '1';
378
+ }
379
+ catch {
380
+ return false;
381
+ }
382
+ }
383
+ markAutoOpened() {
384
+ try {
385
+ sessionStorage.setItem(POPUP_AUTO_OPEN_KEY, '1');
386
+ }
387
+ catch {
388
+ /* storage unavailable — worst case the popup auto-opens again next navigation */
389
+ }
390
+ }
391
+ warn(message) {
392
+ if (this.deps.cfg.mode === 'dev') {
393
+ console.warn('[Nommos Tracker] popup', message);
394
+ }
395
+ }
396
+ }
397
+ //# sourceMappingURL=popup-inbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popup-inbox.js","sourceRoot":"","sources":["../../../../../nommos/core/src/lib/popup-inbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,180 @@
1
+ import { WebPopupContent, WebPopupItem } from './types';
2
+ export interface PopupViewerCallbacks {
3
+ /** Content for a popup, fetched lazily. Resolves null when it can no longer be shown. */
4
+ loadContent: (id: number) => Promise<WebPopupContent | null>;
5
+ onOpened: (item: WebPopupItem, trigger: 'auto' | 'manual') => void;
6
+ /** The viewer was closed. Not a dismissal — the popup stays in the inbox. */
7
+ onClosed: (item: WebPopupItem) => void;
8
+ /**
9
+ * The visitor left the page with the popup still open. Distinct from `onClosed` — "walked away"
10
+ * and "deliberately shut it" are different engagement signals — and it is what stops an
11
+ * engagement span from running forever when the tab simply goes away.
12
+ */
13
+ onAbandoned: (item: WebPopupItem) => void;
14
+ onDismissed: (item: WebPopupItem) => void;
15
+ onCtaClick: (item: WebPopupItem, buttonIndex: number, url: string) => void;
16
+ onLinkClick: (item: WebPopupItem, url: string) => void;
17
+ onCarouselMove: (item: WebPopupItem, direction: 'next' | 'prev') => void;
18
+ onRenderFailed: (item: WebPopupItem, reason: string) => void;
19
+ }
20
+ export declare class PopupViewer {
21
+ private readonly callbacks;
22
+ private host;
23
+ private root;
24
+ private state;
25
+ private launcherHost;
26
+ private launcherRoot;
27
+ /** The element the launcher is currently mounted into. `null` means the floating fallback. */
28
+ private anchor;
29
+ private domObserver;
30
+ private remountQueued;
31
+ /** Resolved once per viewer: the visitor's language does not change mid-page. */
32
+ private readonly copy;
33
+ private items;
34
+ /** What is on screen. Only moves once the next popup's content is in hand. */
35
+ private index;
36
+ /**
37
+ * Where the visitor has navigated to, which runs ahead of {@link index} while a load is in
38
+ * flight. Without it, two quick taps on `›` both step from the same rendered index and the
39
+ * second is swallowed.
40
+ */
41
+ private pendingIndex;
42
+ /** Guards against a slow content load landing after a later move and rewinding the carousel. */
43
+ private swapToken;
44
+ private lastFocused;
45
+ private touchStartX;
46
+ private audio;
47
+ private readonly onKeyDown;
48
+ private readonly onViewportChange;
49
+ private readonly onPageHide;
50
+ constructor(callbacks: PopupViewerCallbacks);
51
+ setItems(items: WebPopupItem[]): void;
52
+ /** Removes every trace of the viewer. Called on consent revocation and on an empty inbox. */
53
+ teardown(): void;
54
+ isVisible(): boolean;
55
+ open(id: number | undefined, trigger: 'auto' | 'manual'): Promise<void>;
56
+ close(): void;
57
+ private ensureHost;
58
+ private handlePageHide;
59
+ /**
60
+ * The launcher gets a host of its own rather than sharing the modal's.
61
+ *
62
+ * <p>It has to, because the two have opposite placement needs. The modal is `position: fixed` and
63
+ * must resolve against the viewport, but `fixed` resolves against the nearest ancestor carrying a
64
+ * `transform`, `filter` or `contain` — which sticky headers routinely do. Mounting one shared host
65
+ * inside a tenant's header would therefore drag the modal in with it and clip it to the header.
66
+ */
67
+ private ensureLauncherHost;
68
+ /**
69
+ * @returns the integrator's chosen mount point, or null for the floating fallback.
70
+ */
71
+ private resolveAnchor;
72
+ /** Places the launcher host under the current anchor, or under `body` when there is none. */
73
+ private mountLauncher;
74
+ /**
75
+ * Follows the anchor for the life of the page. A one-shot query at init would almost always miss
76
+ * it: the SDK boots from a script tag, while an Angular or React tenant renders its header
77
+ * afterwards. This also covers the anchor being removed or swapped on an SPA route change.
78
+ *
79
+ * <p>The callback fires on every DOM change the tenant's app makes, so it must stay cheap — it
80
+ * only re-resolves and compares, and a burst of mutations collapses into one remount per frame.
81
+ */
82
+ private observeDom;
83
+ /**
84
+ * Whether the launcher shows at all depends on where it lives — the two placements have opposite
85
+ * defaults, and that is deliberate.
86
+ *
87
+ * <p><b>Floating</b> shows only while something is unread. A permanent button parked over
88
+ * someone else's page with nothing behind it is clutter, so once the visitor has seen everything
89
+ * it disappears. The cost is real: a read-but-undismissed popup then has no way back for the rest
90
+ * of the session, short of the host app calling {@link PopupInbox.openViewer}.
91
+ *
92
+ * <p><b>Anchored</b> always shows. The tenant deliberately reserved a slot in their own header,
93
+ * so vacating it leaves a hole their layout collapses around — a flex or grid {@code gap} still
94
+ * applies to a zero-width item — and a header bell that vanishes reads as broken chrome rather
95
+ * than as tidiness. Persisting also restores the way back, so a popup closed by accident can be
96
+ * reopened.
97
+ */
98
+ private renderLauncher;
99
+ /**
100
+ * Hiding is per page session and deliberately weaker than dismissing: it silences the launcher
101
+ * for a visitor who does not want it in the corner right now, but a genuinely new popup clears
102
+ * it again (see {@link notifyArrival}) because that is a new reason to be shown.
103
+ */
104
+ private launcherHidden;
105
+ private setLauncherHidden;
106
+ /**
107
+ * A popup arrived while the visitor was already on the page. Without this the only signal is a
108
+ * small badge appearing in a corner nobody is looking at, so shake the launcher and play a short
109
+ * chime. Callers suppress it for first load, cache restore and the auto-open path — see
110
+ * `PopupInbox.refresh`.
111
+ */
112
+ notifyArrival(): void;
113
+ /**
114
+ * Synthesised rather than loaded from a file: a popup renders on the *tenant's* origin, so any
115
+ * asset URL is one more thing that can 404 or trip CORS on someone else's domain.
116
+ *
117
+ * <p>Autoplay policy blocks audio until the page has had a user gesture. If the context is
118
+ * suspended we skip silently instead of calling `resume()` — the shake already carries the
119
+ * signal, and a warning in the tenant's console would be noise they cannot act on.
120
+ */
121
+ private chime;
122
+ static soundEnabled(): boolean;
123
+ static setSoundEnabled(enabled: boolean): void;
124
+ /** @returns false when there is nothing renderable, so the caller can abort the open. */
125
+ private renderCurrent;
126
+ private renderBodyIfVisible;
127
+ /**
128
+ * Picks the variant by viewport width, falling back to whichever one exists — a template with
129
+ * only a desktop variant should still show on a phone rather than showing nothing.
130
+ */
131
+ private bodyFor;
132
+ private applyVariantCss;
133
+ private shell;
134
+ private ctaMarkup;
135
+ private dotsMarkup;
136
+ private isMobileViewport;
137
+ /**
138
+ * One delegated listener covers the chrome and the authored body alike, which is what lets an
139
+ * ordinary `<a href>` inside a template be attributed without any script in the popup.
140
+ */
141
+ private handleClick;
142
+ /**
143
+ * Closes the viewer after a link has been followed, without dismissing.
144
+ *
145
+ * <p>Closing rather than dismissing is deliberate and matches the backend, where
146
+ * {@code recordCtaClick} only marks the popup viewed: a visitor who clicks through has engaged,
147
+ * not refused, and can reopen the message from the launcher.
148
+ *
149
+ * <p>{@link close} hides the overlay rather than detaching it, which is what makes this safe to
150
+ * call from inside the click. Removing the anchor from the document mid-dispatch can cancel the
151
+ * navigation it was about to perform; hiding an ancestor cannot.
152
+ */
153
+ private closeAfterFollowing;
154
+ /**
155
+ * Moves the carousel by one, keeping the current popup on screen until the next one is ready.
156
+ *
157
+ * <p>This used to re-render the whole modal through {@link renderCurrent}, which tore the shell
158
+ * down to a loading placeholder before awaiting the content. Content is cached after its first
159
+ * fetch, so the await almost always resolved on the next microtask — but "almost always" still
160
+ * paints one frame of collapsed spinner, which is what read as a flicker. Loading first and
161
+ * swapping second means the visitor never sees an intermediate state.
162
+ */
163
+ private move;
164
+ /**
165
+ * Cross-slides the authored body, leaving the chrome untouched.
166
+ *
167
+ * <p>Only `.np-stage`'s children change, so the underbar, the chips and the dialog itself never
168
+ * re-render — the frame stays put while the content moves through it.
169
+ */
170
+ private swapBody;
171
+ /** Repoints the dots without rebuilding them, so the underbar never repaints mid-swipe. */
172
+ private syncDots;
173
+ private prefersReducedMotion;
174
+ private handleKeyDown;
175
+ /** Keeps keyboard focus inside the dialog while it is open. */
176
+ private trapFocus;
177
+ private focusDialog;
178
+ private handleTouchStart;
179
+ private handleTouchEnd;
180
+ }