Interface IPushNotificationAPI
- All Superinterfaces:
IPushNotificationCommonAPI
- All Known Implementing Classes:
ClientSessionPushAPI
Obtained from IClientSessionGyro.getPushNotificationAPI(). Everything in IPushNotificationCommonAPI applies, in particular the threading rule: every callback runs on this client session's worker thread.
Which calls run on the client. queryDeviceState(BiFunction), subscribe(String, BiFunction), unsubscribe(String, BiFunction) and resetDevice(BiFunction) need the device itself - its notification permission, its Service Worker or its FirebaseX plugin - so they go out to the client through the channel, PushChannelProps under $env (ruled 2026-09-24, RR-1 B), and complete when the client answers. sendToClient(IPushNotification, String, PushSendOptions, BiFunction) executes on the server, but it targets the device by the UUID the channel reports, so it needs the channel too (RR-16 A). The channel exists since P4-06 (PUSH-PROPS-DESIGN section 3): the client creates it under $env when the session is created, the server writes the application's VAPID public key into it, and since P4-10 the in-session tier delivers down it and sendToClient(IPushNotification, String, PushSendOptions, BiFunction) selects this session by it. queryDeviceState(BiFunction), sendToClient(IPushNotification, String, PushSendOptions, BiFunction), subscribe(String, BiFunction) (P4-07), unsubscribe(String, BiFunction), resetDevice(BiFunction) and resetDeviceEverywhere(BiFunction) (P4-08), and setSubscribeAction(IGProp, String, BiFunction) and removeSubscribeAction(IGProp) (P4-09, the click that asks the user) are live. In every session that has no channel - a Designer, Design Viewer or VSViewer session, a client built before P4 - each client-bound call fails with the reason PushNotificationException.NO_DEVICE, posted to the session's worker, never thrown (design 3.2). The engine-bound calls of IPushNotificationCommonAPI need no channel and work in every session.
The device is identified by the device UUID the client sends as X-iizi on the PN HTTP API, the single device identity of Push Notification (PUSH-WIRE-CONTRACT section 2.5); the application session's own device UUID plays no part. A client session that has not reported it cannot be targeted; its calls fail with the reason no_device.
- Author:
- Christopher Mindus
Method Summary
Modifier and TypeMethodDescriptionvoidqueryDeviceState(BiFunction<IPushDeviceState, Exception, Void> callback) Queries what this device can do: whether it supports Push Notification at all, which transport it would use, what the user has granted, and whether it is subscribed for this application.voidremoveSubscribeAction(IGProp<?> component) Removes the subscribe action of a button configured withsetSubscribeAction(IGProp, String, BiFunction): the next click on it subscribes nothing.voidresetDevice(BiFunction<Integer, Exception, Void> callback) Removes every subscription of the session's user on this device for this application, every topic (ruled 2026-09-24, RR-15 B), on this server and then on the device asunsubscribe(String, BiFunction)does once no row remains: a Browser gives up the subscription it holds under this application's key, a Cordova device keeps its shared FCM token.voidresetDeviceEverywhere(BiFunction<Integer, Exception, Void> callback) Removes every subscription of the session's user on this device, for every application and topic, iiziRun's own rows (*) included: the Java form of the HTTPresetwith an empty body (PUSH-WIRE-CONTRACT section 5.4), what iiziRun's Reset does on the server (ruled 2026-09-25, P4-b).voidsendToClient(IPushNotification notification, String topic, PushSendOptions options, BiFunction<IPushSendResult, Exception, Void> callback) Sends a notification to this device only: in-session over this client session when the delivery mode allows it and the client is connected, and through the device's subscription for this application and topic when it holds one - the device is the one the channel reported (RR-16 A), and the in-session target is this client session's channel.voidsetSubscribeAction(IGProp<?> component, String topic, BiFunction<IPushSubscribeResult, Exception, Void> callback) Configures the subscribe action of a button (PUSH-PROPS-DESIGN section 8.2, ruled 2026-09-24 RR-6 B and RR-7 A): the click oncomponent, on the client and inside the user's gesture, subscribes this device for this application andtopic- the one place where Push Notification is not driven from the server (PUSH-WIRE-CONTRACT section 9.4a) - and this server then stores the subscription as this session's user, this application, this device and the topic, exactly assubscribe(String, BiFunction)does, with no privilege checked (RR-5 A), and callscallbackwith the outcome.voidsubscribe(String topic, BiFunction<IPushSubscribeResult, Exception, Void> callback) Subscribes this device for this application and a topic: creates the device's subscription with its Service Worker (a Browser) or asks FirebaseX for its token (Cordova), and stores it with this server as this session's user, this application, this device and the topic - the Java form of the HTTPsubscribe(PUSH-WIRE-CONTRACT section 5.2), with no HTTP and no Push Notification privilege checked: the application decides, so a user refusedsubscribeon the HTTP API can still be subscribed by an application (ruled 2026-09-24, RR-5 A).voidunsubscribe(String topic, BiFunction<Integer, Exception, Void> callback) Removes this device's subscription for this application and a topic: on this server the row of the session's user, this application, the topic and this device - the Java form of the HTTPunsubscribe(PUSH-WIRE-CONTRACT section 5.3), no privilege checked - and then, on the device, the push subscription a Browser holds under this application's key, but only when no row of this application remains for this device: a Browser holds one subscription for every topic of an application (PUSH-PROPS-DESIGN section 4.2, 8.7), and one it holds under another key - the server's own, or another application's - is left alone.Methods inherited from interface IPushNotificationCommonAPI
addPushNotificationListener, newMessageBuilder, queryAvailableTransports, querySubscriptions, removePushNotificationListener
Method Details
queryDeviceState
Queries what this device can do: whether it supports Push Notification at all, which transport it would use, what the user has granted, and whether it is subscribed for this application.- Parameters:
callback- Called on the client session's worker thread with the state, or with the failure.- Throws:
NullPointerException- Ifcallbackisnull.
subscribe
Subscribes this device for this application and a topic: creates the device's subscription with its Service Worker (a Browser) or asks FirebaseX for its token (Cordova), and stores it with this server as this session's user, this application, this device and the topic - the Java form of the HTTPsubscribe(PUSH-WIRE-CONTRACT section 5.2), with no HTTP and no Push Notification privilege checked: the application decides, so a user refusedsubscribeon the HTTP API can still be subscribed by an application (ruled 2026-09-24, RR-5 A). Listeners of the user's sessions getonSubscriptionCreatedfor a new row.On a Browser this call can only succeed when the user has already granted notifications. Browsers show the permission prompt only in response to a genuine user gesture, and Safari refuses the subscription outright without one; a request sent from the server reaches the client in a WebSocket message handler, after any click has returned, and can never count as one. So with the permission undecided this call fails at once with
gesture_required, without prompting (ruled 2026-09-24, RR-8 A); with it granted the client attempts the subscription, and a Browser that refuses for want of a gesture is reported asgesture_requiredtoo (RR-9 A). The first subscription of a Browser user must come from a click: configure the subscribe action withsetSubscribeAction(IGProp, String, BiFunction)when the panel is created and handle the outcome in that action's callback. Calling this method from an action handler on the server does NOT count as the user's click - by the time the server runs, the click is over.gesture_requiredis therefore not an error in the application's code: it means "ask the user to press the button you configured". This is the one place where Push Notification is not driven from the server (PUSH-WIRE-CONTRACT section 9.4a). On Cordova there is no gesture: FirebaseX asks the system's permission when it is undecided (the system dialog on iOS and on Android 13 and later), and the call waits up to five minutes for the answer.What else the callback can report.
permission_deniedis final until the user changes it in the Browser's or the operating system's settings; no code can ask again.key_conflict: the Browser already holds a push subscription under another VAPID key - the server's own (iiziRun,*) or another application's - which is never replaced (RR-21 A); or a Cordova device's FCM token belongs to another Firebase project than this application's, as it does inside iiziRun Developer, where an application reaches the device through the in-session tier and iiziRun's*subscription only (ruled 2026-09-26, CC-PUSH-ROADMAP row 4 A).not_configured: this application offers no transport the device uses (no VAPID key pair for a Browser, no Firebase project for Cordova);IPushNotificationCommonAPI.queryAvailableTransports(BiFunction)tells beforehand.unsupported: the device cannot receive Push Notification at all - on iOS a web page receives it only as a Home Screen web app, and an ordinary Safari tab reportsUNSUPPORTED.no_user: the session has no authenticated user to store the row for.timeout: the client did not answer within five minutes.bad_request: the topic is not a valid name, or the client answered with a subscription lacking its endpoint or keys, and nothing was stored. Every failure is aPushNotificationException: itsreasonis the code to branch on, andPushNotificationException.getUserText()a text the user can be shown for it - forpermission_deniedon a phone "Notifications are off for this app: turn them on in the phone's Settings." - since a phone whose user once refused is never asked again, and nothing else tells the user why nothing happens (CC-FIX-IIZIRUN-RESUME-RECONNECT, AP-D-2 (c)); an application with a text table of its own uses the reason as the key of its entry.The callback runs on the client session's worker. If the session is disposed first, it is not called and the outcome is logged at INFO.
- Parameters:
topic- The topic, the empty string for the application-level subscription; a name is validated as the HTTP API validates one (PUSH-WIRE-CONTRACT section 2.4).callback- Called on the client session's worker thread with the result - whether a new row was stored, and the subscription as stored - or with the failure.- Throws:
NullPointerException- Iftopicorcallbackisnull.
unsubscribe
Removes this device's subscription for this application and a topic: on this server the row of the session's user, this application, the topic and this device - the Java form of the HTTPunsubscribe(PUSH-WIRE-CONTRACT section 5.3), no privilege checked - and then, on the device, the push subscription a Browser holds under this application's key, but only when no row of this application remains for this device: a Browser holds one subscription for every topic of an application (PUSH-PROPS-DESIGN section 4.2, 8.7), and one it holds under another key - the server's own, or another application's - is left alone. A Cordova device keeps its FCM token, which every application on the device shares, and no FCM Topic is unsubscribed (ruled 2026-09-26, R-5 A: the server's rows are the subscription). Listeners of the user's sessions getonSubscriptionRemovedwith the reasonunsubscribedfor each row removed.The server's part comes first and stands whatever the device answers: a
timeoutreported here means the rows are gone and the device may still hold its subscription, which the nextsubscribeanswers as it is. Zero removed is a success: the state the caller wanted already holds.no_userwhen the session has no authenticated user;bad_requestfor a topic that is not a valid name.- Parameters:
topic- The topic, the empty string for the application-level subscription; a name is validated as the HTTP API validates one.callback- Called on the client session's worker thread with the number of subscriptions removed from the server, zero when there was none, or with the failure.- Throws:
NullPointerException- Iftopicorcallbackisnull.
resetDevice
Removes every subscription of the session's user on this device for this application, every topic (ruled 2026-09-24, RR-15 B), on this server and then on the device asunsubscribe(String, BiFunction)does once no row remains: a Browser gives up the subscription it holds under this application's key, a Cordova device keeps its shared FCM token. Other applications' rows on this device, and iiziRun's own (*), survive; listeners of the user's sessions getonSubscriptionRemovedwith the reasonresetfor each row removed. Removing every application's rows isresetDeviceEverywhere(BiFunction), the HTTPresetwith an empty body (PUSH-WIRE-CONTRACT section 5.4), and iiziRun's Reset.The server's part comes first and stands whatever the device answers; zero removed is a success;
no_userwhen the session has no authenticated user.- Parameters:
callback- Called on the client session's worker thread with the number of subscriptions removed from the server, or with the failure.- Throws:
NullPointerException- Ifcallbackisnull.
resetDeviceEverywhere
Removes every subscription of the session's user on this device, for every application and topic, iiziRun's own rows (*) included: the Java form of the HTTPresetwith an empty body (PUSH-WIRE-CONTRACT section 5.4), what iiziRun's Reset does on the server (ruled 2026-09-25, P4-b). The one member of this API that reaches beyond this application's subscriptions. On the device a Browser then gives up its push subscription whatever key it was made under - nothing of this user on this device remains to deliver to - and a Cordova device keeps its FCM token, which is never unregistered by the Java API. Listeners of the user's sessions getonSubscriptionRemovedwith the reasonresetfor each row of their own application.The server's part comes first and stands whatever the device answers; zero removed is a success;
no_userwhen the session has no authenticated user. Stopping this application's notifications alone isresetDevice(BiFunction).- Parameters:
callback- Called on the client session's worker thread with the number of subscriptions removed from the server, or with the failure.- Throws:
NullPointerException- Ifcallbackisnull.
sendToClient
void sendToClient(IPushNotification notification, String topic, PushSendOptions options, BiFunction<IPushSendResult, Exception, Void> callback) Sends a notification to this device only: in-session over this client session when the delivery mode allows it and the client is connected, and through the device's subscription for this application and topic when it holds one - the device is the one the channel reported (RR-16 A), and the in-session target is this client session's channel. Under the default mode,DeliveryMode.BOTHsince P4-10 (ruled 2026-09-24, RR-14b B), both happen: the result then counts two deliveries for one device, and on a Browser the two arrive as ONE notification, shown under the sametag(PUSH-PROPS-DESIGN section 9.1).DeliveryMode.PREFER_IN_SESSIONin the options keeps it to the session while the client is connected,DeliveryMode.REQUIRE_DEVICEto the subscription.Works in a session without an authenticated user (ruled 2026-09-26, CC-PUSH-ROADMAP row 3 B): the call names this session, not a user, so the in-session delivery reaches the page while the device tier matches no stored subscription - a subscription belongs to a user - and the outcome's user ID is -1 with an empty user name. The fan-out sends of
IAppSessionPushNotificationAPIstay user-bound and failno_userthere. A topic named here does not gate the in-session delivery as it gates a fan-out (row 2 B): the device is named, so the session is the target.- Parameters:
notification- The notification, built withIPushNotificationMessageBuilder.topic- The topic, the empty string for the application-level subscription.options- The time-to-live, urgency and delivery mode, ornullfor every default.callback- Called on the client session's worker thread with the result, whose target outcomes name this device, or with the failure.- Throws:
NullPointerException- Ifnotification,topicorcallbackisnull.
setSubscribeAction
void setSubscribeAction(IGProp<?> component, String topic, BiFunction<IPushSubscribeResult, Exception, Void> callback) Configures the subscribe action of a button (PUSH-PROPS-DESIGN section 8.2, ruled 2026-09-24 RR-6 B and RR-7 A): the click oncomponent, on the client and inside the user's gesture, subscribes this device for this application andtopic- the one place where Push Notification is not driven from the server (PUSH-WIRE-CONTRACT section 9.4a) - and this server then stores the subscription as this session's user, this application, this device and the topic, exactly assubscribe(String, BiFunction)does, with no privilege checked (RR-5 A), and callscallbackwith the outcome. On a Browser the click is what shows the permission prompt:pushManager.subscribe()is called synchronously inside the click, as measured on 2026-09-26 (P4-01) - Firefox, Safari and an iPadOS Home Screen web app show the prompt only there and refuse without a gesture, Chrome prompts either way. On Cordova FirebaseX asks the system's permission when it is undecided, and no gesture is needed.A session without an authenticated user answers
no_userbefore any prompt (CC-FIX-PWA-FOLLOWUPS, PWA-D-6): the row a click stores is the user's, so the client refuses the click at once from the channel'sauthand no permission is spent on it. Configure the action all the same: the first click after the sign-in asks and subscribes.The callback is called once per click, not once per configuration, and may be called several times in one session: the user may click, refuse, and click again. It runs on the client session's worker with the result - whether a new row was stored, and the subscription as stored - or with the failure:
permission_denied(the prompt's Block, or a permission already denied: final until the user changes it in the Browser's or the operating system's settings, no code can ask again),dismissed(the prompt closed without an answer: the permission stays undecided and the next click asks again),key_conflict(the Browser already holds a push subscription under another VAPID key, never replaced, RR-21 A; or a Cordova device's FCM token belongs to another Firebase project, CC-PUSH-ROADMAP row 4 A),not_configured(this application offers no transport the device uses),unsupported(the device cannot receive Push Notification at all: on iOS a web page receives it only as a Home Screen web app whose manifest says"display": "standalone", and an ordinary Safari tab reports it),no_user(the session has no authenticated user to store the row for),bad_request(the client answered with a subscription lacking its endpoint or keys, and nothing was stored).gesture_requiredis not expected from a click and is reported if a Browser refuses inside one. Every failure is aPushNotificationExceptionwhosePushNotificationException.getUserText()is a text the user can be shown for it (seesubscribe(String, BiFunction)).The component's own action handler (
@OnUIAction) still runs, at once, before the user has answered the prompt (RR-20 A): do not decide anything about the subscription there. The client disables the button while the outcome is pending and enables it again after; every other follow-up - a message, hiding the button, changing the screen - is this callback's (RR-6 B). Setting an action on a component that has one replaces it. A successful click stores the subscription even if you removed the action while the prompt was open: the device holds it, and a row nobody stores is a device the server cannot reach; the callback is then not called and the outcome is logged at INFO.Configure it when the panel is created, and show the button only where it can work (
queryDeviceState(BiFunction)). The application's VAPID public key is refreshed into the client when the action is configured, so a key rotated since the session was created reaches the click. With no channel - a Designer, Design Viewer or VSViewer session - nothing is configured andno_deviceis posted to the callback once; an invalid topic postsbad_requestonce.- Parameters:
component- The button: aUIButtonof this client session's screen (P4 hooks the click ofUIButtononly), named to the client by its property path (RR-7 A).topic- The topic, the empty string for the application-level subscription; a name is validated as the HTTP API validates one (PUSH-WIRE-CONTRACT section 2.4).callback- Called on the client session's worker thread, once per click, with the result or with the failure.- Throws:
NullPointerException- Ifcomponent,topicorcallbackisnull.IllegalArgumentException- Ifcomponentis not aUIButton, or not in this client session's property tree.
removeSubscribeAction
Removes the subscribe action of a button configured withsetSubscribeAction(IGProp, String, BiFunction): the next click on it subscribes nothing. A no-op on a component that has none, and in a session without a channel. A click whose prompt was open when the action was removed still stores the subscription the user allowed (seesetSubscribeAction(IGProp, String, BiFunction)).- Parameters:
component- The button.- Throws:
NullPointerException- Ifcomponentisnull.IllegalArgumentException- Ifcomponentis not aUIButton.