![]() |
VOOZH | about |
dotnet add package Shiny.Extensions.Push.DocumentDb --version 1.0.0-beta-0002
NuGet\Install-Package Shiny.Extensions.Push.DocumentDb -Version 1.0.0-beta-0002
<PackageReference Include="Shiny.Extensions.Push.DocumentDb" Version="1.0.0-beta-0002" />
<PackageVersion Include="Shiny.Extensions.Push.DocumentDb" Version="1.0.0-beta-0002" />Directory.Packages.props
<PackageReference Include="Shiny.Extensions.Push.DocumentDb" />Project file
paket add Shiny.Extensions.Push.DocumentDb --version 1.0.0-beta-0002
#r "nuget: Shiny.Extensions.Push.DocumentDb, 1.0.0-beta-0002"
#:package Shiny.Extensions.Push.DocumentDb@1.0.0-beta-0002
#addin nuget:?package=Shiny.Extensions.Push.DocumentDb&version=1.0.0-beta-0002&prereleaseInstall as a Cake Addin
#tool nuget:?package=Shiny.Extensions.Push.DocumentDb&version=1.0.0-beta-0002&prereleaseInstall as a Cake Tool
Server-side push notification dispatch for .NET. Provider-agnostic core with transports for APNs
(direct, .p8/ES256 over HTTP/2), FCM (HTTP v1, with multicast batching), Web Push (VAPID +
RFC 8291) and WNS (Windows, modern Windows App SDK / Entra auth). Structured targeting, topics,
interceptors, dead-token pruning, multi-app keyed registration, metrics + tracing. AOT/trim friendly
(verified by a native-AOT smoke test).
See for a runnable ASP.NET Core API with a Scalar UI.
| Package | Contents |
|---|---|
Shiny.Extensions.Push |
core (manager, in-memory repo, debug provider) plus the built-in APNs, FCM, Web Push and WNS transports |
Shiny.Extensions.Push.DocumentDb |
persistence over any Shiny.DocumentDb backend |
The four transports ship in the core package — there are no separate
*.Apns/*.Fcm/*.WebPush/*.Wnspackages. Each still lives in its own namespace (Shiny.Extensions.Push.Apns,.Fcm,.WebPush,.Wns) and is opt-in viaAddApns/AddFcm/AddWebPush/AddWns, so you only pay for what you register.
Before the library can send anything you need credentials from each platform, and the client app has to hand its device token/subscription to your server. The end-to-end setup per platform:
Requires a paid Apple Developer account.
.p8 (you can only download it once —
store it safely). Note the Key ID (10 chars) shown next to the key.com.example.app). Make sure that App ID has
the Push Notifications capability enabled.registerForRemoteNotifications, and
POST the returned device token to your server. (Use Shiny.Push or the
native APIs.).p8 for both; only the APNs host differs and
is chosen per device by DeviceRegistration.Environment. Debug builds get sandbox tokens, App
Store/TestFlight builds get production tokens — the two are not interchangeable.You end up with: TeamId, KeyId, BundleId, and the AuthKey_XXXXXXXXXX.p8 file.
Requires a Google / Firebase account.
google-services.json for the client
app.project_id, client_email, and
private_key. This is the server credential — keep it secret.You end up with: the service-account JSON (pass its path or contents to AddFcm).
Requires your site served over HTTPS (or localhost) with a service worker.
web-push
CLI:
npm install -g web-push
web-push generate-vapid-keys # prints a base64url Public Key and Private Key
(Any P-256 key pair works: the public key is the 65-byte uncompressed point as base64url, the private
key the 32-byte scalar as base64url.) Pick a Subject — a mailto: or https: contact URL.const reg = await navigator.serviceWorker.register('/sw.js');
const sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: '<VAPID_PUBLIC_KEY>' // base64url
});
// POST to your server: sub.endpoint, sub.toJSON().keys.p256dh, sub.toJSON().keys.auth
/sw.js):
self.addEventListener('push', e => {
const d = e.data.json();
e.waitUntil(self.registration.showNotification(d.title, { body: d.body, data: d }));
});
You end up with: VAPID PublicKey, PrivateKey, Subject, and per device the endpoint +
p256dh + auth (mapped to DeviceToken and Data — see Register a device).
Uses the modern WNS auth model (Windows App SDK / WinUI 3 / unpackaged apps) — Microsoft Entra (Azure AD), not the classic Partner Center Package SID + secret.
PushNotificationManager) and POST
the channel URI to your server.You end up with: TenantId, ClientId, ClientSecret, and per device the channel URI (stored
as the registration's DeviceToken).
services.AddPushNotifications(push =>
{
push.AddApns(o =>
{
o.TeamId = "ABCDE12345";
o.KeyId = "KEY1234567";
o.BundleId = "com.example.app";
o.PrivateKeyPath = "AuthKey_KEY1234567.p8"; // or o.PrivateKey = "<PEM>"
});
push.AddFcm(o => o.ServiceAccountJsonPath = "firebase-service-account.json");
push.AddWebPush(o =>
{
o.PublicKey = "<vapid-public-key>"; // base64url (web-push format)
o.PrivateKey = "<vapid-private-key>";
o.Subject = "mailto:you@example.com";
});
push.AddWns(o =>
{
o.TenantId = "<entra-tenant-id>";
o.ClientId = "<app-registration-client-id>";
o.ClientSecret = "<client-secret>";
});
// push.UseDocumentDb(o => o.DatabaseProvider = new SqliteDatabaseProvider("Data Source=push.db"));
// push.AddInterceptor<LocalizationInterceptor>();
// push.Configure(m => m.MaxDegreeOfParallelism = 25);
});
A device registers its platform (iOS, MacOS, Android, WebBrowser); the manager routes it to the
provider that claims it. Web Push registrations put the subscription endpoint in DeviceToken and the
p256dh/auth keys in Data.
// APNs (iOS/macOS) and FCM (Android): the platform's device token
await pushManager.RegisterDevice(new DeviceRegistration
{
DeviceToken = "<apns-or-fcm-token>",
Platform = DevicePlatform.iOS, // or Android
DeviceId = "install-guid", // stable identity across token rotation
UserIdentifier = "user-42",
Tags = ["beta", "sports"],
Environment = PushEnvironment.Production
});
// Web Push: endpoint goes in DeviceToken; p256dh/auth go in Data
await pushManager.RegisterDevice(new DeviceRegistration
{
DeviceToken = subscription.Endpoint,
Platform = DevicePlatform.WebBrowser,
UserIdentifier = "user-42",
Data = new Dictionary<string, string>
{
["p256dh"] = subscription.Keys.P256dh,
["auth"] = subscription.Keys.Auth
}
});
var result = await pushManager.SendToUser("user-42", new PushNotification
{
Title = "Goal!",
Message = "Your team just scored",
Badge = 1,
DeepLink = "app://match/123"
});
// result.BatchId, result.Sent, result.Failed, result.TokensRemoved, result.Results[...]
await pushManager.SendToTags(["sports"], notification, TagMatch.Any);
await pushManager.SendToTokens(["tokenA", "tokenB"], notification);
await pushManager.Broadcast(notification);
await pushManager.Send(notification, new PushFilter { Platforms = [DevicePlatform.iOS], Tags = ["beta"] });
Silent / background push:
new PushNotification
{
Apple = new ApplePushOptions { ContentAvailable = true },
Data = new Dictionary<string, string> { ["sync"] = "inbox" }
};
Dead tokens (APNs 410 Unregistered / BadDeviceToken) are pruned automatically; rotated tokens are
applied back to the repository.
Batching (FCM multicast): when a provider supports it, devices that share the same notification are
delivered in one transport call (FCM packs up to 500 into a multipart /batch request) instead of one
request per device — automatic for broadcasts and topic fan-out, no call-site change. Per-device pruning,
rotation, and OnSent/OnFailed are preserved. Disable with push.Configure(m => m.EnableBatching = false);
make a custom transport batchable by implementing IPushBatchProvider.
Register one keyed APNs provider per app. Devices carry the matching AppId; the manager routes by it.
services.AddPushNotifications(push =>
{
push.AddApns("consumer", o => { o.BundleId = "com.example.consumer"; /* … */ });
push.AddApns("driver", o => { o.BundleId = "com.example.driver"; /* … */ });
});
await pushManager.RegisterDevice(new DeviceRegistration
{
DeviceToken = "<token>", Platform = DevicePlatform.iOS, AppId = "driver"
});
// target one app explicitly
await pushManager.Send(notification, new PushFilter { AppId = "driver", Tags = ["on-shift"] });
The default repository is in-memory. For production, use the Shiny.Extensions.Push.DocumentDb package —
it runs on any Shiny.DocumentDb backend (SQLite, Postgres, SQL Server, Cosmos, Mongo, …):
services.AddPushNotifications(push =>
{
push.AddApns(o => { /* … */ });
// registers the document store for you
push.UseDocumentDb(o => o.DatabaseProvider =
new SqliteDatabaseProvider("Data Source=push.db"));
// …or, if you already registered IDocumentStore via services.AddDocumentStore(…):
// push.UseDocumentDb();
});
Token-keyed operations (save, remove, rotation, subscribe) are O(1) point lookups. Targeted sends push
UserIdentifier/AppId equality into the store query and filter the rest in-process.
Topics are server-side subscriptions that work across every provider:
await pushManager.SubscribeToTopic("<token>", DevicePlatform.iOS, "sports");
await pushManager.SendToTopic("sports", new PushNotification { Title = "Goal!" });
await pushManager.UnsubscribeFromTopic("<token>", DevicePlatform.iOS, "sports");
Metrics via System.Diagnostics.Metrics under the meter Shiny.Extensions.Push
(PushMetrics.MeterName): counters push.notifications.sent / .failed / .skipped,
push.tokens.pruned, and histogram push.send.duration (ms) — tagged by platform, provider,
status. Distributed tracing via the ActivitySource of the same name (push.send batch span +
push.deliver per-device span, or one push.deliver.batch span for a batched send). Wire both into
OpenTelemetry:
services.AddOpenTelemetry()
.WithMetrics(m => m.AddMeter(PushMetrics.MeterName))
.WithTracing(t => t.AddSource(PushDiagnostics.ActivitySourceName));
Note: this library surfaces
RateLimitedon the result but does not retry — backoff/Retry-Afterhandling is the caller's responsibility by design.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-beta-0002 | 44 | 6/24/2026 |
| 1.0.0-beta-0001 | 52 | 6/21/2026 |