Add push V3 client framework and migration guide#5440
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new Push V3 client framework to Codename One, adding a typed push envelope model and an explicit client binding (PushClient) while preserving the legacy push API via a compatibility endpoint and a deprecated documentation chapter.
Changes:
- Added the Push V3 client API (typed
PushMessage,PushSubscription,PushTransport, listeners/sinks, and dispatch wiring). - Updated native seams across Android/iOS/JavaScript/UWP to route typed envelopes and to support managed vs. legacy behavior.
- Added deterministic push contract tests (including a real JavaScript service-worker contract) and updated the developer guide to V3-first with a deprecated legacy chapter.
Reviewed changes
Copilot reviewed 33 out of 34 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/push-e2e/service-worker-contract.mjs | Executes the real JS service worker in a mocked environment to validate v3 push delivery behavior. |
| tests/push-e2e/scenarios.json | Defines end-to-end diagnostic scenarios for optional/staging push verification. |
| tests/push-e2e/README.md | Documents the push testing strategy and boundaries (CI vs. optional diagnostics). |
| scripts/run-push-e2e.sh | Operator-run script to exercise BuildCloud push admission + receipt polling against real devices/providers. |
| Ports/UWP/VSProjectTemplate/UWPApp/src/com/codename1/impl/SilverlightImplementation.cs | Routes WNS registration/delivery through PushClient when active; falls back to legacy behavior otherwise. |
| Ports/JavaScriptPort/src/main/webapp/sw.js | Adds v3 push detection, delivery semantics, and deep-link handling in notification clicks. |
| Ports/JavaScriptPort/src/main/webapp/js/push.js | Extends web push registration to optionally emit a typed subscription envelope for v3. |
| Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Push.java | Bridges HTML5 push registration and message delivery into PushClient when active. |
| Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java | Ensures explicit push callback binding is respected before registering push on HTML5. |
| Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java | Avoids legacy server registration when PushClient owns registration; forwards APNs token to active callback. |
| Ports/iOSPort/nativeSources/CodenameOne_GLAppDelegate.m | Routes managed typed envelope (cn1 dictionary) intact to Java push dispatch before legacy decoding. |
| Ports/Android/src/com/codename1/impl/android/StubUtil.java | Exposes stub helpers publicly for generated services to query app state/stub class. |
| Ports/Android/src/com/codename1/impl/android/AndroidImplementation.java | Adds Android v3 push handling path, plus Huawei transport accommodations for register/deregister. |
| maven/core-unittests/src/test/java/com/codename1/push/PushTest.java | Updates legacy push URL to the BuildCloud compatibility endpoint. |
| maven/core-unittests/src/test/java/com/codename1/push/PushMessageTest.java | Adds unit tests for typed envelope JSON roundtrip, schema validation, and immutability. |
| maven/core-unittests/src/test/java/com/codename1/push/PushClientTransportTest.java | Adds tests for transport seam behavior, registration sinks, dispatch, and surface replay. |
| maven/codenameone-maven-plugin/src/main/resources/com/codename1/builders/CN1FirebaseMessagingService7.javas | Generated FCM service routes typed envelope payloads into Android v3 handler when present. |
| maven/codenameone-maven-plugin/src/main/resources/com/codename1/builders/CN1FirebaseMessagingService.javas | Same as above for the non-“7” template. |
| maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/AndroidGradleBuilder.java | Adds Huawei Push Kit support and updates generated push callback routing to use explicit callback getter. |
| docs/developer-guide/Push-Notifications.asciidoc | Replaces push chapter with V3-first guidance (typed envelopes, client binding, managed/custom transports). |
| docs/developer-guide/developer-guide.asciidoc | Includes the new deprecated push chapter under historical reference. |
| docs/developer-guide/Deprecated-Push-Notifications.asciidoc | Restores legacy push docs as an explicitly deprecated chapter for migration reference. |
| CodenameOne/src/com/codename1/surfaces/Surfaces.java | Adds push-facing entry point to publish remote widget timelines. |
| CodenameOne/src/com/codename1/surfaces/LiveActivity.java | Adds push-facing entry points to update/end live activities by id. |
| CodenameOne/src/com/codename1/push/PushTransport.java | Introduces pluggable low-level native transport interface for custom push stacks. |
| CodenameOne/src/com/codename1/push/PushSubscription.java | Adds immutable model for transport-produced registration material and capabilities. |
| CodenameOne/src/com/codename1/push/PushRegistrationSink.java | Adds application-owned server sink interface for storing/removing subscriptions. |
| CodenameOne/src/com/codename1/push/PushMessage.java | Adds immutable typed v3 push envelope model + builder + JSON parse/serialize helpers. |
| CodenameOne/src/com/codename1/push/PushListener.java | Adds listener interface for registration/message/error callbacks on EDT. |
| CodenameOne/src/com/codename1/push/PushError.java | Adds structured error model for transport/registration/delivery failures. |
| CodenameOne/src/com/codename1/push/PushClient.java | Adds the core v3 client binding, managed registration/unregistration, and native dispatch entry points. |
| CodenameOne/src/com/codename1/push/Push.java | Points legacy server sends at BuildCloud compatibility endpoint. |
| CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java | Adds getPushCallback() accessor for ports/generated code to retrieve explicit callback binding. |
| .github/workflows/pr.yml | Runs the JS service-worker contract test in PR CI (Node) after core unit tests on Java 8. |
Files not reviewed (1)
- Ports/JavaScriptPort/src/main/webapp/sw.js: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
✅ ByteCodeTranslator Quality ReportTest & Coverage
Benchmark Results
Static Analysis
Generated automatically by the PR CI workflow. |
Cloudflare Preview
|
|
Compared 151 screenshots: 151 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
|
Compared 181 screenshots: 181 matched. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 37 out of 38 changed files in this pull request and generated 4 comments.
Files not reviewed (1)
- Ports/JavaScriptPort/src/main/webapp/sw.js: Generated file
Comments suppressed due to low confidence (2)
tests/push-e2e/service-worker-contract.mjs:1
- This test can produce misleading results if the service worker fails to register a
pushevent handler or doesn’t callevent.waitUntil():handlers.pushmay be undefined (throwing), orcompletionmay remain undefined andawait completionwill resolve immediately without validating any async work. Add an assertion thathandlers.pushis a function and thatwaitUntil()was called with a thenable/Promise before awaiting, so failures reliably indicate a broken service-worker contract.
scripts/run-push-e2e.sh:1 - If the targets file is missing a provider key,
jq -rwill yieldnulland the script will attempt to send a request with"token":"null", which can mask configuration mistakes and create hard-to-triage failures. Add an explicit check thattargetis non-empty and not"null"before constructing/sending the request, and fail the scenario/provider with a clear error message.
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 41 changed files in this pull request and generated 4 comments.
Files not reviewed (1)
- Ports/JavaScriptPort/src/main/webapp/sw.js: Generated file
Comments suppressed due to low confidence (1)
scripts/run-push-e2e.sh:1
- Bash exit codes are limited to 0-255; returning the raw failure count will wrap modulo 256 for larger values (and can mistakenly report success if it wraps to 0). Consider exiting with 0 when failures==0 and 1 otherwise (while still printing the total failure count to stderr/stdout).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 41 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- Ports/JavaScriptPort/src/main/webapp/sw.js: Generated file
Comments suppressed due to low confidence (2)
CodenameOne/src/com/codename1/push/PushClient.java:72
register()sets the global active client and installs the compatibility PushCallback before checking whether a custom transport is supported. Iftransport.isSupported()is false, this leaves the PushClient active and overwrites any existing callback even though registration cannot proceed, which can break legacy push behavior and misroute future native events.
public void register() {
active = this;
CodenameOneImplementation.setPushCallback(compatibilityCallback);
if (transport == null) {
Display.getInstance().registerPush();
} else if (!transport.isSupported()) {
fireError(new PushError("unsupported_transport", transport.getId() + " is unavailable", false));
} else {
transport.register(new TransportCallback());
}
Ports/JavaScriptPort/src/main/webapp/sw.js:143
notificationclickopensdata.deepLinkdirectly for v3 messages and then always posts the push payload to the window. This (1) allows a push payload to open arbitrary URLs (open-redirect/phishing risk) and (2) can duplicate delivery when the push was already posted to an existing client during the originalpushevent. Validate deep links to same-origin destinations and skip posting when the message ID was already delivered.
|
Compared 148 screenshots: 148 matched. Benchmark Results
Detailed Performance Metrics
|
|
Compared 217 screenshots: 217 matched. |
|
Compared 144 screenshots: 144 matched. |
|
Compared 149 screenshots: 149 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
|
Compared 143 screenshots: 143 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
1311f5b to
853c2ff
Compare
853c2ff to
e056224
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 41 changed files in this pull request and generated 2 comments.
Files not reviewed (1)
- Ports/JavaScriptPort/src/main/webapp/sw.js: Generated file
Comments suppressed due to low confidence (4)
CodenameOne/src/com/codename1/push/PushClient.java:220
- Managed unregistration should still be sent to BuildCloud even when a PushRegistrationSink is present (mirroring). Right now unregisterManaged() only runs when there is no sink, so managed subscriptions may remain active on the server when apps use a sink.
if (subscription != null && registrationSink != null) {
registrationSink.unregistered(subscription);
} else if (transport == null) {
unregisterManaged();
}
Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Push.java:168
- If the JS side generated a typed (V3) web registration id, calling _registerServerPush(id) here can register the device with the legacy push service unexpectedly (e.g., if the active PushClient was unregistered between starting registerPushNative() and receiving onSuccess). Gate the legacy registration path on the typedEnvelope decision captured when registration began.
PushCallback active = com.codename1.push.PushClient.getActiveCallback();
if (active != null) active.registeredForPush(id);
else HTML5Implementation.getInstance()._registerServerPush(id);
Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Push.java:181
- onPush() currently falls back to the legacy messageType decoder whenever PushClient.getActiveCallback() is null at delivery time. If registration began in typed-envelope mode, legacy decoding is incorrect; it should either dispatch to PushClient (when active) or no-op. Use the captured typedEnvelope flag to decide whether legacy decoding is allowed.
public void onPush(final JSObject data) {
if (com.codename1.push.PushClient.getActiveCallback() != null) {
com.codename1.push.PushClient.dispatch(stringify(data));
return;
}
Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Push.java:278
- For consistency (and to prevent a race where typedEnvelope is computed differently), pass the captured typedEnvelope flag through to registerPushNative() rather than recomputing PushClient.getActiveCallback() here.
registerPushNative(onSuccess, onFail, onPush, pushActionCategories,
com.codename1.push.PushClient.getActiveCallback() != null);
Summary
PushTransportWhy
The existing callback-only API cannot express modern push lifecycle, typed schema-3 payloads, custom native push transports, registration state, or system surfaces cleanly. The new API provides those bindings while retaining compatibility for existing applications during the server cutover.
Testing strategy
CI does not pretend to provide physical-device end-to-end delivery:
PushClient.dispatch()native seam and cover registration, unregistration, foreground/background delivery, cold-start replay, custom transports, and SurfacesCompanion services
This PR is designed to ship with BuildCloud PR #115 and BuildDaemon PR #156. BuildCloud preserves
/push/pushcompatibility so the standalone PushServer can be retired; BuildDaemon detects Push V3 usage and generates the corresponding native FCM, APNs, Huawei, and callback bindings.BuildCloud includes WNS provider delivery for explicit Windows targets. This client PR intentionally does not modify or revive the unsupported UWP port.
Validation
PushTest: 12 testsPushMessageTest: 5 testsPushClientTransportTest: 4 testsgit diff --check