Skip to content

feat: Add experimental native APNs API#547

Merged
mtrezza merged 14 commits intoparse-community:masterfrom
mtrezza:feat/native-apns
Mar 31, 2026
Merged

feat: Add experimental native APNs API#547
mtrezza merged 14 commits intoparse-community:masterfrom
mtrezza:feat/native-apns

Conversation

@mtrezza
Copy link
Copy Markdown
Member

@mtrezza mtrezza commented Mar 31, 2026

Feature

Adds an opt-in native APNs HTTP/2 client as an alternative to @parse/node-apn. Uses only built-in Node.js modules (node:http2, node:crypto) with zero additional dependencies.

Enable with useNativeAPNs: true in the push config. Only supports token-based auth (.p8). The existing @parse/node-apn path remains the default and is unchanged.

Tasks

  • Add tests (55 new tests, 149 total pass)
  • Add changes to documentation

Summary by CodeRabbit

  • New Features

    • Experimental Native APNS: optional iOS push using token-based .p8 authentication; enable with ios.useNativeAPNs: true (certificate-based auth not supported).
  • Documentation

    • Added Native APNS docs and TOC entry, detailing token fields, topic, and production selection.
  • Bug Fixes

    • Fixed spelling of unknown error message to "Unknown status".
  • Tests

    • Large test coverage added for native APNS, token generation, connection handling, provider selection, sending, retries, and error paths.

@parse-github-assistant
Copy link
Copy Markdown

parse-github-assistant bot commented Mar 31, 2026

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Tip

  • Keep pull requests small. Large PRs will be rejected. Break complex features into smaller, incremental PRs.
  • Use Test Driven Development. Write failing tests before implementing functionality. Ensure tests pass.
  • Group code into logical blocks. Add a short comment before each block to explain its purpose.
  • We offer conceptual guidance. Coding is up to you. PRs must be merge-ready for human review.
  • Our review focuses on concept, not quality. PRs with code issues will be rejected. Use an AI agent.
  • Human review time is precious. Avoid review ping-pong. Inspect and test your AI-generated code.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 31, 2026

Codecov Report

❌ Patch coverage is 97.76786% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.18%. Comparing base (f48d889) to head (52d5775).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
src/APNSConnection.js 94.69% 12 Missing ⚠️
src/APNSNative.js 99.20% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##            master     #547      +/-   ##
===========================================
- Coverage   100.00%   99.18%   -0.82%     
===========================================
  Files            8       11       +3     
  Lines         1172     1841     +669     
===========================================
+ Hits          1172     1826     +654     
- Misses           0       15      +15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

Warning

Rate limit exceeded

@mtrezza has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 41 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 1 minutes and 41 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6c049a85-cdfc-43fd-a249-4505fdf94b29

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5500b and 52d5775.

📒 Files selected for processing (1)
  • src/APNSNative.js
📝 Walkthrough

Walkthrough

Adds a native APNS implementation using HTTP/2 and token-based ES256 authentication: new APNSToken (JWT generation/caching), APNSConnection (HTTP/2 session, retries, timeouts), APNSNative (provider orchestration, routing, retries), README docs, adapter integration, and comprehensive tests.

Changes

Cohort / File(s) Summary
Core Native APNS Implementation
src/APNSToken.js, src/APNSConnection.js, src/APNSNative.js
New modules: APNSToken generates/caches ES256 JWTs; APNSConnection manages HTTP/2 sessions, timeouts, retries, pings, and GOAWAY handling; APNSNative creates providers, builds APNs notifications, routes devices by topic, and performs multi-provider failover and token refresh.
Adapter Integration & Exports
src/ParsePushAdapter.js, src/index.js
ParsePushAdapter conditionally instantiates APNSNative when useNativeAPNs: true (and no firebaseServiceAccount); APNSNative added to named exports.
Tests
spec/APNSToken.spec.js, spec/APNSConnection.spec.js, spec/APNSNative.spec.js, spec/ParsePushAdapter.spec.js, spec/APNS.spec.js
Adds comprehensive unit/integration tests for token generation/refresh, HTTP/2 connection and retry flows, native provider behavior (notification generation, provider selection, retries), adapter initialization, and a spelling fix in APNS error assertions.
Docs
README.md
Added "Native APNS (Experimental)" documentation covering useNativeAPNs: true, token-based config (key, keyId, teamId, topic, production) and note that certificate-based auth is unsupported in this mode.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client Code
    participant Adapter as ParsePushAdapter
    participant Native as APNSNative
    participant Provider as Provider
    participant Token as APNSToken
    participant Conn as APNSConnection
    participant APNS as Apple APNS

    Client->>Adapter: send(data, devices)
    Adapter->>Native: send(data, devices)
    Native->>Native: group devices by appIdentifier
    Native->>Native: select providers by topic
    Native->>Native: _generateNotification(data, headers)
    Native->>Provider: sendThroughProvider(notification, devices)
    loop per-device
        Provider->>Token: token.current
        alt token expired
            Token->>Token: regenerate JWT
        end
        Provider->>Conn: send(deviceToken, headers, payload, authToken)
        Conn->>APNS: POST /3/device/{deviceToken} + Bearer
        APNS-->>Conn: HTTP/2 response
        alt 200
            Conn->>Provider: success
        else 403 + ExpiredProviderToken
            Provider->>Token: refresh()
            Token->>Token: regenerate JWT
            Provider->>Conn: resend with new token
        else 5xx/408/429
            Conn->>Conn: retry with backoff
        else timeout (status 0)
            Conn->>Conn: cancel stream, return RequestTimeout
        end
    end
    Provider->>Native: return {sent, failed}
    Native->>Adapter: aggregated results
    Adapter->>Client: return results
