Firebase 云消息传递服务中,服务器密钥的 Base64 编码

10

我已经在这个问题上卡了三天,搜索了谷歌,但是没有成功。我按照推送通知示例中给出的指示进行操作。但是当我尝试实现它时,出现了这个讨厌的错误。

Uncaught (in promise) DOMException: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.

我发现冒号(:)在Base64字符串中是不允许的,但在Firebase云消息传递选项卡中给出的服务器密钥中存在。
AAAA-8MWvk0:APA91bHmU8XL-rzOn9SPbpG98NSqAJcuhYBBh7oze_XhprBpK7Q9PPWm3PwBo6Llxby4zjhVtgvKPIaSAQGp-8RfMV10_d1GekzICrVX9oYO8pi6dOM4VTp52CCAzug6NYIa10tNddsgE2P5QowGAYcnRHxLkrHXsw

这段文字中包含一个冒号(别担心,这只是一个测试应用程序,所以没有隐私问题)。

当我尝试使用传统的服务器密钥时,它会抛出错误。我也尝试了在Firebase中提供的其他密钥,但都没有成功。请告诉我应该使用哪个服务器密钥以及如何使用?

我附上了我的代码片段,它实际上执行了推送订阅。

 const API_KEY = "AIzaSyByIOl-mW0pu8SEXFeutB8jq59hhiau0wI";
 var GCM_ENDPOINT = 'https://fcm.googleapis.com/fcm/send';
 const legacy  = 'AIzaSyDGF8t125bJ4wBvYn_UdRewkTxHGr7KpH8';
 const applicationServerPublicKey = 'AAAA-8MWvk0APA91bHmU8XL-rzOn9SPbpG98NSqAJcuhYBBh7oze_XhprBpK7Q9PPWm3PwBo6Llxby4zjhVtgvKPIaSAQGp-8RfMV10_d1GekzICrVX9oYO8pi6dOM4VTp52CCAzug6NYIa10tNddsgE2P5QowGAYcnRHxLkrHXsw';

function urlB64ToUint8Array(base64String) {
 const padding = '='.repeat((4 - base64String.length % 4) % 4);
 const base64 = (base64String + padding)
 .replace(/\-/g, '+')
 .replace(/_/g, '/');
 console.log(base64);
 const rawData = window.atob(base64);
 console.log(rawData);
 const outputArray = new Uint8Array(rawData.length);

 for (let i = 0; i < rawData.length; ++i) {
  outputArray[i] = rawData.charCodeAt(i);
 }
 return outputArray;
}

function endpointWorkaround(pushSubscription) {
 // Make sure we only mess with GCM
 if(pushSubscription.endpoint.indexOf('https://fcm.googleapis.com/fcm/send') !== 0) {
  return pushSubscription.endpoint;
 }

 var mergedEndpoint = pushSubscription.endpoint;
 // Chrome 42 + 43 will not have the subscriptionId attached
 // to the endpoint.
 if (pushSubscription.subscriptionId &&
 pushSubscription.endpoint.indexOf(pushSubscription.subscriptionId) === -1)    {
  // Handle version 42 where you have separate subId and Endpoint
  mergedEndpoint = pushSubscription.endpoint + '/' +
  pushSubscription.subscriptionId;
 }
 return mergedEndpoint;
}

function sendSubscriptionToServer(subscription) {
 // TODO: Send the subscription.endpoint
 // to your server and save it to send a
 // push message at a later date
 //
 // For compatibly of Chrome 43, get the endpoint via
 // endpointWorkaround(subscription)
 console.log('TODO: Implement sendSubscriptionToServer()',    JSON.stringify(subscription));

 var mergedEndpoint = endpointWorkaround(subscription);

 // This is just for demo purposes / an easy to test by
 // generating the appropriate cURL command
 var temp = showCurlCommand(mergedEndpoint);
 return temp;
}

// NOTE: This code is only suitable for GCM endpoints,
// When another browser has a working version, alter
// this to send a PUSH request directly to the endpoint
function showCurlCommand(mergedEndpoint) {
 // The curl command to trigger a push message straight from GCM
 if (mergedEndpoint.indexOf(GCM_ENDPOINT) !== 0) {
  console.warn('This browser isn\'t currently ' + 'supported for this demo');
  return;
 }

 var endpointSections = mergedEndpoint.split('/');
 var subscriptionId = endpointSections[endpointSections.length - 1];

 var curlCommand = 'curl --header "Authorization: key=' + API_KEY + '" --header Content-Type:"application/json" ' + GCM_ENDPOINT + ' -d "{\\"registration_ids\\":[\\"' + subscriptionId + '\\"]}"';

 console.log(curlCommand);
 return subscriptionId;
}

