Skip to main content
Version: 1.2

11. Device Tokens

Device tokens are how the framework knows which Firebase Cloud Messaging (FCM) tokens belong to which user, across however many devices/browsers they're signed into.

11.1 Entity​

DeviceToken (table DeviceTokens, framework-owned, namespace-shimmed into Shumoul.Domain.Entities.Notifications):

FieldTypeNotes
UserIdGuidOwning user — globally unique, so cross-tenant collisions on (UserId, DeviceId) are impossible
DeviceIdstring(500)Client-supplied stable per-install identifier (not the FCM token itself)
PlatformDevicePlatformAndroid=1, Ios=2, Web=3
Tokenstring(2000), requiredThe actual FCM registration token
LastSeenOnDateTime?Updated on every register/refresh — used to order GetUserDevices results

11.2 Registration flow — upsert, not insert​

DeviceTokenService.RegisterAsync (backing POST Register):

The lookup uses IgnoreQueryFilters() specifically to find soft-deleted rows too — this is safe only because UserId is a globally unique GUID (a cross-tenant match is not possible), and is a narrow, justified exception to the platform's general "never .IgnoreQueryFilters() in tenant-facing services" rule (.claude/rules/multitenancy.md) — not a precedent for other services.

Why upsert matters: FCM tokens rotate (app reinstall, OS-level refresh, browser cache clear). Without upsert-by-(UserId, DeviceId), every token refresh would either violate a unique constraint or accumulate stale duplicate rows that the Push channel would keep sending to (and failing against).

11.3 Platform values​

ValuePlatform
1Android
2iOS
3Web

11.4 Consumption at dispatch time​

IDeviceTokenService.GetActiveTokensForUserAsync(userId) returns every Token where UserId == userId && IsActive && Is_Deleted != true, with no platform filtering — the Push channel provider sends to every active token for a user regardless of platform, since FCM handles platform-specific delivery transparently once a valid token is supplied.

11.5 Client integration checklist​

Flutter:

  1. On login (and on FirebaseMessaging.instance.onTokenRefresh), call POST DeviceToken/Register with a stable deviceId (e.g. derived from a persisted UUID, not the FCM token) and the current FCM token.
  2. On logout, consider calling DELETE DeviceToken/Delete for the current device so a shared/kiosk device stops receiving another user's push notifications — the framework does not do this automatically on logout today.

Angular (web push):

  1. Obtain a token via the Firebase JS SDK's web push registration.
  2. Call Register the same way, with platform: 3 (Web) and a browser-persisted deviceId.
  3. Re-register whenever the SDK reports a token change event.

11.6 Operator view​

GET GetUserDevices/{userId} (§9.3) lists a user's active devices, most-recently-seen first — useful for support/troubleshooting ("did this user's phone ever register for push?") without exposing the raw FCM token grid to non-admin roles (that requires DeviceTokens.ViewAll via GetDataTable instead).