如何从零到一实现个完整webPush推送服务

前言

经常做海外项目或者做过海外项目的伙伴应该多多少少听过 webPush 也就是:消息推送通知,它的优势在于提高用户参与度、通过向用户发送实时通知,可以提醒他们查看新内容或完成某些操作,同时支持离线通知,即使在用户的设备离线或者浏览器未打开的情况下,也可以通过Service Worker在设备重新连接或浏览器打开时发送通知。

故此国外很多的头部网址都有集成了此功能,向大家比较了解的可能就是:YouTubetwitterfacebook等,下面是我简单截了一下twitter推送消息,供大家参考:

大家可能有个疑问?为什么这么好的功能在国内做的很少呢?

  1. 技术限制:在国内,由于网络环境和网络规则的特殊性,使用 web push 技术的应用受到了一定的限制。例如,一些浏览器可能不支持或者只部分支持 web push 功能,这就限制了这项技术的普及和使用。
  2. 网络安全:web push 技术需要浏览器与服务器之间建立长连接来实时传递推送信息,这可能会涉及到一些网络安全风险。为了保护用户隐私和数据安全,国内政府对于个人信息保护有较为严格的监管要求,这也对 web push 的使用产生了一定的影响。
  3. 用户习惯:在国内,短信和APP推送已经成为用户获取信息的主要方式。相比之下,web push 相对较新,用户对于这种推送方式的接受度和使用习惯还不够普及。
  4. 推广力度不足:由于种种原因,国内对于 web push 的推广力度相对较小。相比之下,国外市场对于 web push 的应用更加广泛,也更容易找到相关的教程和案例。

分析完了以上的利弊相信大家也对这个技术有些许好奇 WebPush到底是个什么呢?别急,下面让我带领大家一起开始。

开始之前

当你文章阅读到此的话 我已经假定你的有PWAService Worker 的基本知识,这篇文章是之前《两句话带你入门webPush》的延伸,感兴趣可以花5分钟阅读一下~

What is webPush?

Web Push(网页推送)是一种在Web浏览器中向用户发送实时消息的通信技术。它允许网站向用户发送通知,即使用户没有打开或使用网站。当用户同意接收通知后,网站可以通过Web Push服务向用户的设备发送推送消息,这些消息将显示在用户的操作系统、浏览器或移动设备上,以吸引用户的注意并提供实时信息。

Web Push技术基于浏览器提供的Push API和推送服务提供商(如Google的FCM和Mozilla的Push Service)的支持。通过Push API,网站可以向用户的设备注册并请求发送推送消息。

作用和优势

Web Push是一种允许服务器向用户的设备发送通知消息的技术,即使在用户没有打开应用的情况下也可以发送。这种技术主要依赖于Service Worker:一个在浏览器后台运行的脚本,可以在用户没有打开网页的情况下响应事件。

主要优势包括:

  1. 提高用户参与度:通过向用户发送实时通知,可以提醒他们查看新内容或完成某些操作,从而提高用户的参与度和活跃度。
  2. 提供更好的用户体验:用户可以选择订阅或取消订阅通知,这意味着他们只会收到他们感兴趣的通知,从而提供了更个性化的用户体验。
  3. 支持离线通知:即使在用户的设备离线或者浏览器未打开的情况下,也可以通过Service Worker在设备重新连接或浏览器打开时发送通知。
  4. 跨平台:Web Push技术支持多种浏览器和操作系统,包括Chrome、Firefox、Opera等,可以在桌面和移动设备上使用。

需要注意: 虽然Web Push有很多优势,但也有一些限制,经过测试在iOS设备上的Safari浏览器中,目前还不支持Web Push通知。

Web Push推送原理

在整个push的过程中其实有三个角色分别是:

  • 浏览器: 就是我们的客户端
  • Push Service :专门的Push服务,你可以认为是一个第三方服务,目前所有接入推送消息的都要遵循web-push的协议
  • 后端服务: 这里指的是Node服务

整理实现如下:

通过上图我们简要整理一下推送消息的流程如下:

  1. 在浏览器判断是否支持serviceWorker对象;
  2. 注册serviceWorker通过pushManager.subscribe在浏览器弹出,询问用户是否允许接受消息通知 注意:用户如果选择拒绝,不可以重新调起
  3. 点击允许会生成一个subscription(订阅)的标志信息,保存到服务端,用来发push给当前用户
  4. 服务器通过接收的subscription(订阅)信息通过 web-push 集成包向 FCM 发送消息
  5. FCM(Firebase Cloud Messaging)会下发到对应的浏览器,
  6. 浏览器触发Service Worker的push事件,向用户弹出弹出消息

需要注意的是: 推送消息仅支持HTTPS或者localhost

Why web-push.js

Web 推送要求从后端触发的推送消息通过Web 推送协议完成 ,如果您想通过推送消息发送数据,则还必须根据Web 推送消息加密规范对该数据进行加密。 该模块可以轻松发送消息,并且还将处理对依赖 GCM 进行消息发送/传递的浏览器的遗留支持。

安装

javascript 复制代码
npm install web-push -g

生成秘钥

javascript 复制代码
web-push generate-vapid-keys --json

用例

javascript 复制代码
const webpush = require('web-push');

// VAPID keys should be generated only once.
const vapidKeys = webpush.generateVAPIDKeys();

webpush.setGCMAPIKey('<Your GCM API Key Here>');
webpush.setVapidDetails(
  'mailto:example@yourdomain.org',
  vapidKeys.publicKey,
  vapidKeys.privateKey
);

// This is the same output of calling JSON.stringify on a PushSubscription
const pushSubscription = {
  endpoint: '.....',
  keys: {
    auth: '.....',
    p256dh: '.....'
  }
};

webpush.sendNotification(pushSubscription, 'Your Push Payload Text');

通过web-push返回唯一的标识符

  1. endpoint: 推送服务端点,即推送服务的地址。通常是一个唯一的URL,用于向推送服务发送推送消息。
  2. keys.p256dh: 这是一个公钥,由客户端生成并发送到服务器。服务器使用此公钥加密推送消息,然后将加密的消息发送回客户端。客户端使用其私钥解密这些消息。这个公钥是使用椭圆曲线 Diffie-Hellman (ECDH) 密钥交换协议生成的,用于确保只有拥有相应私钥的接收方才能解密和阅读消息。
  3. keys.auth: 这是一个密钥,由客户端生成并发送到服务器。它用于生成一个加密密钥和一个认证密钥。这两个密钥都用于加密和解密推送消息。加密密钥用于加密消息内容,而认证密钥用于生成一个 HMAC,以确保消息在传输过程中没有被篡改。这个密钥并不直接用于加密,而是用于生成用于加密和认证的密钥。
  4. expirationTime : 与推送订阅关联的订阅过期时间的A DOMHighResTimeStamp(如果有),否则为 null。

实现一个完整的消息推送

说完了以上的准备条件,下面让我们实现一下整个的推送流程。 基于域名的限制大家可以本地模拟 或者上传到自己的FDS阿里云的OSS 中,具体以自己实际情况而定,接下来我们从三个层面进行搭建一个简易的web push 平台,分为 :前端(消息模板)、后端(Node)、CMS

实现流程如下:

开发环境:

Node 版本: >=18.0.0

消息模板

下图是依次是:PC(macBook)、H5(安卓)、PWA(IOS);

TIPS: 这里可能很多人好奇为什么需要注明不同分辨率的系统,很简单:有兼容; 具体可以往下看有标题注明

经过测试:在macBook、Windows 等电脑上是不存在兼容性的,当然主要还是取决你的浏览器的版本,在移动端中Android表现良好 不管是通过chrome浏览器打开还是生成PWA都是支持,但是在IOS中只能在PWA中支持,这点需要注意。

界面如下

为了演示,图中使用的是以浏览器唯一的标识符 将当前的浏览器信息进行了MD5 加密,实际在项目中可能需要浏览器指纹 来判断唯一性、或者生成业务的唯一的标识符等。

安装依赖:

项目框架采用Vite搭建,相关安装请参考Vite官方文档,相关依赖如下:

javascript 复制代码
//  node >= 18.0.0
yarn add axios crypto-js dayjs sass -S

src/views/HomeView.vue