function initialiseState() {
 // Are Notifications supported in the service worker?
 if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
  console.warn('Notifications aren\'t supported.');
  return;
 }

 // Check the current Notification permission.
 // If its denied, it's a permanent block until the
 // user changes the permission
 if (Notification.permission === 'denied') {
  console.warn('The user has blocked notifications.');
  return;
 }

 // Check if push messaging is supported
 if (!('PushManager' in window)) {
  console.warn('Push messaging isn\'t supported.');
  return;
 }
 var prom = new Promise(function(resolve, reject) {
  navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
    // Do we already have a push message subscription?
serviceWorkerRegistration.pushManager.getSubscription().then(function(subscription) {
            // Enable any UI which subscribes / unsubscribes from
            // push messages.
            // var pushButton = document.querySelector('.js-push-button');
            // pushButton.disabled = false;

            if (!subscription) {
                // We aren’t subscribed to push, so set UI
                // to allow the user to enable push
        subscribe();
                return;
            }

            // Keep your server in sync with the latest subscription
      var temp = sendSubscriptionToServer(subscription);
      if(temp){
        resolve(temp);
      }else{
        reject("Oops!")
      }

            // Set your UI to show they have subscribed for
            // push messages
            // pushButton.textContent = 'Disable Push Messages';
            // isPushEnabled = true;
        })
        .catch(function(err) {
            console.error('Error during getSubscription()', err);
      reject(err);
        });
});
});
return prom;
}

function unsubscribe() {
 // var pushButton = document.querySelector('.js-push-button');
 // pushButton.disabled = true;

 navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
  // To unsubscribe from push messaging, you need get the
  // subcription object, which you can call unsubscribe() on.
  serviceWorkerRegistration.pushManager.getSubscription().then(
  function(pushSubscription) {
   // Check we have a subscription to unsubscribe
   if (!pushSubscription) {
    // No subscription object, so set the state
    // to allow the user to subscribe to push
    //  isPushEnabled = false;
    //  pushButton.disabled = false;
    //  pushButton.textContent = 'Enable Push Messages';
    return;
   }

   // TODO: Make a request to your server to remove
   // the users data from your data store so you
   // don't attempt to send them push messages anymore

   // We have a subcription, so call unsubscribe on it
   pushSubscription.unsubscribe().then(function() {
    //  pushButton.disabled = false;
    //  pushButton.textContent = 'Enable Push Messages';
    //  isPushEnabled = false;
   }).catch(function(e) {
     // We failed to unsubscribe, this can lead to
     // an unusual state, so may be best to remove
     // the subscription id from your data store and
     // inform the user that you disabled push

     console.log('Unsubscription error: ', e);
     //  pushButton.disabled = false;
   });
 }).catch(function(e) {
   console.error('Error thrown while unsubscribing from ' + 'push messaging.', e);
 });
});
}

function subscribe() {
 // Disable the button so it can't be changed while
 // we process the permission request
 // var pushButton = document.querySelector('.js-push-button');
 // pushButton.disabled = true;

 navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
  const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
  serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true,  applicationServerKey: applicationServerKey})
 .then(function(subscription) {
  console.log(subscription);
  // The subscription was successful
  //  isPushEnabled = true;
  //  pushButton.textContent = 'Disable Push Messages';
  //  pushButton.disabled = false;

  // TODO: Send the subscription subscription.endpoint
  // to your server and save it to send a push message
  // at a later date
  return sendSubscriptionToServer(subscription);
 })
 .catch(function(e) {
  if (Notification.permission === 'denied') {
   // The user denied the notification permission which
   // means we failed to subscribe and the user will need
   // to manually change the notification permission to
   // subscribe to push messages
   console.log('Permission for Notifications was denied');
   //  pushButton.disabled = true;
  } else {
   // A problem occurred with the subscription, this can
   // often be down to an issue or lack of the gcm_sender_id
   // and / or gcm_user_visible_only
   console.log('Unable to subscribe to push.', e);
   //  pushButton.disabled = false;
   //  pushButton.textContent = 'Enable Push Messages';
  }
 });
});
}

1
我不熟悉如何在Web应用程序中使用推送通知,但是在您指出的教程中,它并没有真正说明applicationServerPublicKey应该与Firebase控制台中的服务器密钥相同。他们只指出您应该使用此页面生成一个。 - AL.
1
我知道,但是我的生产应用程序使用Firebase的推送服务,因此所有推送消息都在那里生成。因此,如果我想要使用Firebase,那么我应该替换哪个密钥来代替这里的applicationServerPublickey - Tamoghna
2个回答

