@phoenix-iq/notifications 1.1.0 → 1.2.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/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # @phoenix-iq/notifications (web SDK)
2
2
 
3
- Browser push for apps on `notifications.phnx-iq.com`, our replacement for OneSignal's web SDK. It subscribes the browser
4
- (standard Web Push with the app's VAPID key), binds it to your signed-in user with an identity token your backend signs,
5
- hands notification clicks to your app, and shows the subscription bell and banner when the app has them on. TypeScript, no
6
- dependencies.
3
+ Browser push for apps on `notifications.phnx-iq.com`. It subscribes the browser (standard Web Push with the app's VAPID key),
4
+ binds it to your signed-in user with an identity token your backend signs, hands notification clicks to your app, and draws
5
+ the subscribe prompt HQ chose for the app: a bell, the app's logo or a pop-up. TypeScript, no dependencies.
7
6
 
8
7
  How the whole service works, the server API and the identity tokens: [docs/notifications.md](../../docs/notifications.md).
9
8
 
@@ -25,7 +24,7 @@ import { Notifications } from '@phoenix-iq/notifications';
25
24
 
26
25
  await Notifications.init({ appId: '<app id from HQ: Notification management>' });
27
26
 
28
- // from a click ("Get notifications"): browsers only show the prompt on a user gesture
27
+ // from a click on your own button ("Get notifications"): browsers only ask on a user gesture
29
28
  const permission = await Notifications.requestPermission(); // 'granted' | 'denied' | 'default' | 'unsupported'
30
29
 
31
30
  // after your own sign-in, with the identity token your backend returned
@@ -39,27 +38,26 @@ await Notifications.logout();
39
38
  permission is granted is kept and applied once it is. Nothing throws on an unsupported browser; check
40
39
  `Notifications.isSupported()` and `Notifications.permission()` to decide what to show.
41
40
 
42
- ## The subscription bell
41
+ ## The subscribe prompt
43
42
 
44
- Switch it on in HQ (the app → Web push → Subscription bell) and `init` draws it: a round bell in the bottom right or left
45
- corner, in the colour you chose. The first click asks the browser; then the bell shows that this browser is subscribed and
46
- offers "Unsubscribe", or, when notifications were blocked, says how to allow them again. It speaks the page's language
47
- (`<html lang="ar">` → Arabic, right to left; else English, or the `language` option), takes the page's font, and picks a
48
- light or dark panel from the page's background. It sits in a shadow root, so your CSS and its CSS stay apart, and it is
49
- hidden when printing. A change in HQ shows on the next page load. With *Hide the bell once subscribed*, only browsers that
50
- are not subscribed see it.
43
+ HQ chooses one per app (the app → Web push → How visitors subscribe) and `init` draws it; a change shows on the next page load.
51
44
 
52
- ## The subscription banner
45
+ - **Bell** or **logo**: a round button in the bottom right or left corner, in the colour HQ set, showing a bell, or the app's
46
+ icon with a small bell on it (a logo that fails to load gives way to the bell). The first click asks the browser; then the
47
+ button shows that this browser is subscribed and offers "Unsubscribe", or, when notifications were blocked, says how to
48
+ allow them again. With *Hide after subscribing*, only browsers that are not subscribed see it.
49
+ - **Pop-up**: shown, when its time comes, to a browser never asked: a card at the top or bottom with a message, "Subscribe"
50
+ (the browser's own question) and "Later". HQ sets the delay after the page opens, the page view it starts from and how many
51
+ days "Later" keeps it away; the SDK counts these in the browser (IndexedDB). Texts left empty are the SDK's own in the
52
+ page's language. On a phone a top pop-up comes from the bottom, clear of your header; the strip around the card lets
53
+ clicks through.
54
+ - **None**: nothing is drawn. Ask from your own button with `requestPermission()`, and use `unsubscribe()` and the state
55
+ below.
53
56
 
54
- OneSignal's slide prompt. Switch it on in HQ (the app → Web push → Subscription banner) and `init` shows it, when its time
55
- comes, to a browser never asked: a card at the top or bottom with a message, "Subscribe" (the browser's own question) and
56
- "Later". HQ sets the delay after the page opens, the page view it starts from and how many days "Later" keeps it away; the
57
- SDK counts these in the browser (IndexedDB). Texts left empty are the SDK's own in the page's language. On a phone a top
58
- banner comes from the bottom, clear of your header, and a bottom one sits above the bell. Like the bell it lives in a shadow
59
- root; the strip around the card lets clicks through.
60
-
61
- A site with its own "Get notifications" button passes `bell: false` and `banner: false`, and uses `requestPermission()`,
62
- `unsubscribe()` and the state below.
57
+ They speak the page's language (`<html lang="ar">` → Arabic, right to left; else English, or the `language` option), take
58
+ the page's font, pick a light or dark look from the page's background, live in a shadow root (your CSS and theirs stay apart)
59
+ and are hidden when printing. `bell: false` and `banner: false` in `init` keep the button or the pop-up off a page even when
60
+ HQ chose it.
63
61
 
64
62
  ## Subscription state
65
63
 
@@ -92,17 +90,20 @@ With no tab open, the browser opens the link directly.
92
90
  | `serverUrl` | `https://notifications.phnx-iq.com` | `''` uses your own origin (development behind a proxy) |
93
91
  | `serviceWorkerPath` | `/phoenix-notifications-sw.js` | |
94
92
  | `serviceWorkerScope` | `/` | |
95
- | `language` | the page's (`<html lang>`), then the browser's | Picks the Arabic or English text of a notification, and the bell's language |
93
+ | `language` | the page's (`<html lang>`), then the browser's | Picks the Arabic or English text of a notification, and the prompt's language |
96
94
  | `appVersion` | none | Stored with the device; in-app messages can target versions below one |
97
- | `bell` | as HQ set it | `false` never draws the subscription bell (a site with its own button) |
98
- | `banner` | as HQ set it | `false` never shows the subscription banner |
95
+ | `bell` | as HQ chose | `false` never draws the floating button (bell or logo) |
96
+ | `banner` | as HQ chose | `false` never shows the pop-up |
99
97
 
100
98
  ## Notes
101
99
 
102
100
  - iPhone and iPad: web push works only for a site added to the home screen (iOS 16.4+), as with any web push provider.
103
101
  - The device secret the server gives the browser is kept in IndexedDB; it only lets this browser manage its own subscription.
104
102
  - Your site must be in the app's sites (the app → Web push), or registration is refused.
105
- - `example/bell.html` draws the bell and the banner in every state without a server (serve `sdk/web`, open `/example/bell.html`).
103
+ - `example/bell.html` draws the button (bell or logo) and the pop-up in every state without a server (serve `sdk/web`, open
104
+ `/example/bell.html`).
105
+ - Up to 1.1.0 the SDK read a switch on the bell and one on the banner; from 1.2.0 it follows the one prompt HQ chose and can
106
+ draw the logo. An older SDK still works with a newer server: it draws a bell where HQ chose the logo.
106
107
 
107
108
  ## Publishing
108
109
 
package/dist/banner.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import type { SubscriptionState } from './index.js';
2
2
  /**
3
- * The subscription banner (OneSignal's slide prompt): a card that slides in at the top or the bottom of the page with a short
4
- * message, "Subscribe" and "Later". "Subscribe" opens the browser's own question; "Later" keeps it away for some days. The SDK
3
+ * The pop-up of the banner prompt: a card that slides in at the top or the bottom of the page with a short message,
4
+ * "Subscribe" and "Later". "Subscribe" opens the browser's own question; "Later" keeps it away for some days. The SDK
5
5
  * decides when it shows (a browser never asked, from a page view, after a delay); this only draws it and answers its buttons.
6
6
  *
7
- * Like the bell it lives in a shadow root, takes the page's font and language and a light or dark look to suit the page.
7
+ * Like the floating button it lives in a shadow root, takes the page's font and language and a light or dark look to suit the page.
8
8
  * Texts HQ wrote are shown as they are; the empty ones are the SDK's own, in the page's language.
9
9
  */
10
10
  export type BannerPosition = 'top' | 'bottom';
@@ -31,11 +31,5 @@ export interface BannerApi {
31
31
  export interface BannerHandle {
32
32
  destroy(): void;
33
33
  }
34
- export interface BannerPlacement {
35
- /** The app's icon, shown beside the message (else a bell in the banner's colour). */
36
- iconUrl?: string | null;
37
- /** The bell is on the page: a bottom banner keeps clear of it on narrow screens. */
38
- aboveBell?: boolean;
39
- }
40
- /** Slides the banner in; `destroy()` takes it away at once. */
41
- export declare function mountBanner(api: BannerApi, settings: BannerSettings, language: string, placement?: BannerPlacement): BannerHandle;
34
+ /** Slides the banner in, with the app's icon beside the message (else a bell in its colour); `destroy()` takes it away at once. */
35
+ export declare function mountBanner(api: BannerApi, settings: BannerSettings, language: string, iconUrl?: string | null): BannerHandle;
package/dist/banner.js CHANGED
@@ -41,11 +41,9 @@ button { font: inherit; margin: 0; -webkit-tap-highlight-color: transparent; }
41
41
  :host([data-position="top"]) .card { transform: translateY(-24px); }
42
42
  :host([data-position="bottom"]) .card { transform: translateY(24px); }
43
43
  @media (max-width: 600px) {
44
- /* on a phone the top of the page is the site's header and menu: the banner comes from the bottom, as OneSignal's does */
44
+ /* on a phone the top of the page is the site's header and menu: the banner comes from the bottom */
45
45
  :host([data-position="top"]) { top: auto; bottom: calc(16px + env(safe-area-inset-bottom, 0px)); }
46
46
  :host([data-position="top"]) .card { transform: translateY(24px); }
47
- /* and where it would cover the bell in its corner, it sits above it */
48
- :host([data-above-bell]) { bottom: calc(84px + env(safe-area-inset-bottom, 0px)); }
49
47
  }
50
48
  :host([data-shown]) .card { opacity: 1; transform: none; }
51
49
  .icon {
@@ -67,15 +65,12 @@ button { font: inherit; margin: 0; -webkit-tap-highlight-color: transparent; }
67
65
  .card { transition: opacity 0.2s ease; transform: none !important; }
68
66
  }
69
67
  `;
70
- /** Slides the banner in; `destroy()` takes it away at once. */
71
- export function mountBanner(api, settings, language, placement = {}) {
68
+ /** Slides the banner in, with the app's icon beside the message (else a bell in its colour); `destroy()` takes it away at once. */
69
+ export function mountBanner(api, settings, language, iconUrl) {
72
70
  const lang = uiLanguage(language);
73
71
  const t = TEXTS[lang];
74
- const { iconUrl, aboveBell } = placement;
75
72
  const host = document.createElement('phoenix-notifications-banner');
76
73
  host.dataset.position = settings.position === 'bottom' ? 'bottom' : 'top';
77
- if (aboveBell)
78
- host.dataset.aboveBell = '';
79
74
  applyColors(host, colorOr(settings.color));
80
75
  const shadow = host.attachShadow({ mode: 'open' });
81
76
  // only constants go into this markup; HQ's texts and the icon's address are set as text and a property below
package/dist/bell.d.ts CHANGED
@@ -1,20 +1,27 @@
1
1
  import type { SubscriptionState } from './index.js';
2
2
  /**
3
- * The subscription bell (OneSignal's "subscription bell"): a round button in a bottom corner of the site that subscribes this
4
- * browser on a click, shows that it is subscribed and lets the person unsubscribe, or explains how to allow notifications
5
- * again once they blocked them. HQ switches it on per app; the SDK draws it from the web config.
3
+ * The floating subscribe button of the bell and logo prompts: a round button in a bottom corner of the site that subscribes
4
+ * this browser on a click, shows that it is subscribed and lets the person unsubscribe, or explains how to allow notifications
5
+ * again once they blocked them. It shows a bell, or the app's logo with a small bell on it. HQ chooses the prompt per app;
6
+ * the SDK draws it from the web config.
6
7
  *
7
8
  * It lives in a shadow root, so the site's CSS cannot reach it and its CSS cannot leak out. It takes the page's font and
8
9
  * language (Arabic right to left, else English), the colour HQ gave it, and a light or dark panel to suit the page.
9
10
  */
10
11
  export type BellPosition = 'bottom_right' | 'bottom_left';
11
- /** The bell as HQ set it up (the web config's `bell`). */
12
+ /** What the button shows: a bell, or the app's logo (its icon). */
13
+ export type BellFace = 'bell' | 'logo';
14
+ /** The button as HQ set it up (the web config's `bell`). */
12
15
  export interface BellSettings {
13
16
  position: BellPosition;
14
17
  /** `#rrggbb`; null: the SDK's blue. */
15
18
  color?: string | null;
16
19
  /** Only browsers that are not subscribed see it (after its thanks, a browser that subscribes no longer does). */
17
20
  hideWhenSubscribed?: boolean;
21
+ /** `bell` unless the logo prompt is chosen. */
22
+ face?: BellFace;
23
+ /** The logo's address (the app's icon); without one, or when it fails to load, the button shows the bell. */
24
+ logoUrl?: string | null;
18
25
  }
19
26
  /** What the bell needs from the SDK. */
20
27
  export interface BellApi {
@@ -27,5 +34,5 @@ export interface BellApi {
27
34
  export interface BellHandle {
28
35
  destroy(): void;
29
36
  }
30
- /** Draws the bell into the page; `destroy()` takes it away again. */
37
+ /** Draws the button into the page; `destroy()` takes it away again. */
31
38
  export declare function mountBell(api: BellApi, settings: BellSettings, language: string): BellHandle;
package/dist/bell.js CHANGED
@@ -66,6 +66,13 @@ button { font: inherit; margin: 0; -webkit-tap-highlight-color: transparent; }
66
66
  position: absolute; top: -3px; right: -3px; display: grid; place-items: center; width: 18px; height: 18px; border-radius: 50%;
67
67
  background: #16a34a; color: #ffffff; border: 2px solid #ffffff;
68
68
  }
69
+ /* the logo face: the app's icon on a white disc ringed in the colour; its badge says what the button is for */
70
+ :host([data-face="logo"]) .bell { background: #ffffff; box-shadow: 0 0 0 2px var(--bell), 0 6px 20px rgba(0, 0, 0, 0.28), 0 1px 3px rgba(0, 0, 0, 0.22); }
71
+ :host([data-face="logo"]) .icon { width: 100%; height: 100%; border-radius: 50%; overflow: hidden; }
72
+ .logo { display: block; width: 100%; height: 100%; object-fit: cover; }
73
+ :host([data-face="logo"][data-view="blocked"]) .logo { filter: grayscale(1); }
74
+ .badge[data-kind="bell"] { background: var(--bell); color: var(--bell-text); }
75
+ .badge[data-kind="blocked"] { background: #6b7280; }
69
76
  .bubble {
70
77
  position: absolute; bottom: 7px; width: max-content; max-width: min(260px, calc(100vw - 104px)); padding: 7px 11px; border-radius: 8px;
71
78
  background: var(--tip); color: var(--tip-text); font-size: 13px; line-height: 1.45; text-align: start;
@@ -105,10 +112,11 @@ button { font: inherit; margin: 0; -webkit-tap-highlight-color: transparent; }
105
112
  :host([data-attention][data-view="unsubscribed"]) .bell::after { animation: none; }
106
113
  }
107
114
  `;
108
- /** Draws the bell into the page; `destroy()` takes it away again. */
115
+ /** Draws the button into the page; `destroy()` takes it away again. */
109
116
  export function mountBell(api, settings, language) {
110
117
  const lang = uiLanguage(language);
111
118
  const t = TEXTS[lang];
119
+ const logoUrl = settings.face === 'logo' && /^https?:\/\//i.test(settings.logoUrl ?? '') ? settings.logoUrl : null;
112
120
  const host = document.createElement('phoenix-notifications-bell');
113
121
  host.dataset.side = settings.position === 'bottom_left' ? 'left' : 'right';
114
122
  host.dataset.view = 'unsubscribed';
@@ -146,18 +154,48 @@ export function mountBell(api, settings, language) {
146
154
  let iconFor = null;
147
155
  /** Hidden until the first read of the state, so it never shows a wrong state (or shows at all, when it hides) first. */
148
156
  let settled = false;
157
+ /** The logo while it loads and after; a logo that fails to load gives way to the bell. */
158
+ let face = logoUrl ? 'logo' : 'bell';
159
+ let logo = null;
149
160
  const viewOf = (state) => (state.permission === 'denied' ? 'blocked' : state.subscribed ? 'subscribed' : 'unsubscribed');
161
+ function drawFace() {
162
+ if (face === 'bell') {
163
+ icon.innerHTML = svg(view === 'blocked' ? ICONS.bellOff : ICONS.bell, 24);
164
+ badge.innerHTML = svg(ICONS.check, 11, 3);
165
+ delete badge.dataset.kind;
166
+ return;
167
+ }
168
+ if (!logo) {
169
+ logo = document.createElement('img');
170
+ logo.className = 'logo';
171
+ logo.alt = '';
172
+ logo.src = logoUrl;
173
+ logo.addEventListener('error', () => {
174
+ face = 'bell';
175
+ iconFor = null;
176
+ render();
177
+ }, { once: true });
178
+ }
179
+ icon.replaceChildren(logo);
180
+ // the logo alone does not say "notifications": a bell on it does, a check once subscribed, a struck bell once blocked
181
+ badge.innerHTML = view === 'subscribed' ? svg(ICONS.check, 11, 3) : svg(view === 'blocked' ? ICONS.bellOff : ICONS.bell, 11, 2.4);
182
+ if (view === 'subscribed')
183
+ delete badge.dataset.kind;
184
+ else
185
+ badge.dataset.kind = view === 'blocked' ? 'blocked' : 'bell';
186
+ }
150
187
  function render() {
151
188
  host.dataset.view = view;
189
+ host.dataset.face = face;
152
190
  const hidden = !settled || (!!settings.hideWhenSubscribed && view === 'subscribed' && !open && !busy && !message);
153
191
  host.style.display = hidden ? 'none' : '';
154
192
  bell.setAttribute('aria-label', t[view]);
155
193
  bell.setAttribute('aria-expanded', String(open));
156
- if (iconFor !== view) {
157
- icon.innerHTML = svg(view === 'blocked' ? ICONS.bellOff : ICONS.bell, 24);
158
- iconFor = view;
194
+ if (iconFor !== `${face}:${view}`) {
195
+ drawFace();
196
+ iconFor = `${face}:${view}`;
159
197
  }
160
- badge.hidden = view !== 'subscribed';
198
+ badge.hidden = face === 'bell' && view !== 'subscribed';
161
199
  panel.hidden = !open;
162
200
  if (!open)
163
201
  return;
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { type StoredDevice } from './store.js';
2
2
  /**
3
3
  * Phoenix Notifications web SDK: subscribes the browser to web push for an app on notifications.phnx-iq.com, binds it to
4
- * the signed-in user with an identity token from the app's own backend, and hands notification clicks to the app. When the
5
- * app has its subscription bell or banner switched on in HQ, it also draws them, so a site needs no button of its own.
4
+ * the signed-in user with an identity token from the app's own backend, and hands notification clicks to the app. It also
5
+ * draws the prompt HQ chose for the app (a bell, the app's logo or a pop-up), so a site needs no button of its own.
6
6
  *
7
7
  * Nothing here throws for an unsupported browser or a denied permission: check `isSupported()` / `permission()`.
8
8
  * The service worker file (phoenix-notifications-sw.js) must be served from the site, at `serviceWorkerPath`.
@@ -16,17 +16,19 @@ export interface InitOptions {
16
16
  serverUrl?: string;
17
17
  serviceWorkerPath?: string;
18
18
  serviceWorkerScope?: string;
19
- /** Language the device reports (picks Arabic or English text, and the bell's language); defaults to the page's, then the browser's. */
19
+ /** Language the device reports (picks Arabic or English text, and the prompt's language); defaults to the page's, then the browser's. */
20
20
  language?: string;
21
21
  appVersion?: string;
22
22
  /**
23
- * The subscription bell, drawn when the app has it switched on in HQ (the app → Web push → Subscription bell). `false`
24
- * never draws it, for a site with its own "Get notifications" button.
23
+ * The floating button (a bell or the app's logo), drawn when HQ chose one of those as the app's prompt (the app → Web push
24
+ * → How visitors subscribe). `false` never draws it. A site with its own button usually has "None" chosen in HQ instead.
25
25
  */
26
26
  bell?: boolean;
27
- /** The subscription banner, shown when the app has it switched on in HQ (the app → Web push → Subscription banner). `false` never shows it. */
27
+ /** The pop-up, shown when HQ chose it as the app's prompt. `false` never shows it. */
28
28
  banner?: boolean;
29
29
  }
30
+ /** How the app's sites offer notifications, as HQ chose: one prompt, or none. */
31
+ export type Prompt = 'none' | 'bell' | 'logo' | 'banner';
30
32
  export type PushPermission = 'granted' | 'denied' | 'default' | 'unsupported';
31
33
  /** Whether this browser gets the app's notifications, for a site's own button. */
32
34
  export interface SubscriptionState {
@@ -47,7 +49,7 @@ export declare const Notifications: {
47
49
  /** True when this browser can receive web push here (a secure context with service workers and the Push API). */
48
50
  isSupported(): boolean;
49
51
  /**
50
- * Registers the service worker, and draws the subscription bell and the banner when the app has them on. When the person
52
+ * Registers the service worker, and draws the prompt HQ chose for the app (a bell, the logo or a pop-up). When the person
51
53
  * already allowed notifications (and did not unsubscribe here), subscribes quietly.
52
54
  */
53
55
  init(init: InitOptions): Promise<void>;
package/dist/index.js CHANGED
@@ -3,14 +3,21 @@ import { mountBell } from './bell.js';
3
3
  import { keys, store } from './store.js';
4
4
  /**
5
5
  * Phoenix Notifications web SDK: subscribes the browser to web push for an app on notifications.phnx-iq.com, binds it to
6
- * the signed-in user with an identity token from the app's own backend, and hands notification clicks to the app. When the
7
- * app has its subscription bell or banner switched on in HQ, it also draws them, so a site needs no button of its own.
6
+ * the signed-in user with an identity token from the app's own backend, and hands notification clicks to the app. It also
7
+ * draws the prompt HQ chose for the app (a bell, the app's logo or a pop-up), so a site needs no button of its own.
8
8
  *
9
9
  * Nothing here throws for an unsupported browser or a denied permission: check `isSupported()` / `permission()`.
10
10
  * The service worker file (phoenix-notifications-sw.js) must be served from the site, at `serviceWorkerPath`.
11
11
  */
12
12
  export const DEFAULT_SERVER_URL = 'https://notifications.phnx-iq.com';
13
13
  export const DEFAULT_SERVICE_WORKER_PATH = '/phoenix-notifications-sw.js';
14
+ function promptOf(web) {
15
+ if (!web)
16
+ return 'none';
17
+ if (web.prompt)
18
+ return web.prompt;
19
+ return web.bell?.enabled ? 'bell' : web.banner?.enabled ? 'banner' : 'none';
20
+ }
14
21
  export class NotificationsError extends Error {
15
22
  status;
16
23
  constructor(status, message) {
@@ -276,11 +283,12 @@ function watchPermission() {
276
283
  .then((status) => status.addEventListener('change', changed))
277
284
  .catch(() => undefined);
278
285
  }
279
- /** Draws the bell once the app's config says so. A failure only means no bell. */
286
+ /** Draws the floating button (a bell, or the app's logo) when it is the app's prompt. A failure only means no button. */
280
287
  async function showBell(appId) {
281
288
  try {
282
289
  const web = await webConfig();
283
- if (bell?.appId !== appId || !web?.bell?.enabled)
290
+ const prompt = promptOf(web);
291
+ if (bell?.appId !== appId || !web?.bell || (prompt !== 'bell' && prompt !== 'logo'))
284
292
  return;
285
293
  if (document.readyState === 'loading')
286
294
  await new Promise((resolve) => document.addEventListener('DOMContentLoaded', resolve, { once: true }));
@@ -294,10 +302,10 @@ async function showBell(appId) {
294
302
  },
295
303
  unsubscribe: () => Notifications.unsubscribe(),
296
304
  onChange: (handler) => Notifications.onSubscriptionChange(handler),
297
- }, web.bell, language());
305
+ }, { ...web.bell, face: prompt === 'logo' ? 'logo' : 'bell', logoUrl: web.iconUrl }, language());
298
306
  }
299
307
  catch (error) {
300
- console.warn('[notifications] the subscription bell is not available', error);
308
+ console.warn('[notifications] the subscribe button is not available', error);
301
309
  }
302
310
  }
303
311
  function whenDocumentReady() {
@@ -306,15 +314,15 @@ function whenDocumentReady() {
306
314
  return new Promise((resolve) => document.addEventListener('DOMContentLoaded', () => resolve(), { once: true }));
307
315
  }
308
316
  /**
309
- * Slides the banner in when its time has come: only for a browser never asked (the permission still to be given, and no
310
- * unsubscribe here), from the page view HQ set, not within the days after a "Later", and after the delay. Each call is one page
311
- * view. A failure only means no banner.
317
+ * Slides the pop-up in when it is the app's prompt and its time has come: only for a browser never asked (the permission still
318
+ * to be given, and no unsubscribe here), from the page view HQ set, not within the days after a "Later", and after the delay.
319
+ * Each call is one page view. A failure only means no pop-up.
312
320
  */
313
321
  async function showBanner(appId) {
314
322
  try {
315
323
  const web = await webConfig();
316
324
  const settings = web?.banner;
317
- if (banner?.appId !== appId || !settings?.enabled)
325
+ if (banner?.appId !== appId || !settings || promptOf(web) !== 'banner')
318
326
  return;
319
327
  if (Notification.permission !== 'default' || (await optedOut()))
320
328
  return;
@@ -328,7 +336,7 @@ async function showBanner(appId) {
328
336
  return;
329
337
  await new Promise((resolve) => setTimeout(resolve, Math.max(0, settings.delaySeconds) * 1000));
330
338
  await whenDocumentReady();
331
- // the page may have moved on meanwhile: another app, the person subscribed with the bell, or answered the browser
339
+ // the page may have moved on meanwhile: another app, the person subscribed with the site's own button, or answered the browser
332
340
  if (banner?.appId !== appId || banner.handle || Notification.permission !== 'default' || (await optedOut()))
333
341
  return;
334
342
  banner.handle = mountBanner({
@@ -341,10 +349,10 @@ async function showBanner(appId) {
341
349
  await store.set(key, { ...latest, dismissedAt: Date.now() });
342
350
  },
343
351
  onChange: (handler) => Notifications.onSubscriptionChange(handler),
344
- }, settings, language(), { iconUrl: web?.iconUrl, aboveBell: !!bell?.handle });
352
+ }, settings, language(), web?.iconUrl);
345
353
  }
346
354
  catch (error) {
347
- console.warn('[notifications] the subscription banner is not available', error);
355
+ console.warn('[notifications] the subscribe pop-up is not available', error);
348
356
  }
349
357
  }
350
358
  export const Notifications = {
@@ -353,7 +361,7 @@ export const Notifications = {
353
361
  return supported();
354
362
  },
355
363
  /**
356
- * Registers the service worker, and draws the subscription bell and the banner when the app has them on. When the person
364
+ * Registers the service worker, and draws the prompt HQ chose for the app (a bell, the logo or a pop-up). When the person
357
365
  * already allowed notifications (and did not unsubscribe here), subscribes quietly.
358
366
  */
359
367
  async init(init) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@phoenix-iq/notifications",
3
- "version": "1.1.0",
4
- "description": "Phoenix Notifications web SDK: browser push subscriptions, the subscription bell, verified sign-in and click handling for apps on notifications.phnx-iq.com.",
3
+ "version": "1.2.0",
4
+ "description": "Phoenix Notifications web SDK: browser push subscriptions, the subscribe prompt (a bell, the app's logo or a pop-up), verified sign-in and click handling for apps on notifications.phnx-iq.com.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
7
7
  "main": "./dist/index.js",