编写一个简单的模板文件用于接收push 消息,详细的代码如下:

javascript 复制代码
<template>
  <div class="mian">
    <h2>接口传入的web-push 消息</h2>

    <h3>UUID:{{ uuid }}</h3>
    <h4>UUID生成唯一标识符的规则是:MD5(navigator.userAgent)</h4>
    <br />
    <button @click="handleGetAuth">获取权限</button>
    <button @click="handleTestMsg">发送测试消息</button>
  </div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import MD5 from 'crypto-js/md5'
import { runWebPush } from '@/utils/pushMessage'

const uuid = ref('')
uuid.value = MD5(navigator.userAgent)

onMounted(() => {
  watchQueryNotificationPermission()
})

// 监听授权状态
function watchQueryNotificationPermission() {
  const isAllow = queryNotificationPermission()
  if (isAllow === 'default') {
    console.log('初始状态')
  } else if (isAllow === 'granted') {
    runWebPush()
  } else {
    alert('消息通知已关闭')
  }
}

// 查询是否授权消息通知
// https://developer.mozilla.org/en-US/docs/Web/API/Notification/permission_static
function queryNotificationPermission() {
  // granted || denied || default
  return Notification?.permission || null
}

// 请求系统授权
function requestPushNotificationPermission() {
  return new Promise((resolve) => {
    Notification.requestPermission().then(function (permission) {
      if (permission === 'granted') {
        resolve(true)
      } else {
        resolve(false)
      }
    })
  })
}

// 调起授权
async function handleGetAuth() {
  const isAllow = await requestPushNotificationPermission()
  isAllow && runWebPush()
}

// 发送测试消息
function handleTestMsg() {
  navigator.serviceWorker.ready
    .then(function (serviceWorker) {
      const title = '我是测试'
      const options = {
        body: '这是一条测试消息'
      }
      serviceWorker.showNotification(title, options)
    })
    .catch(function (exception) {
      alert(exception)
    })
}
</script>

<style lang="scss" scoped>
.mian {
  h2 {
    margin-bottom: 20px;
  }

  h4 {
    text-decoration: green wavy underline;
  }

  button {
    margin-right: 30px;
    padding: 20px;
  }
}
</style>

public/worker.js

新建sw文件,用于接收服务器推送的消息,需要注意:是推送的push属于浏览器的行为,在Network可看不到,可以通过浏览器的调试行为来进行mock,如下图

对应的就是在sw的push中获取的 const data = e.data.json() 对象,在执行notificationclick 点击卡片的时候需要传递的参数可以通过data = { links:"xxx" } 的形式传递过来。mcok数据如下,

javascript 复制代码
 {
    "links": "https://www.baidu.com/",
    "title": "我是接口测试11",
    "icons": "https://xxx.png",
    "body": "我是测试内容"
} 

详细代码如下:

javascript 复制代码
const MESSAGE_COMMUNICATION = 'web-push-message'

// https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API
const broadcast = new BroadcastChannel(MESSAGE_COMMUNICATION) // 广播通道
let time = null

self.addEventListener('install', function () {
  self.skipWaiting()
})

self.addEventListener('notificationclick', function (event) {
  const notification = event.notification
  broadcast.postMessage({ type: 'NOTI_FICATION_CLICK' })
  notification.close()
  event.waitUntil(clients.openWindow(notification.data.links))
})

// 相关的配置:https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification
self.addEventListener('push', function (e) {
  if (!e.data) return
  const data = e.data.json()
  time = setTimeout(() => {
    broadcast.postMessage({ type: 'PUSH_MESSAGES' })
    clearTimeout(time)
  }, 500)

  e.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: data.icons,
      data: {
        links: data.links
      }
    })
  )
})

后端服务(Node)

服务器的搭建采用的是nest-cli,想要深入了解的同学可以参考

安装mysql依赖:

javascript 复制代码
$ yarn add  @nestjs/typeorm typeorm mysql2 web-push -S

项目目录如下:

javascript 复制代码
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── config
│   ├── index.ts  
│   └── mysql.ts
├── main.ts // 入口文件
└── push  // 发送的push模块
    ├── dto
    ├── push.controller.ts
    ├── push.entity.ts
    ├── push.interface.ts
    ├── push.module.ts
    └── push.service.ts