8

很遗憾,这是Chrome中一个令人困惑的问题。

在最基本的层面上:Web Push与Firebase Messaging for the web是完全分开的

Web Push和应用服务器密钥

Web Push需要一个应用服务器密钥,这是你在服务工作者推送管理器上订阅时所传递的:

pushManager.subscribe({userVisibleOnly: true, applicationServerKey: MY_KEY});

在 Web 推送中,`applicationServerKey` 需要是一个 Uint8Array(即一个长度为 65 字节的数组)。这个密钥可以按照您喜欢的方式生成,只需确保将私钥保持私有,并在 Web 应用程序中使用公钥。
在我编写的代码实验室中,我指示您从以下网址获取密钥:https://web-push-codelab.appspot.com/ 此页面会即时生成一组应用服务器密钥,并将结果存储在本地存储中(因此您可以使用相同的页面发送推送消息——需要该应用服务器密钥)。
您也可以轻松地从像 Web-Push 的 Node 命令行界面 这样的工具中获取密钥。
Firebase 服务端密钥
Firebase Web 消息传递 SDK 使用服务工作者在 Web 上实现推送,但它会为您管理应用服务器密钥。原因是 Firebase 消息传递 SDK 为您设置了使用 Firebase 云消息 API 触发推送消息,而不是您需要在通过的代码实验室中使用 Web 推送协议。
Firebase 控制台中的 "服务器密钥" 是您在触发推送消息时要使用的密钥,它是 "Authorization" 头部。
https://fcm.googleapis.com/fcm/send
Content-Type: application/json
Authorization: key=<YOUR SERVER KEY HERE>

摘要

  • Firebase Messaging为Web推送提供了自己的SDK,如果您使用它,则应遵循其指南并使用其服务器端API。
  • Web Push(开放式Web标准)与Firebase(或任何其他推送服务)没有直接依赖关系,因此您可以创建特定格式的密钥,并使用Web Push协议触发推送消息。(Chrome在幕后使用Firebase,但作为Web Push站点的开发人员,您无需对Firebase进行任何操作)。

您的指令链接失效了。 - Codedreamer
抱歉,看起来应用引擎出了问题。 - Matt Gaunt
能否使用原生的Web推送API与FCM一起使用,而不是使用FCM JavaScript SDK?我想所有原生API需要的只是FCM公钥,而FCM允许您下载。这正确吗?@Matt Gaunt - david_adler
https://web-push-codelab.appspot.com/已经失效。404错误。 - Franva
@MattGaunt,也许你可以验证一下我上面的答案,因为你比我更有权威性! - david_adler

-1

了解FCM Push服务和FCM Firebase产品之间的区别非常重要。

FCM Push服务

  • 负责向Chrome浏览器传递推送消息。推送消息到达Chrome浏览器的唯一方式是通过FCM推送服务。
  • FCM推送服务与Mozilla推送服务并存。每个浏览器都有自己的推送服务。
  • 推送服务实现了Web Push协议。推送服务的Chrome浏览器组件向推送服务的后端组件发起请求,后端组件与浏览器维护着一个开放的HTTP2连接,并使用HTTP2服务器推送将推送消息传递给浏览器。
  • 在Chrome上调用pushManager.subscribe()会给出一个形如https://fcm.googleapis.com/fcm/send/f4wa5jqguZI:APA91bEI0J6gtg3NYqdB_NhB-YWUPxYVdWkK25h0X2nRogeeMYW3INS71hqyAOG06bvyqwbGhukl9qiJJHU9IalxcfLOE47PN70qwKwcBgbDSEW-NFYkQqv3Uj-qANZT4zQ3kG76asdf的端点。

FCM Firebase 产品

  • Firebase 产品使用 FCM 推送服务向 Chrome 浏览器发送推送消息。Firebase 产品可以与其他推送服务和其他浏览器交互。Firebase 产品甚至可以通过 APNS 向 iOS 应用程序发送推送消息,该协议不基于 Web 推送协议。
  • 调用 firebase.messaging().getToken() 将为您提供一个形式为 eyFOxmzsVFGTwi80wpAcR8:APA91bEtIDf2SH4D1CNcQ2M3kX9-i5nHsbsTXcvO7UJaWteXQz7wxTxNvpmHewMpaMeXf90_BsAJblVpsVoQAUT-CIxTUAkJOr4X3LEp0SbK8E1_iRrpd55e5giP3-r3jDN0rxDuasdf 的令牌(有时称为 registrationID)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接