quodolores
Version:
Monorepo for the Firebase JavaScript SDK
185 lines (166 loc) • 5.54 kB
text/typescript
/**
* @license
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { SubscriptionOptions, TokenDetails } from '../interfaces/token-details';
import {
arrayToBase64,
base64ToArray
} from '../helpers/array-base64-translator';
import { dbGet, dbRemove, dbSet } from './idb-manager';
import {
requestDeleteToken,
requestGetToken,
requestUpdateToken
} from './requests';
import { FirebaseInternalDependencies } from '../interfaces/internal-dependencies';
import { MessagingService } from '../messaging-service';
// UpdateRegistration will be called once every week.
const TOKEN_EXPIRATION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
export async function getTokenInternal(
messaging: MessagingService
): Promise<string> {
const pushSubscription = await getPushSubscription(
messaging.swRegistration!,
messaging.vapidKey!
);
const subscriptionOptions: SubscriptionOptions = {
vapidKey: messaging.vapidKey!,
swScope: messaging.swRegistration!.scope,
endpoint: pushSubscription.endpoint,
auth: arrayToBase64(pushSubscription.getKey('auth')!),
p256dh: arrayToBase64(pushSubscription.getKey('p256dh')!)
};
const tokenDetails = await dbGet(messaging.firebaseDependencies);
if (!tokenDetails) {
// No token, get a new one.
return getNewToken(messaging.firebaseDependencies, subscriptionOptions);
} else if (
!isTokenValid(tokenDetails.subscriptionOptions!, subscriptionOptions)
) {
// Invalid token, get a new one.
try {
await requestDeleteToken(
messaging.firebaseDependencies!,
tokenDetails.token
);
} catch (e) {
// Suppress errors because of #2364
console.warn(e);
}
return getNewToken(messaging.firebaseDependencies!, subscriptionOptions);
} else if (Date.now() >= tokenDetails.createTime + TOKEN_EXPIRATION_MS) {
// Weekly token refresh
return updateToken(messaging, {
token: tokenDetails.token,
createTime: Date.now(),
subscriptionOptions
});
} else {
// Valid token, nothing to do.
return tokenDetails.token;
}
}
/**
* This method deletes the token from the database, unsubscribes the token from FCM, and unregisters
* the push subscription if it exists.
*/
export async function deleteTokenInternal(
messaging: MessagingService
): Promise<boolean> {
const tokenDetails = await dbGet(messaging.firebaseDependencies);
if (tokenDetails) {
await requestDeleteToken(
messaging.firebaseDependencies,
tokenDetails.token
);
await dbRemove(messaging.firebaseDependencies);
}
// Unsubscribe from the push subscription.
const pushSubscription = await messaging.swRegistration!.pushManager.getSubscription();
if (pushSubscription) {
return pushSubscription.unsubscribe();
}
// If there's no SW, consider it a success.
return true;
}
async function updateToken(
messaging: MessagingService,
tokenDetails: TokenDetails
): Promise<string> {
try {
const updatedToken = await requestUpdateToken(
messaging.firebaseDependencies,
tokenDetails
);
const updatedTokenDetails: TokenDetails = {
...tokenDetails,
token: updatedToken,
createTime: Date.now()
};
await dbSet(messaging.firebaseDependencies, updatedTokenDetails);
return updatedToken;
} catch (e) {
await deleteTokenInternal(messaging);
throw e;
}
}
async function getNewToken(
firebaseDependencies: FirebaseInternalDependencies,
subscriptionOptions: SubscriptionOptions
): Promise<string> {
const token = await requestGetToken(
firebaseDependencies,
subscriptionOptions
);
const tokenDetails: TokenDetails = {
token,
createTime: Date.now(),
subscriptionOptions
};
await dbSet(firebaseDependencies, tokenDetails);
return tokenDetails.token;
}
/**
* Gets a PushSubscription for the current user.
*/
async function getPushSubscription(
swRegistration: ServiceWorkerRegistration,
vapidKey: string
): Promise<PushSubscription> {
const subscription = await swRegistration.pushManager.getSubscription();
if (subscription) {
return subscription;
}
return swRegistration.pushManager.subscribe({
userVisibleOnly: true,
// Chrome <= 75 doesn't support base64-encoded VAPID key. For backward compatibility, VAPID key
// submitted to pushManager#subscribe must be of type Uint8Array.
applicationServerKey: base64ToArray(vapidKey)
});
}
/**
* Checks if the saved tokenDetails object matches the configuration provided.
*/
function isTokenValid(
dbOptions: SubscriptionOptions,
currentOptions: SubscriptionOptions
): boolean {
const isVapidKeyEqual = currentOptions.vapidKey === dbOptions.vapidKey;
const isEndpointEqual = currentOptions.endpoint === dbOptions.endpoint;
const isAuthEqual = currentOptions.auth === dbOptions.auth;
const isP256dhEqual = currentOptions.p256dh === dbOptions.p256dh;
return isVapidKeyEqual && isEndpointEqual && isAuthEqual && isP256dhEqual;
}