Loading
sequenceDiagram
    participant Conn as APNSConnection
    participant Session as HTTP/2 Session
    participant Handler as Request Handler

    Conn->>Conn: first send() -> _getSession()
    Conn->>Session: http2.connect(endpoint)
    Session-->>Conn: connected
    Conn->>Session: start periodic ping (unref)
    Conn->>Handler: create POST request + write body
    Session-->>Handler: response chunks
    Handler->>Handler: aggregate & parse response
    Handler->>Conn: resolve {status, body}
    Note over Session,Conn: goaway -> mark draining, next send() creates new session
    Note over Conn: destroy() stops ping, destroys session, _session = null
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • dplewis
🚥 Pre-merge checks | ✅ 3 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Human Review ⚠️ Warning Review comments identified four critical code issues that remain unaddressed: bundleId can override explicit topic, nullish coalescing not applied, snake_case fields not filtered, and truthiness checks for falsy-valid fields. Apply suggested fixes: add !providerArgs.topic condition, replace || with ??, filter snake_case fields, and use explicit null checks for falsy-valid fields.
Engage In Review Feedback ⚠️ Warning The pull request has not engaged with the review feedback comments. Code changes addressing the flagged review issues have not been implemented. Author should implement suggested fixes from review comments or engage in discussion with reviewers to explain design rationale.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title begins with the required 'feat:' prefix and clearly describes the main change: adding experimental native APNs API support.
Description check ✅ Passed The PR description addresses the core feature and includes completed tasks from the template, though Issue and Approach sections lack detail.
Security Check ✅ Passed PR introduces secure implementations for handling sensitive data and communications with TLS validation, secure key storage, and proper JWT token generation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
spec/APNSToken.spec.js (1)

11-22: Consider adding ESLint environment directive for test globals.

The static analysis warns that beforeAll and afterAll are not defined. These are valid Jest/Jasmine globals, but ESLint may not recognize them without proper configuration.

🔧 Optional: Add ESLint directive at top of file
+/* eslint-env jest */
 import { generateKeyPairSync } from 'node:crypto';
 import { writeFileSync, mkdtempSync, rmSync } from 'node:fs';

Alternatively, ensure jest is configured in the ESLint environment for spec files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/APNSToken.spec.js` around lines 11 - 22, ESLint is flagging the Jest
globals beforeAll and afterAll as undefined; add an ESLint environment directive
or enable the jest environment for this spec so those globals are recognized.
Specifically, add the eslint-env jest directive at the top of this test file or
update your ESLint config to include "jest" for spec files so functions like
beforeAll, afterAll (and other Jest globals) are treated as defined.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@spec/APNSNative.spec.js`:
- Around line 451-458: The error message string "Unkown status" contains a typo;
search for that exact literal in APNSNative._handlePushFailure and
APNS._handlePushFailure and update it to "Unknown status" in both implementation
files, and update any tests (e.g., the assertion in APNSNative.spec.js) that
expect the misspelled string so they assert "Unknown status" instead; run tests
to confirm the fix.

In `@src/APNSConnection.js`:
- Around line 46-55: The callbacks in _request and other handlers are tearing
down this._session unconditionally; change them to only teardown/clear the
connection if the session instance that failed matches the active session
(compare the closed/failing session variable to this._session before calling
this._destroySession() or setting this._session = null). Locate uses in _request
(the try/catch around session.request), the request/error/ping callbacks and
ensure each path checks if (this._session === session) before destroying or
clearing; otherwise only handle retry/reject for that specific session without
touching the current active session.

