@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.
- package/dist/client.d.ts +181 -0
- package/dist/client.js +412 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/pwa-push-sw.js +61 -39
- package/dist/{server/send.d.ts → server.d.ts} +6 -2
- package/dist/{server/send.js → server.js} +9 -7
- package/dist/shared.d.ts +22 -2
- package/dist/shared.js +9 -6
- package/dist/sw.d.ts +1 -1
- package/dist/sw.js +2 -2
- package/package.json +11 -8
- package/dist/__tests__/client/deviceId.test.d.ts +0 -1
- package/dist/__tests__/client/deviceId.test.js +0 -134
- package/dist/__tests__/client/encoding.test.d.ts +0 -1
- package/dist/__tests__/client/encoding.test.js +0 -89
- package/dist/__tests__/client/indexedDb.test.d.ts +0 -1
- package/dist/__tests__/client/indexedDb.test.js +0 -195
- package/dist/__tests__/client/renewal.test.d.ts +0 -1
- package/dist/__tests__/client/renewal.test.js +0 -170
- package/dist/__tests__/client/subscribe.test.d.ts +0 -1
- package/dist/__tests__/client/subscribe.test.js +0 -299
- package/dist/__tests__/server/send.test.d.ts +0 -1
- package/dist/__tests__/server/send.test.js +0 -226
- package/dist/client/deviceId.d.ts +0 -23
- package/dist/client/deviceId.js +0 -49
- package/dist/client/encoding.d.ts +0 -17
- package/dist/client/encoding.js +0 -32
- package/dist/client/index.d.ts +0 -4
- package/dist/client/index.js +0 -20
- package/dist/client/indexedDb.d.ts +0 -38
- package/dist/client/indexedDb.js +0 -89
- package/dist/client/renewal.d.ts +0 -31
- package/dist/client/renewal.js +0 -80
- package/dist/client/subscribe.d.ts +0 -88
- package/dist/client/subscribe.js +0 -176
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.js +0 -11
- package/dist/types.d.ts +0 -39
- package/dist/types.js +0 -11
package/dist/pwa-push-sw.js
CHANGED
|
@@ -31,23 +31,55 @@ var PwaPush = (() => {
|
|
|
31
31
|
));
|
|
32
32
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
33
|
|
|
34
|
-
// ../core/dist/
|
|
35
|
-
var
|
|
36
|
-
"../core/dist/
|
|
34
|
+
// ../core/dist/shared.js
|
|
35
|
+
var require_shared = __commonJS({
|
|
36
|
+
"../core/dist/shared.js"(exports) {
|
|
37
37
|
"use strict";
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.HTTP_STATUS = void 0;
|
|
39
40
|
exports.ok = ok2;
|
|
40
|
-
exports.okVoid =
|
|
41
|
-
exports.err =
|
|
41
|
+
exports.okVoid = okVoid2;
|
|
42
|
+
exports.err = err2;
|
|
43
|
+
exports.hasUsername = hasUsername;
|
|
44
|
+
exports.hasRole = hasRole;
|
|
45
|
+
exports.isAdmin = isAdmin;
|
|
46
|
+
exports.getErrorMessage = getErrorMessage2;
|
|
42
47
|
function ok2(data) {
|
|
43
48
|
return { ok: true, data };
|
|
44
49
|
}
|
|
45
|
-
function
|
|
50
|
+
function okVoid2() {
|
|
46
51
|
return { ok: true };
|
|
47
52
|
}
|
|
48
|
-
function
|
|
53
|
+
function err2(error, statusCode) {
|
|
49
54
|
return statusCode !== void 0 ? { ok: false, error, statusCode } : { ok: false, error };
|
|
50
55
|
}
|
|
56
|
+
function hasUsername(payload) {
|
|
57
|
+
return "username" in payload && typeof payload.username === "string";
|
|
58
|
+
}
|
|
59
|
+
function hasRole(payload) {
|
|
60
|
+
return "role" in payload;
|
|
61
|
+
}
|
|
62
|
+
function isAdmin(payload) {
|
|
63
|
+
return hasRole(payload) && payload.role === "admin";
|
|
64
|
+
}
|
|
65
|
+
exports.HTTP_STATUS = {
|
|
66
|
+
OK: 200,
|
|
67
|
+
CREATED: 201,
|
|
68
|
+
NO_CONTENT: 204,
|
|
69
|
+
BAD_REQUEST: 400,
|
|
70
|
+
UNAUTHORIZED: 401,
|
|
71
|
+
FORBIDDEN: 403,
|
|
72
|
+
NOT_FOUND: 404,
|
|
73
|
+
CONFLICT: 409,
|
|
74
|
+
GONE: 410,
|
|
75
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
76
|
+
TOO_MANY_REQUESTS: 429,
|
|
77
|
+
INTERNAL_ERROR: 500,
|
|
78
|
+
SERVICE_UNAVAILABLE: 503
|
|
79
|
+
};
|
|
80
|
+
function getErrorMessage2(error, fallback) {
|
|
81
|
+
return error instanceof Error ? error.message : fallback;
|
|
82
|
+
}
|
|
51
83
|
}
|
|
52
84
|
});
|
|
53
85
|
|
|
@@ -57,10 +89,8 @@ var PwaPush = (() => {
|
|
|
57
89
|
handleSubscriptionChange: () => handleSubscriptionChange
|
|
58
90
|
});
|
|
59
91
|
|
|
60
|
-
// src/client
|
|
61
|
-
var
|
|
62
|
-
|
|
63
|
-
// src/client/encoding.ts
|
|
92
|
+
// src/client.ts
|
|
93
|
+
var import_shared = __toESM(require_shared());
|
|
64
94
|
function urlBase64ToUint8Array(base64String) {
|
|
65
95
|
const padding = "=".repeat((4 - base64String.length % 4) % 4);
|
|
66
96
|
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
@@ -72,8 +102,6 @@ var PwaPush = (() => {
|
|
|
72
102
|
}
|
|
73
103
|
return outputArray;
|
|
74
104
|
}
|
|
75
|
-
|
|
76
|
-
// src/client/indexedDb.ts
|
|
77
105
|
var DB_NAME = "PushSubscriptionDB";
|
|
78
106
|
var STORE_NAME = "subscriptionData";
|
|
79
107
|
var DATA_KEY = "current";
|
|
@@ -108,9 +136,6 @@ var PwaPush = (() => {
|
|
|
108
136
|
);
|
|
109
137
|
return result || null;
|
|
110
138
|
}
|
|
111
|
-
|
|
112
|
-
// src/client/subscribe.ts
|
|
113
|
-
var import_types = __toESM(require_types());
|
|
114
139
|
function toPushSubscription(browserSub) {
|
|
115
140
|
const json = browserSub.toJSON();
|
|
116
141
|
return {
|
|
@@ -121,17 +146,30 @@ var PwaPush = (() => {
|
|
|
121
146
|
}
|
|
122
147
|
};
|
|
123
148
|
}
|
|
124
|
-
|
|
125
|
-
|
|
149
|
+
async function resubscribe(data) {
|
|
150
|
+
try {
|
|
151
|
+
const self = globalThis;
|
|
152
|
+
const registration = self.registration;
|
|
153
|
+
const applicationServerKey = urlBase64ToUint8Array(data.publicKey);
|
|
154
|
+
const subscription = await registration.pushManager.subscribe({
|
|
155
|
+
userVisibleOnly: true,
|
|
156
|
+
applicationServerKey
|
|
157
|
+
});
|
|
158
|
+
return toPushSubscription(subscription);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error("Resubscribe failed:", error);
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
126
164
|
async function handleSubscriptionChange(event, renewEndpoint) {
|
|
127
165
|
try {
|
|
128
166
|
const data = await getSubscriptionData();
|
|
129
167
|
if (!data) {
|
|
130
|
-
return (0,
|
|
168
|
+
return (0, import_shared.err)("No subscription data found for renewal");
|
|
131
169
|
}
|
|
132
170
|
const newSubscription = await resubscribe(data);
|
|
133
171
|
if (!newSubscription) {
|
|
134
|
-
return (0,
|
|
172
|
+
return (0, import_shared.err)("Failed to create new subscription");
|
|
135
173
|
}
|
|
136
174
|
const renewalRequest = {
|
|
137
175
|
subscription: newSubscription,
|
|
@@ -144,27 +182,11 @@ var PwaPush = (() => {
|
|
|
144
182
|
body: JSON.stringify(renewalRequest)
|
|
145
183
|
});
|
|
146
184
|
if (!response.ok) {
|
|
147
|
-
return (0,
|
|
185
|
+
return (0, import_shared.err)(`Renewal request failed: ${response.status}`);
|
|
148
186
|
}
|
|
149
|
-
return (0,
|
|
187
|
+
return (0, import_shared.okVoid)();
|
|
150
188
|
} catch (error) {
|
|
151
|
-
|
|
152
|
-
return (0, import_types2.err)(message);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
async function resubscribe(data) {
|
|
156
|
-
try {
|
|
157
|
-
const self = globalThis;
|
|
158
|
-
const registration = self.registration;
|
|
159
|
-
const applicationServerKey = urlBase64ToUint8Array(data.publicKey);
|
|
160
|
-
const subscription = await registration.pushManager.subscribe({
|
|
161
|
-
userVisibleOnly: true,
|
|
162
|
-
applicationServerKey
|
|
163
|
-
});
|
|
164
|
-
return toPushSubscription(subscription);
|
|
165
|
-
} catch (error) {
|
|
166
|
-
console.error("Resubscribe failed:", error);
|
|
167
|
-
return null;
|
|
189
|
+
return (0, import_shared.err)((0, import_shared.getErrorMessage)(error, "Subscription renewal failed"));
|
|
168
190
|
}
|
|
169
191
|
}
|
|
170
192
|
return __toCommonJS(sw_exports);
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* pwa-push/server - Server-side Web Push notification utilities
|
|
3
|
+
*
|
|
4
|
+
* Requires: web-push package
|
|
5
|
+
*/
|
|
6
|
+
import { Result, PushSubscription, NotificationPayload } from './shared';
|
|
3
7
|
/**
|
|
4
8
|
* Initializes VAPID configuration for Web Push. Call once at application startup.
|
|
5
9
|
* @param config - VAPID configuration
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* pwa-push/server - Server-side Web Push notification utilities
|
|
4
|
+
*
|
|
5
|
+
* Requires: web-push package
|
|
6
|
+
*/
|
|
2
7
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
8
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
9
|
};
|
|
@@ -11,10 +16,7 @@ exports.sendPushToAll = sendPushToAll;
|
|
|
11
16
|
exports.sendPushWithDetails = sendPushWithDetails;
|
|
12
17
|
exports.isSubscriptionExpired = isSubscriptionExpired;
|
|
13
18
|
const web_push_1 = __importDefault(require("web-push"));
|
|
14
|
-
const
|
|
15
|
-
/**
|
|
16
|
-
* Server-side Web Push notification utilities
|
|
17
|
-
*/
|
|
19
|
+
const shared_1 = require("./shared");
|
|
18
20
|
let vapidConfig = null;
|
|
19
21
|
/**
|
|
20
22
|
* Initializes VAPID configuration for Web Push. Call once at application startup.
|
|
@@ -112,7 +114,7 @@ async function sendPushToAll(subscriptions, payload) {
|
|
|
112
114
|
*/
|
|
113
115
|
async function sendPushWithDetails(subscription, payload) {
|
|
114
116
|
if (!vapidConfig) {
|
|
115
|
-
return (0,
|
|
117
|
+
return (0, shared_1.err)('Push not initialized');
|
|
116
118
|
}
|
|
117
119
|
// Auto-inject basePath from subscription if not set in payload
|
|
118
120
|
const payloadWithBasePath = {
|
|
@@ -121,11 +123,11 @@ async function sendPushWithDetails(subscription, payload) {
|
|
|
121
123
|
};
|
|
122
124
|
try {
|
|
123
125
|
await web_push_1.default.sendNotification(subscription, JSON.stringify(payloadWithBasePath));
|
|
124
|
-
return (0,
|
|
126
|
+
return (0, shared_1.okVoid)();
|
|
125
127
|
}
|
|
126
128
|
catch (error) {
|
|
127
129
|
const webPushError = error;
|
|
128
|
-
return (0,
|
|
130
|
+
return (0, shared_1.err)(webPushError.message || 'Unknown error', webPushError.statusCode);
|
|
129
131
|
}
|
|
130
132
|
}
|
|
131
133
|
/**
|
package/dist/shared.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Push notification types
|
|
2
|
+
* pwa-push/shared - Push notification types shared between server and client
|
|
3
|
+
*
|
|
4
|
+
* This module has NO Node.js dependencies and can be safely imported in browsers.
|
|
5
|
+
*/
|
|
6
|
+
export { Result, ok, okVoid, err, getErrorMessage } from '@markwharton/pwa-core/shared';
|
|
7
|
+
/**
|
|
8
|
+
* Browser push subscription for backend storage.
|
|
3
9
|
*/
|
|
4
|
-
export { Result, ok, okVoid, err } from '@markwharton/pwa-core/types';
|
|
5
10
|
export interface PushSubscription {
|
|
6
11
|
endpoint: string;
|
|
7
12
|
keys: {
|
|
@@ -10,6 +15,9 @@ export interface PushSubscription {
|
|
|
10
15
|
};
|
|
11
16
|
basePath?: string;
|
|
12
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Notification payload for push messages.
|
|
20
|
+
*/
|
|
13
21
|
export interface NotificationPayload {
|
|
14
22
|
title: string;
|
|
15
23
|
body: string;
|
|
@@ -21,19 +29,31 @@ export interface NotificationPayload {
|
|
|
21
29
|
timestamp?: string;
|
|
22
30
|
basePath?: string;
|
|
23
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* VAPID configuration for web push.
|
|
34
|
+
*/
|
|
24
35
|
export interface VapidConfig {
|
|
25
36
|
publicKey: string;
|
|
26
37
|
privateKey: string;
|
|
27
38
|
subject: string;
|
|
28
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Subscription request sent to backend.
|
|
42
|
+
*/
|
|
29
43
|
export interface SubscriptionRequest {
|
|
30
44
|
subscription: PushSubscription;
|
|
31
45
|
deviceId: string;
|
|
32
46
|
basePath?: string;
|
|
33
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Data stored in IndexedDB for service worker renewal.
|
|
50
|
+
*/
|
|
34
51
|
export interface SubscriptionData {
|
|
35
52
|
publicKey: string;
|
|
36
53
|
deviceId: string;
|
|
37
54
|
basePath: string;
|
|
38
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Browser notification permission state.
|
|
58
|
+
*/
|
|
39
59
|
export type PushPermissionState = 'granted' | 'denied' | 'default';
|
package/dist/shared.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Push notification types
|
|
3
|
+
* pwa-push/shared - Push notification types shared between server and client
|
|
4
|
+
*
|
|
5
|
+
* This module has NO Node.js dependencies and can be safely imported in browsers.
|
|
4
6
|
*/
|
|
5
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.err = exports.okVoid = exports.ok = void 0;
|
|
8
|
+
exports.getErrorMessage = exports.err = exports.okVoid = exports.ok = void 0;
|
|
7
9
|
// Re-export Result from pwa-core for convenience
|
|
8
|
-
var
|
|
9
|
-
Object.defineProperty(exports, "ok", { enumerable: true, get: function () { return
|
|
10
|
-
Object.defineProperty(exports, "okVoid", { enumerable: true, get: function () { return
|
|
11
|
-
Object.defineProperty(exports, "err", { enumerable: true, get: function () { return
|
|
10
|
+
var shared_1 = require("@markwharton/pwa-core/shared");
|
|
11
|
+
Object.defineProperty(exports, "ok", { enumerable: true, get: function () { return shared_1.ok; } });
|
|
12
|
+
Object.defineProperty(exports, "okVoid", { enumerable: true, get: function () { return shared_1.okVoid; } });
|
|
13
|
+
Object.defineProperty(exports, "err", { enumerable: true, get: function () { return shared_1.err; } });
|
|
14
|
+
Object.defineProperty(exports, "getErrorMessage", { enumerable: true, get: function () { return shared_1.getErrorMessage; } });
|
package/dist/sw.d.ts
CHANGED
package/dist/sw.js
CHANGED
|
@@ -14,5 +14,5 @@
|
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
16
|
exports.handleSubscriptionChange = void 0;
|
|
17
|
-
var
|
|
18
|
-
Object.defineProperty(exports, "handleSubscriptionChange", { enumerable: true, get: function () { return
|
|
17
|
+
var client_1 = require("./client");
|
|
18
|
+
Object.defineProperty(exports, "handleSubscriptionChange", { enumerable: true, get: function () { return client_1.handleSubscriptionChange; } });
|
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markwharton/pwa-push",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Web push notifications for Azure PWA projects",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./dist/index.js",
|
|
9
|
-
"./server": "./dist/server
|
|
10
|
-
"./client": "./dist/client
|
|
9
|
+
"./server": "./dist/server.js",
|
|
10
|
+
"./client": "./dist/client.js",
|
|
11
11
|
"./shared": "./dist/shared.js",
|
|
12
12
|
"./sw": "./dist/pwa-push-sw.js"
|
|
13
13
|
},
|
|
14
14
|
"typesVersions": {
|
|
15
15
|
"*": {
|
|
16
16
|
"server": [
|
|
17
|
-
"dist/server
|
|
17
|
+
"dist/server.d.ts"
|
|
18
18
|
],
|
|
19
19
|
"client": [
|
|
20
|
-
"dist/client
|
|
20
|
+
"dist/client.d.ts"
|
|
21
21
|
],
|
|
22
22
|
"shared": [
|
|
23
23
|
"dist/shared.d.ts"
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"clean": "rm -rf dist"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@markwharton/pwa-core": "^
|
|
33
|
+
"@markwharton/pwa-core": "^2.0.0",
|
|
34
34
|
"web-push": "^3.6.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@markwharton/pwa-core": "^
|
|
37
|
+
"@markwharton/pwa-core": "^2.0.0",
|
|
38
38
|
"@types/node": "^20.10.0",
|
|
39
39
|
"@types/web-push": "^3.6.4",
|
|
40
40
|
"esbuild": "^0.27.2",
|
|
@@ -50,5 +50,8 @@
|
|
|
50
50
|
"directory": "packages/push"
|
|
51
51
|
},
|
|
52
52
|
"author": "Mark Wharton",
|
|
53
|
-
"license": "MIT"
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20"
|
|
56
|
+
}
|
|
54
57
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,134 +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
|
-
// Storage for mock
|
|
38
|
-
let mockStore = {};
|
|
39
|
-
// Mock localStorage
|
|
40
|
-
const localStorageMock = {
|
|
41
|
-
getItem: vitest_1.vi.fn((key) => mockStore[key] || null),
|
|
42
|
-
setItem: vitest_1.vi.fn((key, value) => { mockStore[key] = value; }),
|
|
43
|
-
removeItem: vitest_1.vi.fn((key) => { delete mockStore[key]; }),
|
|
44
|
-
clear: vitest_1.vi.fn(() => { mockStore = {}; })
|
|
45
|
-
};
|
|
46
|
-
// Mock crypto.getRandomValues
|
|
47
|
-
const mockGetRandomValues = vitest_1.vi.fn((array) => {
|
|
48
|
-
for (let i = 0; i < array.length; i++) {
|
|
49
|
-
array[i] = Math.floor(Math.random() * 256);
|
|
50
|
-
}
|
|
51
|
-
return array;
|
|
52
|
-
});
|
|
53
|
-
// Helper to setup global mocks (needed after vi.resetModules)
|
|
54
|
-
function setupGlobalMocks() {
|
|
55
|
-
Object.defineProperty(global, 'localStorage', {
|
|
56
|
-
value: localStorageMock,
|
|
57
|
-
writable: true,
|
|
58
|
-
configurable: true
|
|
59
|
-
});
|
|
60
|
-
Object.defineProperty(global, 'crypto', {
|
|
61
|
-
value: { getRandomValues: mockGetRandomValues },
|
|
62
|
-
writable: true,
|
|
63
|
-
configurable: true
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
(0, vitest_1.beforeEach)(() => {
|
|
67
|
-
vitest_1.vi.resetModules();
|
|
68
|
-
mockStore = {};
|
|
69
|
-
localStorageMock.getItem.mockClear();
|
|
70
|
-
localStorageMock.setItem.mockClear();
|
|
71
|
-
localStorageMock.removeItem.mockClear();
|
|
72
|
-
setupGlobalMocks();
|
|
73
|
-
});
|
|
74
|
-
(0, vitest_1.describe)('Device ID utilities', () => {
|
|
75
|
-
(0, vitest_1.describe)('getDeviceId', () => {
|
|
76
|
-
(0, vitest_1.it)('returns existing deviceId from localStorage', async () => {
|
|
77
|
-
mockStore['push_device_id'] = 'existing-uuid';
|
|
78
|
-
const { getDeviceId } = await Promise.resolve().then(() => __importStar(require('../../client/deviceId')));
|
|
79
|
-
const result = getDeviceId();
|
|
80
|
-
(0, vitest_1.expect)(result).toBe('existing-uuid');
|
|
81
|
-
});
|
|
82
|
-
(0, vitest_1.it)('generates new UUID if not in localStorage', async () => {
|
|
83
|
-
const { getDeviceId } = await Promise.resolve().then(() => __importStar(require('../../client/deviceId')));
|
|
84
|
-
const result = getDeviceId();
|
|
85
|
-
(0, vitest_1.expect)(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
|
|
86
|
-
});
|
|
87
|
-
(0, vitest_1.it)('saves generated UUID to localStorage', async () => {
|
|
88
|
-
const { getDeviceId } = await Promise.resolve().then(() => __importStar(require('../../client/deviceId')));
|
|
89
|
-
const result = getDeviceId();
|
|
90
|
-
(0, vitest_1.expect)(localStorageMock.setItem).toHaveBeenCalledWith('push_device_id', result);
|
|
91
|
-
});
|
|
92
|
-
(0, vitest_1.it)('returns same ID on repeated calls', async () => {
|
|
93
|
-
const { getDeviceId } = await Promise.resolve().then(() => __importStar(require('../../client/deviceId')));
|
|
94
|
-
const id1 = getDeviceId();
|
|
95
|
-
const id2 = getDeviceId();
|
|
96
|
-
(0, vitest_1.expect)(id1).toBe(id2);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
(0, vitest_1.describe)('clearDeviceId', () => {
|
|
100
|
-
(0, vitest_1.it)('removes deviceId from localStorage', async () => {
|
|
101
|
-
mockStore['push_device_id'] = 'test-uuid';
|
|
102
|
-
const { clearDeviceId } = await Promise.resolve().then(() => __importStar(require('../../client/deviceId')));
|
|
103
|
-
clearDeviceId();
|
|
104
|
-
(0, vitest_1.expect)(localStorageMock.removeItem).toHaveBeenCalledWith('push_device_id');
|
|
105
|
-
});
|
|
106
|
-
(0, vitest_1.it)('allows new ID generation after clear', async () => {
|
|
107
|
-
const { getDeviceId, clearDeviceId } = await Promise.resolve().then(() => __importStar(require('../../client/deviceId')));
|
|
108
|
-
getDeviceId();
|
|
109
|
-
clearDeviceId();
|
|
110
|
-
// Verify removeItem was called
|
|
111
|
-
(0, vitest_1.expect)(localStorageMock.removeItem).toHaveBeenCalledWith('push_device_id');
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
(0, vitest_1.describe)('UUID format', () => {
|
|
115
|
-
(0, vitest_1.it)('generates valid UUID v4 format', async () => {
|
|
116
|
-
const { getDeviceId } = await Promise.resolve().then(() => __importStar(require('../../client/deviceId')));
|
|
117
|
-
const uuid = getDeviceId();
|
|
118
|
-
// UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
|
119
|
-
// where y is 8, 9, a, or b
|
|
120
|
-
(0, vitest_1.expect)(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
|
|
121
|
-
});
|
|
122
|
-
(0, vitest_1.it)('generates unique UUIDs across module resets', async () => {
|
|
123
|
-
const uuids = new Set();
|
|
124
|
-
for (let i = 0; i < 10; i++) {
|
|
125
|
-
vitest_1.vi.resetModules();
|
|
126
|
-
mockStore = {};
|
|
127
|
-
setupGlobalMocks();
|
|
128
|
-
const { getDeviceId } = await Promise.resolve().then(() => __importStar(require('../../client/deviceId')));
|
|
129
|
-
uuids.add(getDeviceId());
|
|
130
|
-
}
|
|
131
|
-
(0, vitest_1.expect)(uuids.size).toBe(10);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,89 +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 atob for Node.js environment
|
|
38
|
-
(0, vitest_1.beforeEach)(() => {
|
|
39
|
-
global.atob = (str) => Buffer.from(str, 'base64').toString('binary');
|
|
40
|
-
});
|
|
41
|
-
(0, vitest_1.describe)('urlBase64ToUint8Array', () => {
|
|
42
|
-
(0, vitest_1.it)('converts standard base64 to Uint8Array', async () => {
|
|
43
|
-
const { urlBase64ToUint8Array } = await Promise.resolve().then(() => __importStar(require('../../client/encoding')));
|
|
44
|
-
const base64 = 'SGVsbG8gV29ybGQ='; // "Hello World"
|
|
45
|
-
const result = urlBase64ToUint8Array(base64);
|
|
46
|
-
(0, vitest_1.expect)(result).toBeInstanceOf(Uint8Array);
|
|
47
|
-
const decoded = new TextDecoder().decode(result);
|
|
48
|
-
(0, vitest_1.expect)(decoded).toBe('Hello World');
|
|
49
|
-
});
|
|
50
|
-
(0, vitest_1.it)('handles URL-safe base64 characters', async () => {
|
|
51
|
-
const { urlBase64ToUint8Array } = await Promise.resolve().then(() => __importStar(require('../../client/encoding')));
|
|
52
|
-
// URL-safe base64 uses - instead of + and _ instead of /
|
|
53
|
-
const urlSafeBase64 = 'SGVsbG8-V29ybGRf'; // Contains - and _
|
|
54
|
-
const result = urlBase64ToUint8Array(urlSafeBase64);
|
|
55
|
-
(0, vitest_1.expect)(result).toBeInstanceOf(Uint8Array);
|
|
56
|
-
(0, vitest_1.expect)(result.length).toBeGreaterThan(0);
|
|
57
|
-
});
|
|
58
|
-
(0, vitest_1.it)('adds padding when needed', async () => {
|
|
59
|
-
const { urlBase64ToUint8Array } = await Promise.resolve().then(() => __importStar(require('../../client/encoding')));
|
|
60
|
-
// Base64 without padding (should be 'ab==' with padding)
|
|
61
|
-
const noPadding = 'YWI';
|
|
62
|
-
const result = urlBase64ToUint8Array(noPadding);
|
|
63
|
-
(0, vitest_1.expect)(result).toBeInstanceOf(Uint8Array);
|
|
64
|
-
const decoded = new TextDecoder().decode(result);
|
|
65
|
-
(0, vitest_1.expect)(decoded).toBe('ab');
|
|
66
|
-
});
|
|
67
|
-
(0, vitest_1.it)('works with VAPID-like keys', async () => {
|
|
68
|
-
const { urlBase64ToUint8Array } = await Promise.resolve().then(() => __importStar(require('../../client/encoding')));
|
|
69
|
-
// Typical VAPID public key format (65 bytes)
|
|
70
|
-
const vapidKey = 'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U';
|
|
71
|
-
const result = urlBase64ToUint8Array(vapidKey);
|
|
72
|
-
(0, vitest_1.expect)(result).toBeInstanceOf(Uint8Array);
|
|
73
|
-
(0, vitest_1.expect)(result.length).toBe(65); // VAPID public keys are 65 bytes
|
|
74
|
-
});
|
|
75
|
-
(0, vitest_1.it)('returns correct byte values', async () => {
|
|
76
|
-
const { urlBase64ToUint8Array } = await Promise.resolve().then(() => __importStar(require('../../client/encoding')));
|
|
77
|
-
// 'AQID' decodes to bytes [1, 2, 3]
|
|
78
|
-
const result = urlBase64ToUint8Array('AQID');
|
|
79
|
-
(0, vitest_1.expect)(result[0]).toBe(1);
|
|
80
|
-
(0, vitest_1.expect)(result[1]).toBe(2);
|
|
81
|
-
(0, vitest_1.expect)(result[2]).toBe(3);
|
|
82
|
-
});
|
|
83
|
-
(0, vitest_1.it)('handles empty string', async () => {
|
|
84
|
-
const { urlBase64ToUint8Array } = await Promise.resolve().then(() => __importStar(require('../../client/encoding')));
|
|
85
|
-
const result = urlBase64ToUint8Array('');
|
|
86
|
-
(0, vitest_1.expect)(result).toBeInstanceOf(Uint8Array);
|
|
87
|
-
(0, vitest_1.expect)(result.length).toBe(0);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|