Skip to content

Commit f887962

Browse files
authored
feat: [WPN-16, WPN-15] VAPID JWT expiration claim issue and Test the methods in different environments for compatibility (#29)
1 parent 709c833 commit f887962

4 files changed

Lines changed: 68 additions & 2 deletions

File tree

packages/builder/lib/jwt.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import type { JwtData } from './types.js';
88
* This function takes a JSON Web Key (JWK) and JWT data, encodes them,
99
* and signs the token using the specified algorithm and hash function.
1010
*
11+
* In the Web Push protocol, the VAPID JWT includes an `exp` (expiration) claim
12+
* that specifies the token's validity period. According to the VAPID specification,
13+
* the `exp` value must not exceed 24 hours from the time of the request. If it does,
14+
* the push service (like FCM) will reject the request with a 403 Forbidden error.
15+
*
1116
* @param {JsonWebKey} jwk - The JSON Web Key used for signing the JWT.
1217
* @param {JwtData} jwtData - The data to be included in the JWT payload.
1318
* @returns {Promise<string>} A promise that resolves to the signed JWT as a string.

packages/builder/lib/main.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
// This is the main page
1+
export type {
2+
PushMessage,
3+
PushSubscription,
4+
BuilderOptions,
5+
PushOptions,
6+
} from './types.js';
7+
export { buildPushHTTPRequest } from './request.js';

packages/builder/lib/request.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,46 @@ import { vapidHeaders } from './vapid.js';
1616
* @returns {Promise<{ endpoint: string, body: ArrayBuffer, headers: Record<string, string> | Headers }>} A promise that resolves to an object containing the endpoint, encrypted body, and headers for the push notification.
1717
*
1818
* @throws {Error} Throws an error if the privateJWK is invalid, if the request fails, or if the payload encryption fails.
19+
*
20+
* @example
21+
* // Example usage
22+
* const privateJWK = '{"kty":"EC","crv":"P-256","d":"_eQ..."}'; // Your private VAPID key
23+
*
24+
* const message = {
25+
* payload: {
26+
* title: "New Message",
27+
* body: "You have a new message!",
28+
* icon: "/images/icon.png"
29+
* },
30+
* options: {
31+
* ttl: 3600, // 1 hour in seconds
32+
* urgency: "high",
33+
* topic: "new-messages"
34+
* },
35+
* adminContact: "mailto:admin@example.com"
36+
* };
37+
*
38+
* const subscription = {
39+
* endpoint: "https://fcm.googleapis.com/fcm/send/...",
40+
* keys: {
41+
* p256dh: "BNn5....",
42+
* auth: "tBHI...."
43+
* }
44+
* };
45+
*
46+
* // Build the request
47+
* const request = await buildPushHTTPRequest({
48+
* privateJWK,
49+
* message,
50+
* subscription
51+
* });
52+
*
53+
* // Send the push notification
54+
* const response = await fetch(request.endpoint, {
55+
* method: 'POST',
56+
* headers: request.headers,
57+
* body: request.body
58+
* });
1959
*/
2060
export async function buildPushHTTPRequest({
2161
privateJWK,
@@ -30,11 +70,17 @@ export async function buildPushHTTPRequest({
3070
const jwk: JsonWebKey =
3171
typeof privateJWK === 'string' ? JSON.parse(privateJWK) : privateJWK;
3272

73+
const MAX_TTL = 24 * 60 * 60;
74+
75+
if (message.options?.ttl && message.options.ttl > MAX_TTL) {
76+
throw new Error('TTL must be less than 24 hours');
77+
}
78+
3379
// Determine the time-to-live (TTL) for the push notification
3480
const ttl =
3581
message.options?.ttl && message.options.ttl > 0
3682
? message.options.ttl
37-
: 24 * 60 * 60; // Default to 24 hours
83+
: MAX_TTL; // Default to 24 hours
3884

3985
// Create the JWT payload
4086
const jwt = {

packages/builder/lib/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ export interface PushMessage<T extends Jsonifiable = Jsonifiable> {
4343
options?: RequireAtLeastOne<{
4444
/**
4545
* The time-to-live for the push message in seconds.
46+
* Must be a positive number greater than 0.
47+
* Default value is 24 * 60 * 60 (24 hours).
48+
* If set to 0, undefined, or a negative value, it will default to 24 hours.
49+
* The VAPID JWT expiration claim (`exp`) must not exceed 24 hours from the time of the request.
50+
* If it does, the push service (like FCM) will reject the request with a 403 Forbidden error.
4651
*/
4752
ttl?: PushOptions['ttl'];
4853

@@ -70,6 +75,8 @@ export interface JwtData {
7075
/**
7176
* The expiration time of the JWT, in seconds.
7277
* This prevents reuse of the JWT after it has expired.
78+
* The `exp` value must not exceed 24 hours from the time of the request.
79+
* If it does, the push service (like FCM) will reject the request with a 403 Forbidden error.
7380
*/
7481
exp: number;
7582

@@ -104,6 +111,8 @@ export interface PushOptions {
104111
* Must be a positive number greater than 0.
105112
* Default value is 24 * 60 * 60 (24 hours).
106113
* If set to 0, undefined, or a negative value, it will default to 24 hours.
114+
* The VAPID JWT expiration claim (`exp`) must not exceed 24 hours from the time of the request.
115+
* If it does, the push service (like FCM) will reject the request with a 403 Forbidden error.
107116
*/
108117
ttl: number;
109118

0 commit comments

Comments
 (0)