src/push/push.module.ts

javascript 复制代码
import { Module } from '@nestjs/common';
import { PushService } from './push.service';
import { PushController } from './push.controller';
import { newTable } from './push.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ScheduleModule } from '@nestjs/schedule';

@Module({
  imports: [TypeOrmModule.forFeature([newTable]), ScheduleModule.forRoot()],
  controllers: [PushController],
  providers: [PushService],
})
export class PushModule {}

src/push/push.controller.ts

在当前push的模板中提供了以下四个接口:

javascript 复制代码
import { Controller, Get, Post, Body, HttpCode, Query } from '@nestjs/common';
import { PushService } from './push.service';
@Controller('push')
export class PushController {
  constructor(private readonly pushService: PushService) {}

  //接收端=> H5获取私钥
  @Post('/get-keys')
  @HttpCode(200)
  async getKeys(@Body() Body) {
    return await this.pushService.getKeys(Body);
  }

  // 接收端 => 推送消息端内消息凭证
  @Post('/push-endpoint')
  @HttpCode(200)
  async pushEndpoint(@Body() Body) {
    return await this.pushService.pushEndpoint(Body);
  }

  // csm查询用户信息
  @Post('/notice-users')
  @HttpCode(200)
  async noticeUsers(@Body() body) {
    return await this.pushService.queryNoticeUsers(body);
  }

  // csm配置消息
  @Post('/config-message')
  @HttpCode(200)
  async configSubscribe(@Body() body) {
    return await this.pushService.configSubscribe(body);
  }

}

src/push/push.service.ts

通过定义的方式进行mysql的增删改查来完成针对某个用户的精确推送,代码如下:

javascript 复制代码
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { newTable } from './push.entity';
import { GenerateKeyType } from './push.interface';
import { SchedulerRegistry } from '@nestjs/schedule';

// !重要 核心推送
import * as webpush from 'web-push';

@Injectable()
export class PushService {
  constructor(
    protected schedulerRegistry: SchedulerRegistry,
    @InjectRepository(newTable)
    private readonly NewTableRepository: Repository<newTable>,
  ) {
  }

  // 每次生成新的新的秘钥
  refreshKeys(): GenerateKeyType {
    const keys = webpush.generateVAPIDKeys();
    return keys;
  }

  // H5 获取私钥
  async getKeys(body): Promise<any> {
    const { browserName = '' } = body;

    if (!browserName) {
      return {
        code: 200,
        data: {},
        msg: '不能传递空值',
      };
    }
    const users: any = await this.NewTableRepository.find({
      where: { name: browserName },
    });

    // 查询SQL的库是否存在
    if (users && users.length === 0) {
      // 未新用户生成唯一的key
      const { publicKey, privateKey } = this.refreshKeys();
      const _users = {
        publicKey,
        privateKey,
        name: browserName,
        infoAuthUa: '',
        title: '',
        content: '',
        icons: '',
      };

      try {
        await this.NewTableRepository.save(_users);
        return {
          code: 200,
          data: {
            publicKey,
            isNew: true,
          },
          msg: 'success',
        };
      } catch (error) {
        return {
          code: 500,
          data: JSON.stringify(error),
          msg: 'error',
        };
      }
    } else {
      return {
        code: 200,
        data: {
          publicKey: users[0].publicKey,
          isNew: false,
        },
        msg: 'success',
      };
    }
  }

  // 保存H5的秘钥
  async pushEndpoint(body): Promise<any> {
    const { browserName = '', infoAuthUa = '' } = body;

    if (!browserName) {
      return {
        code: 200,
        data: {},
        msg: '浏览器唯一标识不能传递为空',
      };
    }

    const users: newTable = await this.NewTableRepository.findOne({
      where: { name: browserName },
    });

    if (!users) {
      return {
        code: 200,
        data: {},
        msg: '传递的uuid不存在',
      };
    }

    // 更新当前浏览器的推送标识
    users.infoAuthUa = infoAuthUa;

    try {
      await this.NewTableRepository.save(users);
      return {
        code: 200,
        data: {},
        msg: 'success',
      };
    } catch (error) {
      return {
        code: 500,
        data: JSON.stringify(error),
        msg: 'error',
      };
    }
  }

