feat: create lib function to verify FCM token
This commit is contained in:
26
src/libs/fcm/getGoogleAuthToken.ts
Normal file
26
src/libs/fcm/getGoogleAuthToken.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { GoogleToken } from "gtoken";
|
||||
|
||||
export async function getGoogleAuthToken(adminSdkJson: AdminSdkKey) {
|
||||
const { privateKey, clientEmail } = adminSdkJson;
|
||||
|
||||
const gToken = new GoogleToken({
|
||||
key: privateKey,
|
||||
email: clientEmail,
|
||||
scope: ["https://www.googleapis.com/auth/firebase.messaging"],
|
||||
});
|
||||
return gToken.getToken().then((token) => token.access_token);
|
||||
}
|
||||
|
||||
interface AdminSdkKey {
|
||||
type: string;
|
||||
projectID: string;
|
||||
privateKeyID: string;
|
||||
privateKey: string;
|
||||
clientEmail: string;
|
||||
clientID: string;
|
||||
authURI: string;
|
||||
tokenURI: string;
|
||||
authProviderX509CERTURL: string;
|
||||
clientX509CERTURL: string;
|
||||
universeDomain: string;
|
||||
}
|
||||
73
src/libs/fcm/sendFcmMessage.ts
Normal file
73
src/libs/fcm/sendFcmMessage.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { getGoogleAuthToken } from "./getGoogleAuthToken";
|
||||
|
||||
export async function sendFcmMessage(
|
||||
adminSdkJson: string,
|
||||
message: FcmMessagePayload,
|
||||
isOnlyValidatingFcmMessage?: boolean,
|
||||
) {
|
||||
return fetch(
|
||||
"https://fcm.googleapis.com/v1/projects/aniplay-73b59/messages:send",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
message,
|
||||
validate_only: isOnlyValidatingFcmMessage,
|
||||
}),
|
||||
headers: {
|
||||
Authorization: `Bearer ${await getGoogleAuthToken(JSON.parse(adminSdkJson))}`,
|
||||
},
|
||||
},
|
||||
).then((res) => res.json<SendFcmMessageResponse>());
|
||||
}
|
||||
|
||||
type SendFcmMessageResponse =
|
||||
| { error: SendFcmMessageError }
|
||||
| FcmMessagePayload;
|
||||
|
||||
interface SendFcmMessageError {
|
||||
code: number;
|
||||
message: string;
|
||||
status: string;
|
||||
details: Detail[];
|
||||
}
|
||||
|
||||
export interface Detail {
|
||||
type: string;
|
||||
errorCode: string;
|
||||
}
|
||||
|
||||
export type FcmMessagePayload = {
|
||||
name?: string;
|
||||
data?: Record<string, string>;
|
||||
notification?: Notification;
|
||||
android?: Partial<AndroidConfig>;
|
||||
webpush?: {};
|
||||
apns?: {};
|
||||
fcm_options?: {};
|
||||
} & (
|
||||
| { token: string; topic?: never; condition?: never }
|
||||
| { token?: never; topic: string; condition?: never }
|
||||
| { token?: never; topic?: never; condition: string }
|
||||
);
|
||||
|
||||
interface Notification {
|
||||
title: string;
|
||||
body: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
interface AndroidConfig {
|
||||
collapse_key: string;
|
||||
priority: "normal" | "high";
|
||||
/**
|
||||
* How long (in seconds) the message should be kept in FCM storage if the device is offline. The maximum time to live supported is 4 weeks, and the default value is 4 weeks if not set. Set it to 0 if want to send the message immediately. In JSON format, the Duration type is encoded as a string rather than an object, where the string ends in the suffix "s" (indicating seconds) and is preceded by the number of seconds, with nanoseconds expressed as fractional seconds. For example, 3 seconds with 0 nanoseconds should be encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should be expressed in JSON format as "3.000000001s". The ttl will be rounded down to the nearest second.
|
||||
*
|
||||
* A duration in seconds with up to nine fractional digits, ending with 's'. Example: "3.5s".
|
||||
*/
|
||||
ttl: string;
|
||||
restricted_package_name: string;
|
||||
data: Record<string, string>;
|
||||
notification: {};
|
||||
fcm_options: {};
|
||||
direct_boot_ok: boolean;
|
||||
}
|
||||
25
src/libs/fcm/verifyFcm.spec.ts
Normal file
25
src/libs/fcm/verifyFcm.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
|
||||
import { server } from "~/mocks";
|
||||
import "~/mocks/gToken";
|
||||
|
||||
import { verifyFcmToken } from "./verifyFcmToken";
|
||||
|
||||
server.listen();
|
||||
|
||||
describe("verifyFcmToken", () => {
|
||||
it("valid token, returns true", async () => {
|
||||
const token =
|
||||
"7v8sy43aq0re4r8xe7rmr0cn1fsmh6phehnfla2pa73z899zmhyarivmkt4sj6pyv0py43u6p2sim6wz2vg9ypjp9rug1keoth7f6ll3gdvas4q020u3ah51r6bjgn51j6bd92ztmtof3ljpcm8q31njvndy65enm68";
|
||||
const res = await verifyFcmToken(token, '{"clientEmail": "test@test.com"}');
|
||||
|
||||
expect(res).toBeTrue();
|
||||
});
|
||||
|
||||
it("invalid token, returns false", async () => {
|
||||
const token = "abc123";
|
||||
const res = await verifyFcmToken(token, '{"clientEmail": "test@test.com"}');
|
||||
|
||||
expect(res).toBeFalse();
|
||||
});
|
||||
});
|
||||
26
src/libs/fcm/verifyFcmToken.ts
Normal file
26
src/libs/fcm/verifyFcmToken.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { sendFcmMessage } from "./sendFcmMessage";
|
||||
|
||||
export async function verifyFcmToken(
|
||||
token: string,
|
||||
adminSdkJson: string,
|
||||
): Promise<boolean> {
|
||||
return sendFcmMessage(
|
||||
adminSdkJson,
|
||||
{ name: "token_verification", token },
|
||||
true,
|
||||
)
|
||||
.then((response) => {
|
||||
const error = "error" in response ? response.error : undefined;
|
||||
if (error) {
|
||||
console.error("Received error response while validating FCM token");
|
||||
console.error(JSON.stringify(error));
|
||||
}
|
||||
|
||||
return !error;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to verify FCM token", err);
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user