WebPush
TsgcHTMLWebPush:面向未运行或不在您页面上的浏览器的 Web Push,适用于 Delphi、C++ Builder 和 .NET。仅在 Enterprise 和 All-Access 版本中提供。
TsgcHTMLWebPush:面向未运行或不在您页面上的浏览器的 Web Push,适用于 Delphi、C++ Builder 和 .NET。仅在 Enterprise 和 All-Access 版本中提供。
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,不需要它:只有推送通道需要。
GenerateVAPIDKeys 是一个类方法,返回一个新的密钥对:公钥是浏览器用来订阅的 base64url 应用服务器密钥,私钥是 PEM 格式的 EC 密钥。将它们分别指定给 VAPIDPublicKey 和 VAPIDPrivateKey。Subject 是 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 发送到推送服务。它返回成功投递的订阅数量,当 Enabled 为 False 时返回 0。发送器 TsgcHTMLWebPushClient 复用 TsgcHTTP_API_WebPush_Client。
TTL 默认为 2419200 秒(28 天),Urgency 为空,表示不发送紧急程度请求头。把 aTTL 或 aUrgency 传给 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 负责在这两者之间选择。指定 Presence 和 WebPush,在 OnGetChannel 中返回 ncPush,并调用 AddNotification(aUserID, aId, aTitle, aMessage, ...):已连接的用户会在页面中收到通知,未连接的用户则收到一次 Send,因此没有人会被通知两次。没有 OnGetChannel 处理程序时,永远不会推送任何内容。