  // csm查询用户信息
  async queryNoticeUsers(body: any): Promise<any> {
    const users: newTable[] = await this.NewTableRepository.find();

    const result = users.map((item) => ({
      name: item.name,
      id: item.name,
    }));

    const userlist = result && result.length > 0 ? result : [];

    return {
      code: 200,
      data: {
        userlist,
      },
      msg: 'success',
    };
  }

  // csm配置消息
  async configSubscribe(body): Promise<any> {
    const { title = '', content = '', uuids = [], icons = ' ' } = body;

    if (uuids && uuids.length === 0) {
      return {
        code: 500,
        msg: '请上传用户ID',
      };
    }

    if (!(title && content)) {
      return {
        code: 500,
        msg: 'title、content 不能为空',
      };
    }

    try {
      // 更新传入的CMS的数据
      await this.NewTableRepository.createQueryBuilder('new_table')
        .update(newTable)
        .set({ title, content, icons })
        .where('new_table.name In(:...name)', {
          name: uuids,
        })
        .execute();

      // 查询全部用户的信息
      const usersList = await this.NewTableRepository.createQueryBuilder(
        'new_table',
      )
        .where('new_table.name In(:...name)', {
          name: uuids,
        })
        .getMany();

      if (usersList && usersList.length === 0) {
        return {
          code: 500,
          msg: '配置的用户ID无效、数据未找到',
        };
      }

      return await this.allSendMessage(usersList);
    } catch (error) {
      return {
        code: 500,
        data: JSON.stringify(error),
        msg: '数据更新有问题',
      };
    }
  }

  // 批量发送消息
  async allSendMessage(usersList) {
    for (const k of usersList) {
      const {
        title,
        content,
        publicKey,
        privateKey,
        icons,
        infoAuthUa: clientAuth,
      } = k;
      const message = {
        title,
        content,
        publicKey,
        privateKey,
        clientAuth,
        icons,
      };

      return await this.receiveMessages(message);
    }
  }

  // H5接收消息
  async receiveMessages(data) {
    try {
      const {
        title = '',
        content = '',
        publicKey = '',
        privateKey = '',
        icons = '',
        links = '',
        clientAuth = '',
      } = data;

      const vapidDetails = {
        subject: 'mailto:123@163.com',
        publicKey,
        privateKey,
      };

      const payload = JSON.stringify({
        title,
        body: content,
        icons,
        links,
      });

      const mds =
        typeof clientAuth === 'string' ? JSON.parse(clientAuth) : clientAuth;

      const result = await webpush.sendNotification(mds, payload, {
        vapidDetails,
      });

      return {
        code: 200,
        data: JSON.stringify(result),
        msg: 'success',
      };
    } catch (error) {
      console.log('发送消息失败', error);
      return {
        code: 500,
        data: error,
        msg: 'error',
      };
    }
  }
}

链接数据库

数据库名称:push_rule

javascript 复制代码
// 本地数据库
export const mysqlconfig: any = {
  // 连接数据库
  type: 'mysql', // 数据库类型
  host: 'localhost', // 数据库ip地址
  port: 3306, // 端口
  username: 'root', // 登录名
  password: 'test', // 密码
  database: 'push_rule', // 数据库名称
  synchronize: true, // 定义数据库表结构与实体类字段同步(这里一旦数据库少了字段就会自动加入,根据需要来使用)
  autoLoadEntities: true, // 自动加载实体
};

创建表结构

表名:new_table

javascript 复制代码
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

/**
 * 常用选项参数:
  @Column({
      type: 'varchar',    列的数据类型,参考mysql
      name: 'password',   数据库表中的列名,string,如果和装饰的字段是一样的可以不指定
      length: 30,         列类型的长度,number
      nullable: false,    是否允许为空,boolean,默认值是false
      select:false,      查询数据库时是否显示该字段,boolean,默认值是true,密码一般使用false
      comment: '密码'     数据库注释,stirng
  })
 */