In `@src/APNSNative.js`:
- Around line 133-139: The code buckets devices by appIdentifier into a plain
object (devicesPerAppIdentifier) using untrusted keys (appIdentifier), which can
collide with prototype properties like __proto__ and break .push(); fix this by
replacing the plain object with a safe map-style container (e.g., use a Map or
an object created via Object.create(null)) and update the grouping logic that
iterates allDevices and accesses appIdentifier so it uses the safe container’s
get/set or safe own-property checks when creating and pushing arrays for each
key (update references to devicesPerAppIdentifier and the forEach grouping code
accordingly).
- Around line 341-346: The code currently treats failure.status falsily so
status === 0 (used by APNSConnection for timeouts/network errors) is lost;
update the conditional in APNSNative where it checks failure.status to use an
explicit undefined check (e.g., failure.status !== undefined) so status 0 is
preserved, keep checking failure.response && failure.response.reason, and call
APNSNative._createErrorPromise(failure.device, failure.response.reason) as
before; also fix the typo 'Unkown' to 'Unknown' in the fallback log/message so
the unknown-status branch reads clearly.

---

Nitpick comments:
In `@spec/APNSToken.spec.js`:
- Around line 11-22: ESLint is flagging the Jest globals beforeAll and afterAll
as undefined; add an ESLint environment directive or enable the jest environment
for this spec so those globals are recognized. Specifically, add the eslint-env
jest directive at the top of this test file or update your ESLint config to
include "jest" for spec files so functions like beforeAll, afterAll (and other
Jest globals) are treated as defined.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d5a5de8a-6b4f-4d66-b1f9-0e935a37b21d

📥 Commits

Reviewing files that changed from the base of the PR and between f48d889 and f9fb6a6.

📒 Files selected for processing (10)
  • README.md
  • spec/APNSConnection.spec.js
  • spec/APNSNative.spec.js
  • spec/APNSToken.spec.js
  • spec/ParsePushAdapter.spec.js
  • src/APNSConnection.js
  • src/APNSNative.js
  • src/APNSToken.js
  • src/ParsePushAdapter.js
  • src/index.js

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/APNSNative.js`:
- Around line 38-42: The migration shim currently unconditionally copies
providerArgs.bundleId into providerArgs.topic; change it so the deprecated alias
does not override an explicit topic by only backfilling when providerArgs.topic
is null/undefined. In the block that references providerArgs, bundleId and
LOG_PREFIX, check if providerArgs.topic is absent (e.g., === undefined or ==
null) before assigning providerArgs.topic = providerArgs.bundleId, and emit the
deprecation log only when you actually use bundleId to set topic.
- Around line 127-130: Replace falsy-driven `||` checks with nullish coalescing
(`??`) for header fields (expirationTime, collapseId, pushType, priority) and in
the header-building logic so legitimate values like 0 are preserved; update all
presence checks to use `!= null` rather than truthiness. In
_generateNotification (the switch/filter that builds the APNs payload) add the
snake_case variants expiration_time, collapse_id, and push_type to the cases
that are excluded from the payload (they should remain only in headers). Ensure
the header construction code that reads those fields uses `??` and `!= null`
checks consistently so snake_case inputs don’t leak into the final notification
body.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f7c7ba5b-d966-4eac-b62c-55b48df1398f

📥 Commits

Reviewing files that changed from the base of the PR and between f9fb6a6 and 5c5500b.

📒 Files selected for processing (6)
  • spec/APNS.spec.js
  • spec/APNSConnection.spec.js
  • spec/APNSNative.spec.js
  • src/APNS.js
  • src/APNSConnection.js
  • src/APNSNative.js
✅ Files skipped from review due to trivial changes (2)
  • src/APNS.js
  • spec/APNS.spec.js

@mtrezza mtrezza merged commit cabcd54 into parse-community:master Mar 31, 2026
7 of 9 checks passed
parseplatformorg pushed a commit that referenced this pull request Mar 31, 2026
# [8.4.0](8.3.1...8.4.0) (2026-03-31)

### Features

* Add experimental native APNs API ([#547](#547)) ([cabcd54](cabcd54))
@parseplatformorg
Copy link
Copy Markdown
Contributor

🎉 This change has been released in version 8.4.0

@mtrezza mtrezza deleted the feat/native-apns branch March 31, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants