Skip to content

Commit e8dfcb9

Browse files
authored
Merge pull request #192 from GleapSDK/fix-disable-cached-notifications-20261022
Fix notification suppression for cached and visible cards
2 parents 9d4c15d + 99c1a28 commit e8dfcb9

5 files changed

Lines changed: 146 additions & 3 deletions

File tree

‎index.d.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ export namespace Gleap {
170170
function attachNetworkLogs(networkLogs: string): void;
171171
function clearIdentity(): void;
172172
function setTags(tags: string[]): void;
173+
/**
174+
* Hide notification cards, including cached or already-visible cards, without
175+
* hiding the support button or messenger. Reenable to show eligible cards again.
176+
*/
173177
function setDisableInAppNotifications(
174178
disableInAppNotifications: boolean
175179
): void;

‎src/Gleap.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,12 @@ class Gleap {
178178
*/
179179
static setDisableInAppNotifications(disableInAppNotifications) {
180180
const instance = this.getInstance();
181+
if (instance.disableInAppNotifications === disableInAppNotifications) {
182+
return;
183+
}
181184
instance.disableInAppNotifications = disableInAppNotifications;
185+
// Apply route changes to cards already on screen, without marking them read.
186+
GleapNotificationManager.getInstance().renderNotifications();
182187
}
183188

184189
/**

‎src/GleapNotificationManager.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ export default class GleapNotificationManager {
135135
* rendered on an allowed page followed users onto excluded ones.
136136
*/
137137
getVisibleNotifications() {
138+
// Cached cards and direct deliveries bypass performActions' arrival guard.
139+
if (Gleap.getInstance().disableInAppNotifications) {
140+
return [];
141+
}
138142
return this.notifications.filter((notification) => this.notificationPassesPageRules(notification));
139143
}
140144

@@ -199,7 +203,11 @@ export default class GleapNotificationManager {
199203
// Play sound only if it's a new one and its page rules allow the
200204
// current page (it may have arrived on an excluded page and only
201205
// become visible later).
202-
if (notification.sound && this.notificationPassesPageRules(notification)) {
206+
if (
207+
notification.sound &&
208+
!Gleap.getInstance().disableInAppNotifications &&
209+
this.notificationPassesPageRules(notification)
210+
) {
203211
GleapAudioManager.ping();
204212
}
205213
}

‎src/GleapNotificationManager.test.js‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { GleapTabCommunication, GleapSession, GleapAudioManager } from './Gleap';
1+
import Gleap, { GleapTabCommunication, GleapSession, GleapAudioManager } from './Gleap';
22
import { loadFromGleapCache, saveToGleapCache } from './GleapHelper';
33
import GleapNotificationManager from './GleapNotificationManager';
44

@@ -7,7 +7,7 @@ import GleapNotificationManager from './GleapNotificationManager';
77
// GleapTabCommunication, GleapSession and saveToGleapCache.
88
jest.mock('./Gleap', () => ({
99
__esModule: true,
10-
default: {},
10+
default: { getInstance: jest.fn() },
1111
GleapFeedbackButtonManager: { getInstance: jest.fn(() => ({ updateNotificationBadge: jest.fn() })) },
1212
GleapConfigManager: { getInstance: jest.fn(() => ({ getFlowConfig: () => ({}) })) },
1313
GleapSession: { getInstance: jest.fn() },
@@ -44,6 +44,7 @@ const withContainer = (nm) => {
4444
};
4545

4646
beforeEach(() => {
47+
Gleap.getInstance.mockReturnValue({ disableInAppNotifications: false });
4748
sendMessageMock = jest.fn();
4849
GleapTabCommunication.getInstance.mockReturnValue({ sendMessage: sendMessageMock });
4950
GleapSession.getInstance.mockReturnValue({ session: { gleapId: 'user-1' } });
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
import Gleap, {
5+
GleapAudioManager,
6+
GleapConfigManager,
7+
GleapFeedbackButtonManager,
8+
GleapFrameManager,
9+
GleapNotificationManager,
10+
GleapSession,
11+
} from './Gleap';
12+
import * as cache from './GleapHelper';
13+
14+
const news = (extra = {}) => ({
15+
outbound: 'product-announcement',
16+
createdAt: new Date().toISOString(),
17+
cachedForGleapId: 'signup-visitor',
18+
data: { news: { id: 'news-1' }, text: 'Product announcement' },
19+
...extra,
20+
});
21+
22+
const mountNotifications = () => {
23+
const manager = GleapNotificationManager.getInstance();
24+
manager.notificationContainer = document.createElement('div');
25+
document.body.appendChild(manager.notificationContainer);
26+
return manager;
27+
};
28+
29+
const visibleCards = (manager) => manager.notificationContainer.querySelectorAll('.gleap-notification-card');
30+
31+
beforeEach(() => {
32+
// Exercise the real public setter and notification renderer, without SDK startup.
33+
jest.spyOn(Gleap, 'getInstance').mockReturnValue({ disableInAppNotifications: false });
34+
jest.spyOn(GleapSession, 'getInstance').mockReturnValue({
35+
session: { gleapId: 'signup-visitor' },
36+
getName: () => 'Visitor',
37+
});
38+
jest.spyOn(GleapConfigManager, 'getInstance').mockReturnValue({ getFlowConfig: () => ({}) });
39+
jest.spyOn(GleapAudioManager, 'ping').mockImplementation(() => {});
40+
jest.spyOn(cache, 'loadFromGleapCache').mockReturnValue(null);
41+
jest.spyOn(cache, 'saveToGleapCache').mockImplementation(() => {});
42+
GleapNotificationManager.instance = undefined;
43+
});
44+
45+
afterEach(() => {
46+
jest.restoreAllMocks();
47+
GleapNotificationManager.instance = undefined;
48+
document.body.innerHTML = '';
49+
});
50+
51+
test('disabling before startup keeps cached announcements hidden without deleting them', () => {
52+
Gleap.setDisableInAppNotifications(true);
53+
const manager = mountNotifications();
54+
const cached = news();
55+
cache.loadFromGleapCache.mockReturnValue([cached]);
56+
57+
manager.reloadNotificationsFromCache();
58+
59+
expect(visibleCards(manager)).toHaveLength(0);
60+
expect(manager.notifications).toEqual([cached]);
61+
expect(cache.saveToGleapCache).not.toHaveBeenCalled();
62+
63+
Gleap.setDisableInAppNotifications(false);
64+
expect(visibleCards(manager)).toHaveLength(1);
65+
});
66+
67+
test('disabling immediately hides an existing card and keeps it hidden on cache reload', () => {
68+
const manager = mountNotifications();
69+
const cached = news();
70+
cache.loadFromGleapCache.mockReturnValue([cached]);
71+
manager.reloadNotificationsFromCache();
72+
expect(visibleCards(manager)).toHaveLength(1);
73+
74+
Gleap.setDisableInAppNotifications(true);
75+
expect(manager.notificationContainer.children).toHaveLength(0);
76+
77+
// Closing the messenger reloads cached notifications through the same path.
78+
manager.reloadNotificationsFromCache();
79+
expect(manager.notificationContainer.children).toHaveLength(0);
80+
expect(manager.notifications).toEqual([cached]);
81+
82+
Gleap.setDisableInAppNotifications(false);
83+
expect(visibleCards(manager)).toHaveLength(1);
84+
expect(cache.saveToGleapCache).not.toHaveBeenCalled();
85+
});
86+
87+
test('a directly delivered notification stays silent and hidden while disabled', () => {
88+
const manager = mountNotifications();
89+
Gleap.setDisableInAppNotifications(true);
90+
91+
Gleap.showNotification(news({ sound: true }));
92+
93+
expect(visibleCards(manager)).toHaveLength(0);
94+
expect(GleapAudioManager.ping).not.toHaveBeenCalled();
95+
expect(manager.notifications).toHaveLength(1);
96+
97+
Gleap.setDisableInAppNotifications(false);
98+
expect(visibleCards(manager)).toHaveLength(1);
99+
expect(GleapAudioManager.ping).not.toHaveBeenCalled();
100+
});
101+
102+
test('reenabling notifications still respects their page exclusions', () => {
103+
const manager = mountNotifications();
104+
manager.notifications = [news({ pageRules: [{ pageFilter: '/', pageFilterType: 'notcontains' }] })];
105+
106+
Gleap.setDisableInAppNotifications(true);
107+
Gleap.setDisableInAppNotifications(false);
108+
109+
expect(visibleCards(manager)).toHaveLength(0);
110+
expect(manager.notifications).toHaveLength(1);
111+
});
112+
113+
test('support chat can still open while notification cards are disabled', () => {
114+
const frame = { setAppMode: jest.fn(), showWidget: jest.fn(), hideWidget: jest.fn() };
115+
jest.spyOn(GleapFrameManager, 'getInstance').mockReturnValue(frame);
116+
const buttonManager = jest.spyOn(GleapFeedbackButtonManager, 'getInstance');
117+
118+
Gleap.setDisableInAppNotifications(true);
119+
Gleap.open();
120+
121+
expect(frame.setAppMode).toHaveBeenCalledWith('widget');
122+
expect(frame.showWidget).toHaveBeenCalledTimes(1);
123+
expect(frame.hideWidget).not.toHaveBeenCalled();
124+
expect(buttonManager).not.toHaveBeenCalled();
125+
});

0 commit comments

Comments
 (0)