WebPush

TsgcHTMLWebPush:面向未运行或不在您页面上的浏览器的 Web Push,适用于 Delphi、C++ Builder 和 .NET。仅在 Enterprise 和 All-Access 版本中提供。

TsgcHTMLWebPush

Web Push 可以触达浏览器已关闭或正在显示其他页面的用户。页面完成订阅,您的应用通过三个事件保存该订阅,调用一次 Send 即可投递通知。仅在 Enterprise 和 All-Access 版本中提供。

组件类

TsgcHTMLWebPush(单元 sgcHTML_WebPush

渲染为

无标记:发送到浏览器的加密推送消息

语言

Delphi, C++ Builder, .NET

生成密钥、保存订阅、发送

只生成一次 VAPID 密钥对并妥善保管。指定该密钥对和 Subject,处理存储事件,在引擎上设置 WebPush,在其模板上设置 WebPushEnabled,然后调用 Send

// Enterprise and All-Access only: compiled when SGC_WEBPUSH is defined
uses
  Data.DB, sgcHTML_WebPush, sgcHTMX_Engine_Server;

var
  vPublic, vPrivate: string;
begin
  // once, then keep both values in your own settings
  TsgcHTMLWebPush.GenerateVAPIDKeys(vPublic, vPrivate);

  FPush := TsgcHTMLWebPush.Create(Self);
  FPush.VAPIDPublicKey := vPublic;
  FPush.VAPIDPrivateKey := vPrivate;
  FPush.Subject := 'mailto:support@example.com';
  FPush.OnSaveSubscription := PushSave;
  FPush.OnLoadSubscriptions := PushLoad;
  FPush.OnDeleteSubscription := PushDelete;
  FPush.OnSubscriptionExpired := PushExpired;
  FPush.Enabled := True;

  // oHTMX is a TsgcHTMX_Engine_Server
  oHTMX.WebPush := FPush;
  oHTMX.Template.WebPushEnabled := True;

  // later, from anywhere in the application
  FPush.Send('u1', 'Order shipped',
    'Order 1042 left the warehouse', '/orders/1042');
end;

// the application owns the storage
procedure TForm1.PushSave(Sender: TObject;
  const aUserID, aEndpoint, aP256dh, aAuth: string);
begin
  SaveSubscription(aUserID, aEndpoint, aP256dh, aAuth);
end;

procedure TForm1.PushLoad(Sender: TObject; const aUserID: string;
  const aList: TsgcHTMLWebPushSubscriptions);
var
  oQuery: TDataSet;
begin
  oQuery := OpenSubscriptions(aUserID);
  try
    // one Add per stored subscription of this user
    while not oQuery.Eof do
    begin
      aList.Add(oQuery.FieldByName('endpoint').AsString,
        oQuery.FieldByName('p256dh').AsString,
        oQuery.FieldByName('auth').AsString);
      oQuery.Next;
    end;
  finally
    oQuery.Free;
  end;
end;

procedure TForm1.PushDelete(Sender: TObject; const aUserID, aEndpoint: string);
begin
  DeleteSubscription(aUserID, aEndpoint);
end;

procedure TForm1.PushExpired(Sender: TObject;
  const aUserID, aEndpoint: string; aStatusCode: Integer);
begin
  // 404 or 410: the push service dropped it for good
  DeleteSubscription(aUserID, aEndpoint);
end;

// in the page, a click starts the permission prompt:
// <button data-sgc-webpush>Enable notifications</button>
// includes: sgcHTML_WebPush.hpp, sgcHTMX_Engine_Server.hpp
// Enterprise and All-Access only: compiled when SGC_WEBPUSH is defined

String vPublic, vPrivate;
// once, then keep both values in your own settings
TsgcHTMLWebPush::GenerateVAPIDKeys(vPublic, vPrivate);

FPush = new TsgcHTMLWebPush(this);
FPush->VAPIDPublicKey = vPublic;
FPush->VAPIDPrivateKey = vPrivate;
FPush->Subject = "mailto:support@example.com";
FPush->OnSaveSubscription = PushSave;
FPush->OnLoadSubscriptions = PushLoad;
FPush->OnDeleteSubscription = PushDelete;
FPush->OnSubscriptionExpired = PushExpired;
FPush->Enabled = true;

// oHTMX is a TsgcHTMX_Engine_Server
oHTMX->WebPush = FPush;
oHTMX->Template->WebPushEnabled = true;

// later, from anywhere in the application
FPush->Send("u1", "Order shipped",
  "Order 1042 left the warehouse", "/orders/1042");

// the application owns the storage
void __fastcall TForm1::PushSave(TObject *Sender,
  const UnicodeString aUserID, const UnicodeString aEndpoint,
  const UnicodeString aP256dh, const UnicodeString aAuth)
{
  SaveSubscription(aUserID, aEndpoint, aP256dh, aAuth);
}

void __fastcall TForm1::PushLoad(TObject *Sender, const UnicodeString aUserID,
  TsgcHTMLWebPushSubscriptions *const aList)
{
  // one Add per stored subscription of this user
  for (const TStoredSubscription &row : LoadSubscriptions(aUserID))
    aList->Add(row.Endpoint, row.P256dh, row.Auth);
}

// in the page, a click starts the permission prompt:
// <button data-sgc-webpush>Enable notifications</button>
using esegece.sgcWebSockets;

// once, then keep both values in your own settings
TsgcHTMLWebPush.GenerateVAPIDKeys(out var pub, out var priv);

var push = new TsgcHTMLWebPush();
push.VAPIDPublicKey = pub;
push.VAPIDPrivateKey = priv;
push.Subject = "mailto:support@example.com";

// the application owns the storage
push.OnSaveSubscription += (sender, aUserID, aEndpoint, aP256dh, aAuth) =>
    SaveSubscription(aUserID, aEndpoint, aP256dh, aAuth);
push.OnLoadSubscriptions += (sender, aUserID, aList) =>
{
    // one Add per stored subscription of this user
    foreach (var row in LoadSubscriptions(aUserID))
        aList.Add(row.Endpoint, row.P256dh, row.Auth);
};
push.OnDeleteSubscription += (sender, aUserID, aEndpoint) =>
    DeleteSubscription(aUserID, aEndpoint);
push.OnSubscriptionExpired += (sender, aUserID, aEndpoint, aStatusCode) =>
    DeleteSubscription(aUserID, aEndpoint);   // 404 or 410: gone for good
push.Enabled = true;

// htmx is a TsgcHTMX_Engine_Server
htmx.WebPush = push;
htmx.Template.WebPushEnabled = true;

// later, from anywhere in the application
int delivered = push.Send("u1", "Order shipped",
    "Order 1042 left the warehouse", "/orders/1042");

// in the page, a click starts the permission prompt:
// <button data-sgc-webpush>Enable notifications</button>

关键属性与方法

您最常使用的成员。

版本

Web Push 仅在 Enterprise 和 All-Access 版本中提供。该单元在定义了 SGC_WEBPUSH 时编译,sgcVer.inc 会为这两个版本设置它,同时它还需要 sgcHTML 本身,后者是单独销售的。通知收件箱和偏好设置表属于 sgcHTML,不需要它:只有推送通道需要。

VAPID 密钥

GenerateVAPIDKeys 是一个类方法,返回一个新的密钥对:公钥是浏览器用来订阅的 base64url 应用服务器密钥,私钥是 PEM 格式的 EC 密钥。将它们分别指定给 VAPIDPublicKeyVAPIDPrivateKeySubject 是 VAPID 联系人,即 mailto:https: URI。标准的 Delphi 版本会加载 OpenSSL 3.x 用于加密,并自行选择该 API;.NET 移植版使用托管加密,不需要 OpenSSL。

存储事件

组件自身不保存任何订阅。OnSaveSubscription(aUserID, aEndpoint, aP256dh, aAuth) 保存一个订阅,OnLoadSubscriptions(aUserID, aList) 针对该用户每个已保存的订阅,向 aList 调用一次 Add(endpoint, p256dh, auth)OnDeleteSubscription(aUserID, aEndpoint) 删除一个订阅。每个条目都是一个 TsgcHTMLWebPushSubscription

发送

Send(aUserID, aTitle, aBody, aURL, aIcon, aTag, aTTL, aUrgency) 为该用户每个已保存的订阅加密一条通知(RFC 8291),并带着 VAPID 签名(RFC 8292)通过 POST 发送到推送服务。它返回成功投递的订阅数量,当 EnabledFalse 时返回 0。发送器 TsgcHTMLWebPushClient 复用 TsgcHTTP_API_WebPush_Client

生存时间与紧急程度

TTL 默认为 2419200 秒(28 天),Urgency 为空,表示不发送紧急程度请求头。把 aTTLaUrgency 传给 Send,即可为单条通知覆盖其中任一项。

过期与失败的发送

当推送服务返回 404 或 410 时,会触发 OnSubscriptionExpired(aUserID, aEndpoint, aStatusCode),以便您删除该订阅。任何其他失败会引发 OnSendError(aUserID, aEndpoint, E),并且仍会继续尝试其余订阅。

订阅端点

将组件指定给引擎后,向 SubscribeEndpoint/push/subscribe)或 UnsubscribeEndpoint/push/unsubscribe)发出的 POST 会自动得到响应:处理成功返回 204,订阅无效返回 400,无法确定用户返回 403,超过 64 KB 返回 413。如果想以自己的方式路由这些 POST,请自行调用 HandleSubscribe(aJSON, aUserID)HandleUnsubscribe

谁在订阅

用户绝不会从浏览器提交的内容中读取。如果引擎上有 TsgcHTMLAuth,用户就是会话 cookie 所对应的用户,并且需要 CSRF 令牌。如果没有,OnResolveUser(aCookieHeader, aHeaders, aBody, aUserID, aAllowed) 会返回您自己的用户 ID,或者拒绝。两者都没有时,请求得到 403。

在页面中

启用 Template.WebPushEnabled 后,引擎会把公钥和端点复制到模板中,在 ServiceWorkerPath 处提供 service worker,页面带有订阅脚本。请在页面中放置 <button data-sgc-webpush>,使权限提示由点击触发;data-sgc-webpush="unsubscribe" 则用于关闭订阅。浏览器需要 https,开发时可以使用 localhost。

页面或推送

TsgcHTMLComponent_Notification 负责在这两者之间选择。指定 PresenceWebPush,在 OnGetChannel 中返回 ncPush,并调用 AddNotification(aUserID, aId, aTitle, aMessage, ...):已连接的用户会在页面中收到通知,未连接的用户则收到一次 Send,因此没有人会被通知两次。没有 OnGetChannel 处理程序时,永远不会推送任何内容。

继续探索

在线帮助此组件的完整 API 参考和使用指南。
Web Push 指南各部分如何配合:订阅、VAPID、引擎端点和通知组件。
所有 sgcHTML 组件浏览 80 多个组件的完整功能矩阵。
下载免费试用版30 天试用版包含 60.HTML 演示项目,其中 17.FieldService 使用了 Web Push。
价格Single、Team 和 Site 授权,均含完整源代码。
超值之选:All-AccesseSeGeCe 全部产品,含高级支持,每年 €1,059 起。
查看 All-Access 价格

准备好开始了吗?

下载免费试用版,开始在 Delphi、C++ Builder 和 .NET 中构建 Web 界面。