function arrBufToB64Url(buf: ArrayBuffer) { const b64data = btoa(String.fromCharCode(...new Uint8Array(buf))); return b64data.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); } function stringToBuffer(str: string) { const buffer = new ArrayBuffer(str.length); const ui8 = new Uint8Array(buffer); for (let i = 0, bufLen = str.length; i < bufLen; i++) { ui8[i] = str.charCodeAt(i); } return buffer; } export async function GetAccessToken(env: Env): Promise { const claimSet = btoa( JSON.stringify({ aud: "https://oauth2.googleapis.com/token", exp: Math.floor(Date.now() / 1000) + 120, iat: Math.floor(Date.now() / 1000), iss: env.WORKER_GSERVICEACCOUNT, scope: "https://www.googleapis.com/auth/cloud-platform", }), ) .replaceAll("+", "-") .replaceAll("/", "_") .replaceAll("=", ""); const signingKey = await crypto.subtle.importKey( "pkcs8", stringToBuffer(atob(env.STORAGE_ACCOUNT_KEY.replace(/(\r\n|\n|\r)/gm, ""))), { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["sign"], ); const signature = await crypto.subtle.sign( { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, signingKey, stringToBuffer(`eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.${claimSet}`), ); const assertion = `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.${claimSet}.${arrBufToB64Url( signature, )}`; const tokenRequest = await fetch("https://oauth2.googleapis.com/token", { body: `grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=${assertion}`, headers: { "content-type": "application/x-www-form-urlencoded", }, method: "POST", }); if (!tokenRequest.ok) throw new Error(`Failed to get access token: ${await tokenRequest.text()}`); return ((await tokenRequest.json()) as { [k: string]: any }).access_token; } export async function sendPushNotification( env: Env, title: string, body: string, token: string, ) { const message = { notification: { body, title, }, token, }; const notifResp = await fetch( "https://fcm.googleapis.com/v1/projects/car-crushers-mobile/messages:send", { body: JSON.stringify({ message }), headers: { authorization: `Bearer ${await GetAccessToken(env)}`, "content-type": "application/json", }, method: "POST", }, ); if (!notifResp.ok) throw new Error(await notifResp.text()); }