@Entity('new_table') 
export class newTable {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ name: 'name', nullable: true })   // 名称「存储唯一的标识符」
  name: string;

  @Column({ name: 'title', nullable: true })  // 配置发送的标题
  title: string;

  @Column({ name: 'content', nullable: true }) // 配置发送的内容
  content: string;

  @Column({ name: 'icons', nullable: true }) // 配置发送的icon
  icons: string;

  @Column({ name: 'public_key', nullable: true }) // 生成公钥,借助web-push.js
  publicKey: string;

  @Column({ name: 'private_key', nullable: true })  // 生成私钥,借助web-push.js
  privateKey: string;

  @Column({ name: 'info_auth_ua', nullable: true, type: 'text' }) // 客户端传入的浏览器标识符
  infoAuthUa: string;
}

数据库一览

CMS

技术栈这边根据自己喜欢搭建满足下图选项即可,我这里使用的是vue-cli直接生成,通过下图可以看到在「发送用户」里面的数据就是由 /push/notice-users 进行查询,发送的时候使用/push/config-message 进行发送通知

完整效果演示

数据埋点 && 通信方式

很多时候我们需要在接收到消息或者点击消息等一些动作进行两端的通信,比如做一些数据埋点等操作,在Service Worker中提供很多方法,这里我简单说两种:clientsnew BroadcastChannel

clients 通信实例代码

service-worker.js

javascript 复制代码
 ...
self.addEventListener('message', function(event) {
  self.clients.matchAll().then(function(clients) {
    clients.forEach(function(client) {
      client.postMessage({msg: '这是来自 Service Worker 的消息'});
    });
  });
});
  ...

src/demo.vue

javascript 复制代码
navigator.serviceWorker.addEventListener('message', function(event) {
  console.log('接收到来自 Service Worker 的消息:', event.data.msg);
});

Broadcast Channel 实例代码

service-worker.js

javascript 复制代码
const MESSAGE_COMMUNICATION = 'web-push-message'
const broadcast = new BroadcastChannel(MESSAGE_COMMUNICATION)

...
// 点击消息的时候发送广播
self.addEventListener('notificationclick', function (event) {
  const notification = event.notification
  broadcast.postMessage({ type: 'NOTI_FICATION_CLICK' })
  notification.close()
  event.waitUntil(clients.openWindow(notification.data.links))
})

...

src/demo.vue

javascript 复制代码
const broadcast = new BroadcastChannel(this.MESSAGE_COMMUNICATION)
broadcast.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'NOTI_FICATION_CLICK') {
    console.log('进行通信')
  }
})

需要注意 :BroadcastChannel虽好,但是容易造成页面的longTask 和BFCache失效,相关可以参考

fetch

相信大家看完通信方式会有一个疑问❓如果当浏览器关闭的时候sw还在运行但是还能收到push推送的消息,那么如果进行一些数据上报怎么办?这里有一种方式就是用fetch来进行接口上传,通过Node或者业务的服务器进行接收达到数据收集的效果,

当然方法绝对不止这一种,有更多的方案欢迎大家留言探讨,下方是模拟实例代码

service-worker.js

javascript 复制代码
...
function uploadBuriedPoints() {
  fetch('https://www.fetch.com?xxxx=11&dd=222', {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
    },
    // 可以在这里设置其他选项,例如查询参数等
  })
    .then((response) => response.json())
    .then((data) => {
      console.log(data)
      // 在这里对返回的数据进行处理
    })
    .catch((error) => {
      console.error('请求出错:', error)
    })
}
...

self.addEventListener('notificationclick', function (event) {
  const notification = event.notification
  uploadBuriedPoints()
  notification.close()
  event.waitUntil(clients.openWindow(notification.data.links))
})

基于Web Push推送功能的浏览器支持

需要注意: Safari的版本是 iOS 操作系统的一部分,因此其版本与您当前拥有的 iOS 版本相同。

web-push 错误状态代码

有多种问题可能会导致推送服务返回非 201 响应代码。下面列出了 HTTP 状态代码及其与 Web 推送的关系。

状态代码 说明
429 请求数量过多。您的应用服务器已达到推送服务的速率限制。服务的响应应包含"重试"标头,以指示在多长时间后可以发出其他请求
400 请求无效。您有一个标头无效或格式不正确。
404 未找到。在这种情况下,您应该从后端删除 PushSubscription,并等待机会重新订阅用户。
410 不见了。订阅不再有效,应从您的后端中移除。这可以通过对"PushSubscription"调用"unsubscribe()"来重现。
413 载荷大小过大。推送服务必须支持的最小载荷为 4096 字节(或 4kb)。大于该值就会导致此错误。

其他问题

1、MacOS操作系统接收不到消息❓

事实上在MAC上很多浏览器的消息通知都是默认关闭的,需要手动打开如图所示:

系统偏好设置 => 通知=> firefox\Chrome

2、Windows操作系统接收不到消息❓

3、Android手机收不到消息❓

Chrome浏览器 => 头部的三个点=> 设置 => 通知 「打开全部的通知」=> 网站 => 设置当前弹出形式

4、设置icon图像大小多少合适❓

答:Icon 配置的链接地址 要求的尺寸是256*256 ;另外可以使用一个在线生成PWA的工具

5、Web Push 在 Android WebView 中不起作用❓

答:不支持,如果需要推送消息,可以使用APP的内置方法进行发送。

相关资料

6、IOS的pwa形式不支持自定义icon❓

答:没找到相关的资料,但是实际在设置的时候确实不起作用,然后找了GPT问了问 得到了验证。

7、异常场景还是收不到❓

确定浏览器是否开启了隐身模式 、或者浏览器是否安装了拦截广告消息等插件 、在部分Android 设备有"请勿打扰 "模式和"无干扰"模式。 如果您启用了其中任何一个,您将不会收到任何通知。

未来发展和趋势

  1. 平台支持扩大:随着Web Push技术的成熟和普及,越来越多的平台和浏览器开始支持该功能。目前,主流的浏览器如Chrome、Firefox、Safari等都已经支持Web Push,未来更多的浏览器可能会加入支持行列。这将促使更多网站和应用采用Web Push来与用户进行沟通和互动。
  2. 个性化和智能化:未来的Web Push通知将越来越注重个性化和智能化。通过分析用户的兴趣、行为和偏好等数据,系统可以向用户发送更加精准和有针对性的推送通知,提供更好的用户体验。同时,结合人工智能和机器学习等技术,Web Push通知可以更好地理解和适应用户的需求,提供更加个性化和智能化的推送服务。
  3. 跨平台和跨设备:未来的Web Push技术可能会越来越支持跨平台和跨设备的消息推送。用户可以在不同的设备上接收和管理推送通知,无论是在手机、平板还是电脑上,用户都能够及时获取到重要的消息。这将极大地提高用户的便利性和可用性。
  4. 安全性和隐私保护:Web Push通知涉及到用户的个人信息和隐私,未来的发展趋势将更加注重安全性和隐私保护。技术和标准将不断完善,以确保用户的数据得到有效的保护,并防止被滥用和泄露。同时,用户对于隐私权的关注也会促使厂商和开发者更加注重用户数据的安全和合规性。
  5. 更多应用场景和创新:Web Push通知不仅仅局限于一般的消息推送,未来可能会在更多的应用场景中得到应用和创新。比如,在电子商务领域,可以通过Web Push通知来实现订单状态更新、促销活动提醒等功能;在新闻媒体领域,可以通过Web Push通知来推送新闻资讯、热点话题等内容;在社交网络领域,可以通过Web Push通知来提醒用户有新的消息、评论或者关注等。未来,随着技术的不断进步和应用场景的拓展,Web Push通知将会发挥更大的作用。

总的来说,未来Web Push的发展趋势将会更加多元化、智能化和个性化。随着技术的不断进步和用户需求的不断变化,Web Push将成为用户与网站、应用进行互动和沟通的重要手段之一。

最后

如果本篇文章能帮助到大家的话欢迎点赞收藏,避免下次做需求的时候找不到哦~

参考资料

相关推荐
辻戋2 小时前
从零实现React Scheduler调度器
前端·react.js·前端框架
徐同保2 小时前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun3 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp3 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.4 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl6 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫7 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友7 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理9 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻9 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js