WebPush

TsgcHTMLWebPush: Web Push for a browser that is not running or not on your page, in Delphi, C++ Builder and .NET. Available only in the Enterprise and All-Access editions.

TsgcHTMLWebPush

Web Push reaches a user whose browser is closed or showing another page. The page subscribes, your application stores the subscription through three events, and one call to Send delivers the notification. Available only in the Enterprise and All-Access editions.

Component class

TsgcHTMLWebPush (unit sgcHTML_WebPush)

Renders

No markup: encrypted push messages sent to the browser

Languages

Delphi, C++ Builder, .NET

Generate keys, store subscriptions, send

Generate a VAPID key pair once and keep it. Assign the pair and a Subject, handle the storage events, set WebPush on the engine and WebPushEnabled on its template, then call 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>

Key properties & methods

The members you reach for most often.

Editions

Web Push is available only in the Enterprise and All-Access editions. The unit compiles when SGC_WEBPUSH is defined, which sgcVer.inc sets for those two editions, and it also needs sgcHTML itself, which is sold independently. The notification inbox and the preferences table are part of sgcHTML and do not need it: only the push channel does.

VAPID keys

GenerateVAPIDKeys is a class method that returns a new pair: the public half is the base64url application server key the browser subscribes with, the private half an EC key in PEM. Assign them to VAPIDPublicKey and VAPIDPrivateKey. Subject is the VAPID contact, a mailto: or https: URI. The standard Delphi build loads OpenSSL 3.x for the cryptography and selects that API itself; the .NET port uses managed cryptography and needs no OpenSSL.

Storage events

The component keeps no subscription of its own. OnSaveSubscription(aUserID, aEndpoint, aP256dh, aAuth) stores one, OnLoadSubscriptions(aUserID, aList) fills aList with one Add(endpoint, p256dh, auth) per stored subscription of the user, and OnDeleteSubscription(aUserID, aEndpoint) removes one. Each entry is a TsgcHTMLWebPushSubscription.

Sending

Send(aUserID, aTitle, aBody, aURL, aIcon, aTag, aTTL, aUrgency) encrypts one notification per stored subscription of the user (RFC 8291) and POSTs it to the push service with a VAPID signature (RFC 8292). It returns how many subscriptions it delivered to, or 0 when Enabled is False. The sender, TsgcHTMLWebPushClient, reuses TsgcHTTP_API_WebPush_Client.

Time to live and urgency

TTL defaults to 2419200 seconds (28 days) and Urgency is empty, which sends no urgency header. Pass aTTL or aUrgency to Send to override either for one notification.

Expired and failed sends

When a push service answers 404 or 410, OnSubscriptionExpired(aUserID, aEndpoint, aStatusCode) fires so you can drop the subscription. Any other failure raises OnSendError(aUserID, aEndpoint, E) and the remaining subscriptions are still tried.

Subscribe endpoints

Once the component is assigned to the engine, a POST to SubscribeEndpoint (/push/subscribe) or UnsubscribeEndpoint (/push/unsubscribe) is answered for you: 204 when handled, 400 for an invalid subscription, 403 when no user resolves, 413 above 64 KB. Call HandleSubscribe(aJSON, aUserID) and HandleUnsubscribe yourself to route the POSTs your own way.

Who is subscribing

The user is never read from what the browser posts. With a TsgcHTMLAuth on the engine it is the user of the session cookie, and the CSRF token is required. Without one, OnResolveUser(aCookieHeader, aHeaders, aBody, aUserID, aAllowed) answers with your own user id or refuses. With neither, the request gets 403.

In the page

With Template.WebPushEnabled on, the engine copies the public key and endpoints into the template, serves the service worker at ServiceWorkerPath and the page carries the subscribe script. Put <button data-sgc-webpush> in the page so the permission prompt runs from a click; data-sgc-webpush="unsubscribe" turns it off. Browsers need https, or localhost while developing.

Page or push

TsgcHTMLComponent_Notification chooses between the two. Assign Presence and WebPush, answer OnGetChannel with ncPush and call AddNotification(aUserID, aId, aTitle, aMessage, ...): a connected user gets the notification in the page, a user who is not connected gets one Send, so nobody is told twice. Without an OnGetChannel handler nothing is ever pushed.

Keep exploring

Online HelpFull API reference and usage guide for this component.
Web Push guideHow the pieces fit together: subscriptions, VAPID, the engine endpoints and the notification component.
All sgcHTML ComponentsBrowse the full feature matrix of 80+ components.
Download Free TrialThe 30-day trial ships the 60.HTML demo projects, including 17.FieldService, which uses Web Push.
PricingSingle, Team and Site licenses with full source code.
Best value: All-AccessEvery eSeGeCe product, Premium Support included, from €1,059/year.
See All-Access pricing

Ready to Get Started?

Download the free trial and start building web UIs in Delphi, C++ Builder and .NET.