@markwharton/pwa-push 1.5.3 → 2.0.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 (40) hide show
  1. package/dist/client.d.ts +181 -0
  2. package/dist/client.js +412 -0
  3. package/dist/index.d.ts +9 -0
  4. package/dist/index.js +9 -0
  5. package/dist/pwa-push-sw.js +61 -39
  6. package/dist/{server/send.d.ts → server.d.ts} +6 -2
  7. package/dist/{server/send.js → server.js} +9 -7
  8. package/dist/shared.d.ts +22 -2
  9. package/dist/shared.js +9 -6
  10. package/dist/sw.d.ts +1 -1
  11. package/dist/sw.js +2 -2
  12. package/package.json +11 -8
  13. package/dist/__tests__/client/deviceId.test.d.ts +0 -1
  14. package/dist/__tests__/client/deviceId.test.js +0 -134
  15. package/dist/__tests__/client/encoding.test.d.ts +0 -1
  16. package/dist/__tests__/client/encoding.test.js +0 -89
  17. package/dist/__tests__/client/indexedDb.test.d.ts +0 -1
  18. package/dist/__tests__/client/indexedDb.test.js +0 -195
  19. package/dist/__tests__/client/renewal.test.d.ts +0 -1
  20. package/dist/__tests__/client/renewal.test.js +0 -170
  21. package/dist/__tests__/client/subscribe.test.d.ts +0 -1
  22. package/dist/__tests__/client/subscribe.test.js +0 -299
  23. package/dist/__tests__/server/send.test.d.ts +0 -1
  24. package/dist/__tests__/server/send.test.js +0 -226
  25. package/dist/client/deviceId.d.ts +0 -23
  26. package/dist/client/deviceId.js +0 -49
  27. package/dist/client/encoding.d.ts +0 -17
  28. package/dist/client/encoding.js +0 -32
  29. package/dist/client/index.d.ts +0 -4
  30. package/dist/client/index.js +0 -20
  31. package/dist/client/indexedDb.d.ts +0 -38
  32. package/dist/client/indexedDb.js +0 -89
  33. package/dist/client/renewal.d.ts +0 -31
  34. package/dist/client/renewal.js +0 -80
  35. package/dist/client/subscribe.d.ts +0 -88
  36. package/dist/client/subscribe.js +0 -176
  37. package/dist/server/index.d.ts +0 -1
  38. package/dist/server/index.js +0 -11
  39. package/dist/types.d.ts +0 -39
  40. package/dist/types.js +0 -11
@@ -1,195 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- const vitest_1 = require("vitest");
37
- // Mock IndexedDB
38
- const mockObjectStore = {
39
- put: vitest_1.vi.fn(),
40
- get: vitest_1.vi.fn(),
41
- delete: vitest_1.vi.fn()
42
- };
43
- const mockTransaction = {
44
- objectStore: vitest_1.vi.fn(() => mockObjectStore),
45
- oncomplete: null
46
- };
47
- const mockDatabase = {
48
- transaction: vitest_1.vi.fn(() => mockTransaction),
49
- objectStoreNames: {
50
- contains: vitest_1.vi.fn(() => false)
51
- },
52
- createObjectStore: vitest_1.vi.fn(),
53
- close: vitest_1.vi.fn()
54
- };
55
- const mockOpenRequest = {
56
- result: mockDatabase,
57
- error: null,
58
- onsuccess: null,
59
- onerror: null,
60
- onupgradeneeded: null
61
- };
62
- (0, vitest_1.beforeEach)(() => {
63
- vitest_1.vi.resetModules();
64
- vitest_1.vi.clearAllMocks();
65
- // Reset mock implementations
66
- mockOpenRequest.result = mockDatabase;
67
- mockOpenRequest.error = null;
68
- global.indexedDB = {
69
- open: vitest_1.vi.fn((dbName, version) => {
70
- // Simulate async open
71
- setTimeout(() => {
72
- if (mockOpenRequest.onupgradeneeded) {
73
- mockOpenRequest.onupgradeneeded({ target: mockOpenRequest });
74
- }
75
- if (mockOpenRequest.onsuccess) {
76
- mockOpenRequest.onsuccess();
77
- }
78
- }, 0);
79
- return mockOpenRequest;
80
- })
81
- };
82
- });
83
- (0, vitest_1.describe)('IndexedDB utilities', () => {
84
- (0, vitest_1.describe)('saveSubscriptionData', () => {
85
- (0, vitest_1.it)('saves data to IndexedDB', async () => {
86
- const putRequest = {
87
- onsuccess: null,
88
- onerror: null,
89
- result: undefined
90
- };
91
- mockObjectStore.put.mockReturnValue(putRequest);
92
- const { saveSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
93
- const savePromise = saveSubscriptionData({
94
- publicKey: 'test-key',
95
- deviceId: 'test-device',
96
- basePath: '/app'
97
- });
98
- // Simulate success
99
- await new Promise(resolve => setTimeout(resolve, 10));
100
- if (putRequest.onsuccess)
101
- putRequest.onsuccess();
102
- await (0, vitest_1.expect)(savePromise).resolves.toBeUndefined();
103
- (0, vitest_1.expect)(mockObjectStore.put).toHaveBeenCalledWith({ publicKey: 'test-key', deviceId: 'test-device', basePath: '/app' }, 'current');
104
- });
105
- (0, vitest_1.it)('opens database with correct name and version', async () => {
106
- const putRequest = { onsuccess: null, onerror: null, result: undefined };
107
- mockObjectStore.put.mockReturnValue(putRequest);
108
- const { saveSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
109
- saveSubscriptionData({
110
- publicKey: 'key',
111
- deviceId: 'device',
112
- basePath: '/'
113
- });
114
- await new Promise(resolve => setTimeout(resolve, 10));
115
- (0, vitest_1.expect)(global.indexedDB.open).toHaveBeenCalledWith('PushSubscriptionDB', 1);
116
- });
117
- });
118
- (0, vitest_1.describe)('getSubscriptionData', () => {
119
- (0, vitest_1.it)('retrieves data from IndexedDB', async () => {
120
- const testData = {
121
- publicKey: 'stored-key',
122
- deviceId: 'stored-device',
123
- basePath: '/stored'
124
- };
125
- const getRequest = {
126
- onsuccess: null,
127
- onerror: null,
128
- result: testData
129
- };
130
- mockObjectStore.get.mockReturnValue(getRequest);
131
- const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
132
- const getPromise = getSubscriptionData();
133
- // Simulate success
134
- await new Promise(resolve => setTimeout(resolve, 10));
135
- if (getRequest.onsuccess)
136
- getRequest.onsuccess();
137
- const result = await getPromise;
138
- (0, vitest_1.expect)(result).toEqual(testData);
139
- (0, vitest_1.expect)(mockObjectStore.get).toHaveBeenCalledWith('current');
140
- });
141
- (0, vitest_1.it)('returns null when no data exists', async () => {
142
- const getRequest = {
143
- onsuccess: null,
144
- onerror: null,
145
- result: undefined
146
- };
147
- mockObjectStore.get.mockReturnValue(getRequest);
148
- const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
149
- const getPromise = getSubscriptionData();
150
- await new Promise(resolve => setTimeout(resolve, 10));
151
- if (getRequest.onsuccess)
152
- getRequest.onsuccess();
153
- const result = await getPromise;
154
- (0, vitest_1.expect)(result).toBeNull();
155
- });
156
- });
157
- (0, vitest_1.describe)('clearSubscriptionData', () => {
158
- (0, vitest_1.it)('deletes data from IndexedDB', async () => {
159
- const deleteRequest = {
160
- onsuccess: null,
161
- onerror: null,
162
- result: undefined
163
- };
164
- mockObjectStore.delete.mockReturnValue(deleteRequest);
165
- const { clearSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
166
- const clearPromise = clearSubscriptionData();
167
- await new Promise(resolve => setTimeout(resolve, 10));
168
- if (deleteRequest.onsuccess)
169
- deleteRequest.onsuccess();
170
- await (0, vitest_1.expect)(clearPromise).resolves.toBeUndefined();
171
- (0, vitest_1.expect)(mockObjectStore.delete).toHaveBeenCalledWith('current');
172
- });
173
- });
174
- (0, vitest_1.describe)('database initialization', () => {
175
- (0, vitest_1.it)('creates object store on upgrade', async () => {
176
- mockDatabase.objectStoreNames.contains.mockReturnValue(false);
177
- const getRequest = { onsuccess: null, onerror: null, result: undefined };
178
- mockObjectStore.get.mockReturnValue(getRequest);
179
- const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
180
- getSubscriptionData();
181
- await new Promise(resolve => setTimeout(resolve, 10));
182
- (0, vitest_1.expect)(mockDatabase.createObjectStore).toHaveBeenCalledWith('subscriptionData');
183
- });
184
- (0, vitest_1.it)('skips creating object store if exists', async () => {
185
- mockDatabase.objectStoreNames.contains.mockReturnValue(true);
186
- mockDatabase.createObjectStore.mockClear();
187
- const getRequest = { onsuccess: null, onerror: null, result: undefined };
188
- mockObjectStore.get.mockReturnValue(getRequest);
189
- const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
190
- getSubscriptionData();
191
- await new Promise(resolve => setTimeout(resolve, 10));
192
- (0, vitest_1.expect)(mockDatabase.createObjectStore).not.toHaveBeenCalled();
193
- });
194
- });
195
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,170 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- const vitest_1 = require("vitest");
37
- // Mock dependencies
38
- vitest_1.vi.mock('../../client/indexedDb', () => ({
39
- getSubscriptionData: vitest_1.vi.fn()
40
- }));
41
- vitest_1.vi.mock('../../client/encoding', () => ({
42
- urlBase64ToUint8Array: vitest_1.vi.fn(() => new Uint8Array([1, 2, 3]))
43
- }));
44
- vitest_1.vi.mock('../../client/subscribe', () => ({
45
- toPushSubscription: vitest_1.vi.fn((sub) => ({
46
- endpoint: sub.endpoint,
47
- keys: { p256dh: 'key', auth: 'auth' }
48
- }))
49
- }));
50
- const mockSubscription = {
51
- endpoint: 'https://fcm.googleapis.com/renewed',
52
- toJSON: () => ({ keys: { p256dh: 'key', auth: 'auth' } })
53
- };
54
- const mockPushManager = {
55
- subscribe: vitest_1.vi.fn(() => Promise.resolve(mockSubscription))
56
- };
57
- const mockRegistration = {
58
- pushManager: mockPushManager
59
- };
60
- (0, vitest_1.beforeEach)(() => {
61
- vitest_1.vi.clearAllMocks();
62
- // Mock service worker global scope
63
- Object.defineProperty(global, 'self', {
64
- value: { registration: mockRegistration },
65
- writable: true,
66
- configurable: true
67
- });
68
- globalThis.registration = mockRegistration;
69
- // Mock fetch
70
- global.fetch = vitest_1.vi.fn(() => Promise.resolve({
71
- ok: true,
72
- status: 200
73
- }));
74
- // Mock console
75
- global.console = {
76
- ...console,
77
- log: vitest_1.vi.fn(),
78
- error: vitest_1.vi.fn()
79
- };
80
- });
81
- (0, vitest_1.describe)('Push subscription renewal', () => {
82
- (0, vitest_1.describe)('handleSubscriptionChange', () => {
83
- const mockEvent = {};
84
- (0, vitest_1.it)('returns error when no stored data', async () => {
85
- const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
86
- vitest_1.vi.mocked(getSubscriptionData).mockResolvedValue(null);
87
- const { handleSubscriptionChange } = await Promise.resolve().then(() => __importStar(require('../../client/renewal')));
88
- const result = await handleSubscriptionChange(mockEvent, '/api/renew');
89
- (0, vitest_1.expect)(result.ok).toBe(false);
90
- (0, vitest_1.expect)(result.error).toBe('No subscription data found for renewal');
91
- });
92
- (0, vitest_1.it)('renews subscription successfully', async () => {
93
- const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
94
- vitest_1.vi.mocked(getSubscriptionData).mockResolvedValue({
95
- publicKey: 'test-vapid-key',
96
- deviceId: 'test-device-id',
97
- basePath: '/app'
98
- });
99
- const { handleSubscriptionChange } = await Promise.resolve().then(() => __importStar(require('../../client/renewal')));
100
- const result = await handleSubscriptionChange(mockEvent, '/api/renew');
101
- (0, vitest_1.expect)(result.ok).toBe(true);
102
- (0, vitest_1.expect)(fetch).toHaveBeenCalledWith('/api/renew', {
103
- method: 'POST',
104
- headers: { 'Content-Type': 'application/json' },
105
- body: vitest_1.expect.stringContaining('test-device-id')
106
- });
107
- });
108
- (0, vitest_1.it)('returns error when resubscribe fails', async () => {
109
- const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
110
- vitest_1.vi.mocked(getSubscriptionData).mockResolvedValue({
111
- publicKey: 'test-key',
112
- deviceId: 'device',
113
- basePath: '/'
114
- });
115
- mockPushManager.subscribe.mockRejectedValueOnce(new Error('Failed'));
116
- const { handleSubscriptionChange } = await Promise.resolve().then(() => __importStar(require('../../client/renewal')));
117
- const result = await handleSubscriptionChange(mockEvent, '/api/renew');
118
- (0, vitest_1.expect)(result.ok).toBe(false);
119
- (0, vitest_1.expect)(result.error).toBe('Failed to create new subscription');
120
- });
121
- (0, vitest_1.it)('returns error when backend request fails', async () => {
122
- const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
123
- vitest_1.vi.mocked(getSubscriptionData).mockResolvedValue({
124
- publicKey: 'test-key',
125
- deviceId: 'device',
126
- basePath: '/'
127
- });
128
- global.fetch.mockResolvedValueOnce({
129
- ok: false,
130
- status: 500
131
- });
132
- const { handleSubscriptionChange } = await Promise.resolve().then(() => __importStar(require('../../client/renewal')));
133
- const result = await handleSubscriptionChange(mockEvent, '/api/renew');
134
- (0, vitest_1.expect)(result.ok).toBe(false);
135
- (0, vitest_1.expect)(result.error).toBe('Renewal request failed: 500');
136
- });
137
- (0, vitest_1.it)('sends correct renewal request body', async () => {
138
- const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
139
- vitest_1.vi.mocked(getSubscriptionData).mockResolvedValue({
140
- publicKey: 'my-vapid-key',
141
- deviceId: 'my-device-id',
142
- basePath: '/myapp'
143
- });
144
- const { handleSubscriptionChange } = await Promise.resolve().then(() => __importStar(require('../../client/renewal')));
145
- await handleSubscriptionChange(mockEvent, '/api/push/renew');
146
- (0, vitest_1.expect)(fetch).toHaveBeenCalledWith('/api/push/renew', {
147
- method: 'POST',
148
- headers: { 'Content-Type': 'application/json' },
149
- body: vitest_1.expect.any(String)
150
- });
151
- const body = JSON.parse(fetch.mock.calls[0][1].body);
152
- (0, vitest_1.expect)(body.deviceId).toBe('my-device-id');
153
- (0, vitest_1.expect)(body.basePath).toBe('/myapp');
154
- (0, vitest_1.expect)(body.subscription).toBeDefined();
155
- });
156
- (0, vitest_1.it)('handles network error', async () => {
157
- const { getSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
158
- vitest_1.vi.mocked(getSubscriptionData).mockResolvedValue({
159
- publicKey: 'key',
160
- deviceId: 'device',
161
- basePath: '/'
162
- });
163
- global.fetch.mockRejectedValueOnce(new Error('Network error'));
164
- const { handleSubscriptionChange } = await Promise.resolve().then(() => __importStar(require('../../client/renewal')));
165
- const result = await handleSubscriptionChange(mockEvent, '/api/renew');
166
- (0, vitest_1.expect)(result.ok).toBe(false);
167
- (0, vitest_1.expect)(result.error).toBe('Network error');
168
- });
169
- });
170
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,299 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- const vitest_1 = require("vitest");
37
- // Mock factory for client dependencies (needed after vi.resetModules)
38
- function mockClientDependencies() {
39
- vitest_1.vi.mock('../../client/deviceId', () => ({
40
- getDeviceId: vitest_1.vi.fn(() => 'mock-device-id')
41
- }));
42
- vitest_1.vi.mock('../../client/indexedDb', () => ({
43
- saveSubscriptionData: vitest_1.vi.fn(() => Promise.resolve()),
44
- clearSubscriptionData: vitest_1.vi.fn(() => Promise.resolve())
45
- }));
46
- vitest_1.vi.mock('../../client/encoding', () => ({
47
- urlBase64ToUint8Array: vitest_1.vi.fn(() => new Uint8Array([1, 2, 3]))
48
- }));
49
- }
50
- // Initial mock setup (hoisted by Vitest)
51
- mockClientDependencies();
52
- // Setup browser mocks
53
- const mockSubscription = {
54
- endpoint: 'https://fcm.googleapis.com/test',
55
- toJSON: () => ({
56
- endpoint: 'https://fcm.googleapis.com/test',
57
- keys: {
58
- p256dh: 'test-p256dh',
59
- auth: 'test-auth'
60
- }
61
- }),
62
- unsubscribe: vitest_1.vi.fn(() => Promise.resolve(true))
63
- };
64
- const mockPushManager = {
65
- subscribe: vitest_1.vi.fn(() => Promise.resolve(mockSubscription)),
66
- getSubscription: vitest_1.vi.fn(() => Promise.resolve(mockSubscription))
67
- };
68
- const mockRegistration = {
69
- pushManager: mockPushManager
70
- };
71
- // Helper to setup navigator with serviceWorker
72
- function setupNavigatorWithServiceWorker() {
73
- Object.defineProperty(global, 'navigator', {
74
- value: {
75
- serviceWorker: {
76
- ready: Promise.resolve(mockRegistration)
77
- }
78
- },
79
- writable: true,
80
- configurable: true
81
- });
82
- }
83
- // Helper to setup empty navigator (no serviceWorker)
84
- function setupEmptyNavigator() {
85
- Object.defineProperty(global, 'navigator', {
86
- value: {},
87
- writable: true,
88
- configurable: true
89
- });
90
- }
91
- (0, vitest_1.beforeEach)(() => {
92
- vitest_1.vi.clearAllMocks();
93
- // Mock Notification
94
- global.Notification = {
95
- permission: 'default',
96
- requestPermission: vitest_1.vi.fn(() => Promise.resolve('granted'))
97
- };
98
- setupNavigatorWithServiceWorker();
99
- // Mock window.PushManager
100
- global.window = {
101
- PushManager: {}
102
- };
103
- });
104
- (0, vitest_1.describe)('Push subscription client', () => {
105
- (0, vitest_1.describe)('getPermissionState', () => {
106
- (0, vitest_1.it)('returns current notification permission', async () => {
107
- global.Notification.permission = 'granted';
108
- const { getPermissionState } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
109
- (0, vitest_1.expect)(getPermissionState()).toBe('granted');
110
- });
111
- (0, vitest_1.it)('returns denied permission', async () => {
112
- global.Notification.permission = 'denied';
113
- const { getPermissionState } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
114
- (0, vitest_1.expect)(getPermissionState()).toBe('denied');
115
- });
116
- (0, vitest_1.it)('returns default permission', async () => {
117
- global.Notification.permission = 'default';
118
- const { getPermissionState } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
119
- (0, vitest_1.expect)(getPermissionState()).toBe('default');
120
- });
121
- });
122
- (0, vitest_1.describe)('requestPermission', () => {
123
- (0, vitest_1.it)('requests permission and returns result', async () => {
124
- global.Notification.requestPermission.mockResolvedValue('granted');
125
- const { requestPermission } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
126
- const result = await requestPermission();
127
- (0, vitest_1.expect)(result).toBe('granted');
128
- (0, vitest_1.expect)(global.Notification.requestPermission).toHaveBeenCalled();
129
- });
130
- (0, vitest_1.it)('returns denied when user denies', async () => {
131
- global.Notification.requestPermission.mockResolvedValue('denied');
132
- const { requestPermission } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
133
- const result = await requestPermission();
134
- (0, vitest_1.expect)(result).toBe('denied');
135
- });
136
- });
137
- (0, vitest_1.describe)('isPushSupported', () => {
138
- (0, vitest_1.it)('returns true when serviceWorker and PushManager available', async () => {
139
- const { isPushSupported } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
140
- (0, vitest_1.expect)(isPushSupported()).toBe(true);
141
- });
142
- (0, vitest_1.it)('returns false when serviceWorker missing', async () => {
143
- setupEmptyNavigator();
144
- vitest_1.vi.resetModules();
145
- const { isPushSupported } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
146
- (0, vitest_1.expect)(isPushSupported()).toBe(false);
147
- });
148
- (0, vitest_1.it)('returns false when PushManager missing', async () => {
149
- delete global.window.PushManager;
150
- vitest_1.vi.resetModules();
151
- const { isPushSupported } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
152
- (0, vitest_1.expect)(isPushSupported()).toBe(false);
153
- });
154
- });
155
- (0, vitest_1.describe)('toPushSubscription', () => {
156
- (0, vitest_1.it)('converts browser subscription to internal format', async () => {
157
- const { toPushSubscription } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
158
- const result = toPushSubscription(mockSubscription);
159
- (0, vitest_1.expect)(result.endpoint).toBe('https://fcm.googleapis.com/test');
160
- (0, vitest_1.expect)(result.keys.p256dh).toBe('test-p256dh');
161
- (0, vitest_1.expect)(result.keys.auth).toBe('test-auth');
162
- });
163
- (0, vitest_1.it)('handles missing keys', async () => {
164
- const { toPushSubscription } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
165
- const subWithoutKeys = {
166
- endpoint: 'https://example.com',
167
- toJSON: () => ({ endpoint: 'https://example.com', keys: undefined })
168
- };
169
- const result = toPushSubscription(subWithoutKeys);
170
- (0, vitest_1.expect)(result.keys.p256dh).toBe('');
171
- (0, vitest_1.expect)(result.keys.auth).toBe('');
172
- });
173
- });
174
- (0, vitest_1.describe)('subscribeToPush', () => {
175
- (0, vitest_1.it)('returns error when push not supported', async () => {
176
- setupEmptyNavigator();
177
- vitest_1.vi.resetModules();
178
- mockClientDependencies();
179
- const { subscribeToPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
180
- const result = await subscribeToPush('vapid-key');
181
- (0, vitest_1.expect)(result.ok).toBe(false);
182
- (0, vitest_1.expect)(result.error).toBe('Push notifications not supported');
183
- });
184
- (0, vitest_1.it)('returns error when permission denied', async () => {
185
- global.Notification.requestPermission.mockResolvedValue('denied');
186
- const { subscribeToPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
187
- const result = await subscribeToPush('vapid-key');
188
- (0, vitest_1.expect)(result.ok).toBe(false);
189
- (0, vitest_1.expect)(result.error).toBe('Push permission denied');
190
- });
191
- (0, vitest_1.it)('returns subscription request on success', async () => {
192
- global.Notification.requestPermission.mockResolvedValue('granted');
193
- const { subscribeToPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
194
- const result = await subscribeToPush('vapid-key', '/app');
195
- (0, vitest_1.expect)(result.ok).toBe(true);
196
- (0, vitest_1.expect)(result.data?.deviceId).toBe('mock-device-id');
197
- (0, vitest_1.expect)(result.data?.basePath).toBe('/app');
198
- (0, vitest_1.expect)(result.data?.subscription.endpoint).toBe('https://fcm.googleapis.com/test');
199
- });
200
- (0, vitest_1.it)('saves subscription data to IndexedDB', async () => {
201
- global.Notification.requestPermission.mockResolvedValue('granted');
202
- const { saveSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
203
- const { subscribeToPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
204
- await subscribeToPush('vapid-key', '/myapp');
205
- (0, vitest_1.expect)(saveSubscriptionData).toHaveBeenCalledWith({
206
- publicKey: 'vapid-key',
207
- deviceId: 'mock-device-id',
208
- basePath: '/myapp'
209
- });
210
- });
211
- (0, vitest_1.it)('uses default basePath of "/"', async () => {
212
- global.Notification.requestPermission.mockResolvedValue('granted');
213
- const { subscribeToPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
214
- const result = await subscribeToPush('vapid-key');
215
- (0, vitest_1.expect)(result.data?.basePath).toBe('/');
216
- });
217
- (0, vitest_1.it)('handles subscription error', async () => {
218
- global.Notification.requestPermission.mockResolvedValue('granted');
219
- mockPushManager.subscribe.mockRejectedValueOnce(new Error('Network error'));
220
- const { subscribeToPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
221
- const result = await subscribeToPush('vapid-key');
222
- (0, vitest_1.expect)(result.ok).toBe(false);
223
- (0, vitest_1.expect)(result.error).toBe('Network error');
224
- });
225
- (0, vitest_1.it)('handles non-Error throw', async () => {
226
- global.Notification.requestPermission.mockResolvedValue('granted');
227
- mockPushManager.subscribe.mockRejectedValueOnce('string error');
228
- const { subscribeToPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
229
- const result = await subscribeToPush('vapid-key');
230
- (0, vitest_1.expect)(result.ok).toBe(false);
231
- (0, vitest_1.expect)(result.error).toBe('Subscription failed');
232
- });
233
- });
234
- (0, vitest_1.describe)('unsubscribeFromPush', () => {
235
- (0, vitest_1.it)('unsubscribes successfully', async () => {
236
- const { unsubscribeFromPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
237
- const result = await unsubscribeFromPush();
238
- (0, vitest_1.expect)(result.ok).toBe(true);
239
- (0, vitest_1.expect)(mockSubscription.unsubscribe).toHaveBeenCalled();
240
- });
241
- (0, vitest_1.it)('clears subscription data from IndexedDB', async () => {
242
- const { clearSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
243
- const { unsubscribeFromPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
244
- await unsubscribeFromPush();
245
- (0, vitest_1.expect)(clearSubscriptionData).toHaveBeenCalled();
246
- });
247
- (0, vitest_1.it)('succeeds when no subscription exists', async () => {
248
- mockPushManager.getSubscription.mockResolvedValueOnce(null);
249
- const { clearSubscriptionData } = await Promise.resolve().then(() => __importStar(require('../../client/indexedDb')));
250
- const { unsubscribeFromPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
251
- const result = await unsubscribeFromPush();
252
- (0, vitest_1.expect)(result.ok).toBe(true);
253
- (0, vitest_1.expect)(clearSubscriptionData).toHaveBeenCalled();
254
- });
255
- (0, vitest_1.it)('handles unsubscribe error', async () => {
256
- mockPushManager.getSubscription.mockRejectedValueOnce(new Error('Failed'));
257
- const { unsubscribeFromPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
258
- const result = await unsubscribeFromPush();
259
- (0, vitest_1.expect)(result.ok).toBe(false);
260
- (0, vitest_1.expect)(result.error).toBe('Failed');
261
- });
262
- (0, vitest_1.it)('handles non-Error throw', async () => {
263
- mockPushManager.getSubscription.mockRejectedValueOnce('string error');
264
- const { unsubscribeFromPush } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
265
- const result = await unsubscribeFromPush();
266
- (0, vitest_1.expect)(result.ok).toBe(false);
267
- (0, vitest_1.expect)(result.error).toBe('Unsubscribe failed');
268
- });
269
- });
270
- (0, vitest_1.describe)('getCurrentSubscription', () => {
271
- (0, vitest_1.it)('returns current subscription', async () => {
272
- const { getCurrentSubscription } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
273
- const result = await getCurrentSubscription();
274
- (0, vitest_1.expect)(result.ok).toBe(true);
275
- (0, vitest_1.expect)(result.data?.endpoint).toBe('https://fcm.googleapis.com/test');
276
- });
277
- (0, vitest_1.it)('returns error when no subscription', async () => {
278
- mockPushManager.getSubscription.mockResolvedValueOnce(null);
279
- const { getCurrentSubscription } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
280
- const result = await getCurrentSubscription();
281
- (0, vitest_1.expect)(result.ok).toBe(false);
282
- (0, vitest_1.expect)(result.error).toBe('No active subscription');
283
- });
284
- (0, vitest_1.it)('handles error', async () => {
285
- mockPushManager.getSubscription.mockRejectedValueOnce(new Error('Service worker error'));
286
- const { getCurrentSubscription } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
287
- const result = await getCurrentSubscription();
288
- (0, vitest_1.expect)(result.ok).toBe(false);
289
- (0, vitest_1.expect)(result.error).toBe('Service worker error');
290
- });
291
- (0, vitest_1.it)('handles non-Error throw', async () => {
292
- mockPushManager.getSubscription.mockRejectedValueOnce('string error');
293
- const { getCurrentSubscription } = await Promise.resolve().then(() => __importStar(require('../../client/subscribe')));
294
- const result = await getCurrentSubscription();
295
- (0, vitest_1.expect)(result.ok).toBe(false);
296
- (0, vitest_1.expect)(result.error).toBe('Failed to get subscription');
297
- });
298
- });
299
- });