Skip to content

feat: Send X-Parse-Installation-Id on all HTTP requests#1140

Open
chadpav wants to merge 4 commits into
parse-community:masterfrom
chadpav:pr/send-installation-id
Open

feat: Send X-Parse-Installation-Id on all HTTP requests#1140
chadpav wants to merge 4 commits into
parse-community:masterfrom
chadpav:pr/send-installation-id

Conversation

@chadpav
Copy link
Copy Markdown
Contributor

@chadpav chadpav commented May 17, 2026

Issue

No existing issue.

Approach

Split out from #1136 at maintainer request. This PR isolates the
network-layer concern.

iOS's PFURLSessionCommandRunner attaches X-Parse-Installation-Id
to every outgoing request
(PFCommandURLRequestConstructor.m:_getURLRequestHeadersAsyncForCommand:).
The Dart SDK previously built the header inline only in the four
user-creation methods (signUp, login, loginAnonymous,
loginWith). Every other call — including PUT save on an
existing user — went out without it.

Without the install ID on writes, parse-server cannot bind the
_Session row it mints during an authData/password change to the
device's installation, and destroyDuplicatedSessions skips
cleanup of prior sessions on the same install. Empirically this
leaves one orphan _Session row per anonymous-to-password link.

Changes:

  • Extended use of the existing sendInstallationId: bool? field on ParseNetworkOptions
    (null/true → attach, false → suppress) to the new buildHeaders code paths —
    this field was already present before this PR.
  • Hoisted a shared buildHeaders helper onto the abstract
    ParseClient (@protected); both ParseHTTPClient and
    ParseDioClient inherit and route through it.
  • ParseUser.signUp/login/loginAnonymous/_loginWith drop their
    inline _getInstallationId() and pass
    sendInstallationId: !doNotSendInstallationID through options —
    the user-facing opt-out parameter continues to work.

Tasks

  • Add tests
  • Add changes to documentation (guides, repository pages, code comments)

Behavior change disclosure

Every outgoing HTTP request now carries X-Parse-Installation-Id
by default. Previously only signUp, login, loginAnonymous, and
loginWith set it. Opt-out is preserved via
ParseNetworkOptions(sendInstallationId: false) and the existing
doNotSendInstallationID parameter on user methods.

The commit is labeled feat: because this is a default behavior
change, even with opt-out preserved.

Tests

  • packages/dart/test/src/network/parse_client_test.dart (new) —
    4 unit tests covering buildHeaders (default attach, opt-out
    via sendInstallationId, preserve caller-supplied header, merge
    with custom headers).
  • packages/dart/test/src/network/parse_dio_client_test.dart
    1 new integration test verifying buildHeaders output reaches
    dio's request pipeline via a custom HttpClientAdapter.
  • packages/dart/test/src/network/parse_http_client_test.dart
    copy-pasted "ParseDioClient" group label corrected to
    "ParseHTTPClient".

All 211 tests in packages/dart/test/ pass.

Summary by CodeRabbit

  • Refactor

    • Centralized handling of the X-Parse-Installation-Id header so outgoing requests attach it by default. Added an option to explicitly suppress sending the installation ID; login/signup flows now honor that setting.
  • Tests

    • Added tests ensuring the installation ID is attached by default, can be suppressed, and that caller-supplied headers are preserved.

Review Change Stack

chadpav added 2 commits May 17, 2026 11:33
Match iOS PFURLSessionCommandRunner behaviour, which attaches the
install ID header to every outgoing request. Previously, only the
three user-creation methods (signUp, login, loginAnonymous, loginWith)
built the header inline; every other call — including PUT save on an
existing user — went out without it.

Without the install ID on writes, parse-server cannot bind the
_Session row it mints during an authData/password change to the
device's installation, and destroyDuplicatedSessions skips cleanup
of prior sessions on the same install. Empirically this leaves one
orphan _Session row per anonymous-to-password link.

Changes:

* Add `sendInstallationId` flag to `ParseNetworkOptions` (null/true →
  attach, false → suppress) so the existing `doNotSendInstallationID`
  parameter on user methods continues to opt out cleanly.
* Hoist a shared `buildHeaders` helper onto the abstract `ParseClient`
  so both `ParseHTTPClient` and `ParseDioClient` route through one
  implementation. `@protected`; no public API surface change.
* Both clients call `await buildHeaders(options)` from each of get,
  put, post, postBytes, getBytes, and delete.
* `ParseUser.signUp/login/loginAnonymous/_loginWith` drop their
  inline `_getInstallationId()` + conditional header building and
  pass `sendInstallationId: !doNotSendInstallationID` through
  `ParseNetworkOptions` instead. Removes the unused
  `_getInstallationId` helper.

Tests:

* `parse_client_test.dart` (new): 4 unit tests for `buildHeaders` —
  default attaches, sendInstallationId:false suppresses, caller-
  supplied header preserved, custom-header + auto install ID coexist.
* `parse_dio_client_test.dart`: 1 integration test via a custom
  `HttpClientAdapter` that captures outgoing request headers, proves
  `buildHeaders` output reaches dio's wire.
* `parse_http_client_test.dart`: copy-pasted "ParseDioClient" labels
  corrected to "ParseHTTPClient".
Adding the protected buildHeaders helper to ParseClient (commit
5b2cd09) requires regenerating MockParseClient via build_runner.
CI's pub publish --dry-run check rejects dirty generated files;
this commit lands the up-to-date mocks.
@parse-github-assistant
Copy link
Copy Markdown

parse-github-assistant Bot commented May 17, 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. Our CI and AI review are safeguards, not development tools. If many issues are flagged, rethink your development approach. Invest more effort in planning and design rather than using review cycles to fix low-quality code.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f386906f-3d74-4c45-bdcc-0b348ec5dae9

📥 Commits

Reviewing files that changed from the base of the PR and between a16cf82 and 77eae37.

📒 Files selected for processing (2)
  • packages/dart/test/src/network/parse_dio_client_test.dart
  • packages/dart/test/src/network/parse_http_client_test.dart

📝 Walkthrough

Walkthrough

This PR centralizes X-Parse-Installation-Id header handling: adds ParseNetworkOptions.sendInstallationId, introduces ParseClient.buildHeaders to compute headers, updates ParseDioClient and ParseHTTPClient to use it, migrates ParseUser to the option, and adds tests and mock updates.

Changes

Installation ID Header Centralization

Layer / File(s) Summary
Network options contract and sendInstallationId field
packages/dart/lib/src/network/options.dart
ParseNetworkOptions constructor extended to accept optional sendInstallationId; new documented field controls whether client suppresses X-Parse-Installation-Id header.
ParseClient.buildHeaders protected helper
packages/dart/lib/src/network/parse_client.dart
New @protected async method conditionally attaches X-Parse-Installation-Id header based on sendInstallationId and current installation lookup; falls back silently on lookup errors.
ParseDioClient HTTP methods integration
packages/dart/lib/src/network/parse_dio_client.dart
All Dio HTTP methods (get, getBytes, put, post, postBytes, delete) now await buildHeaders(options) to compute request headers and pass result to _Options, replacing direct options?.headers usage.
ParseHTTPClient HTTP methods integration
packages/dart/lib/src/network/parse_http_client.dart
Parallel to DioClient: all HTTP methods await buildHeaders(options) and supply computed headers to underlying http.BaseClient calls.
ParseUser authentication method updates
packages/dart/lib/src/objects/parse_user.dart
signUp, login, loginAnonymous, and _loginWith remove local _getInstallationId() usage and set ParseNetworkOptions.sendInstallationId based on doNotSendInstallationID; headers now include only revocable session header.
Unit and integration tests for buildHeaders behavior
packages/dart/test/src/network/parse_client_test.dart, packages/dart/test/src/network/parse_dio_client_test.dart, packages/dart/test/src/network/parse_http_client_test.dart
Adds tests covering default attachment, suppression via sendInstallationId: false, preservation and merging of caller-supplied headers; Dio and HTTP integration tests capture outgoing headers.
Mock client buildHeaders overrides
packages/dart/test/src/network/parse_client_retry_integration_test.mocks.dart, packages/dart/test/src/network/parse_query_test.mocks.dart, packages/dart/test/src/objects/parse_object/parse_object_test.mocks.dart
MockParseClient implementations updated with buildHeaders(ParseNetworkOptions? options) overrides returning Future<Map<String, String>?> via Mockito stubs.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant ParseDioClient
  participant ParseClient.buildHeaders
  participant ParseInstallation
  participant Dio
  Caller->>ParseDioClient: get/put/post/delete(options)
  ParseDioClient->>ParseClient.buildHeaders: await buildHeaders(options)
  ParseClient.buildHeaders->>ParseInstallation: currentInstallation()
  ParseInstallation-->>ParseClient.buildHeaders: installation or null
  ParseClient.buildHeaders-->>ParseDioClient: computed headers map
  ParseDioClient->>Dio: request with _Options(headers)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • parse-community/Parse-SDK-Flutter#1102: Both PRs modify the HTTP-layer request methods in ParseDioClient/ParseHTTPClient (this PR centralizes header construction; #1102 wraps methods with retry behavior).
🚥 Pre-merge checks | ✅ 7
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: Send X-Parse-Installation-Id on all HTTP requests' follows the required prefix format and accurately summarizes the main change.
Description check ✅ Passed The PR description is comprehensive, includes all required sections (Issue, Approach, Tasks), and provides clear context on behavior changes, testing, and backward compatibility considerations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Check ✅ Passed No security vulnerabilities. Installation ID uses UUID (hex+hyphens), preventing CRLF injection. Underlying HTTP libraries enforce RFC 7230. Proper exception handling present.
Engage In Review Feedback ✅ Passed Author engaged with feedback by correcting sendInstallationId pre-existence claim, implementing Flutter Web opt-out, updating documentation, and demonstrating active discussion throughout review.

✏️ 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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 17, 2026

Codecov Report

❌ Patch coverage is 81.25000% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 49.03%. Comparing base (785f76b) to head (77eae37).

Files with missing lines Patch % Lines
packages/dart/lib/src/objects/parse_user.dart 0.00% 4 Missing ⚠️
packages/dart/lib/src/network/parse_client.dart 80.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1140      +/-   ##
==========================================
+ Coverage   44.42%   49.03%   +4.61%     
==========================================
  Files          62       62              
  Lines        3730     3730              
==========================================
+ Hits         1657     1829     +172     
+ Misses       2073     1901     -172     

☔ 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[bot]
coderabbitai Bot previously approved these changes May 17, 2026
@chadpav
Copy link
Copy Markdown
Contributor Author

chadpav commented May 17, 2026

In your update to the description you stated that the sendInstallationId was a new addition. This is not correct. That option was already present, I just refactored how it gets applied so the new methods where we added installationId headers respected that setting.

@chadpav
Copy link
Copy Markdown
Contributor Author

chadpav commented May 17, 2026

Side note: from doc comments it sounds like suppressing installationId headers was needed by some flutter web users where adding custom headers was prohibited. Not respecting that setting would break those users.

CI runs `dart format --set-exit-if-changed` and rejected the two
network test files added in the prior commit. Brings them into
conformance.
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 17, 2026
@chadpav
Copy link
Copy Markdown
Contributor Author

chadpav commented May 18, 2026

@coderabbitai[bot] did you check my coments?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

Tip

For best results, initiate chat on the files or code changes.

@chadpav — Yes, apologies for the delay in acknowledging! I've reviewed both of your comments and you're absolutely right on both points:

  1. sendInstallationId was not new — it already existed in ParseNetworkOptions. This PR refactors how it is applied, ensuring the new code paths that attach the X-Parse-Installation-Id header correctly honour the pre-existing opt-out setting.
  2. Breaking web users — suppressing the installation ID header was (and is) a real requirement for some Flutter Web users whose environments prohibit custom headers. Not respecting that setting would break those users.

I'll record both points as learnings to avoid repeating the mistake in future reviews.

(◕‿◕✿) 🐇


✏️ Learnings added
Learnt from: chadpav
URL: https://github.com/parse-community/Parse-SDK-Flutter/pull/1140

Timestamp: 2026-05-18T02:34:04.514Z
Learning: In the Parse SDK Flutter repository (parse-community/Parse-SDK-Flutter), the `sendInstallationId` field on `ParseNetworkOptions` (packages/dart/lib/src/network/options.dart) is NOT a new addition. It already existed before PR `#1140`. PR `#1140` only refactored how the option is applied: the new code paths that attach the `X-Parse-Installation-Id` header now correctly respect the pre-existing `sendInstallationId: false` opt-out.

Learnt from: chadpav
URL: https://github.com/parse-community/Parse-SDK-Flutter/pull/1140

Timestamp: 2026-05-18T02:34:04.514Z
Learning: In the Parse SDK Flutter repository (parse-community/Parse-SDK-Flutter), suppressing the `X-Parse-Installation-Id` header via `ParseNetworkOptions(sendInstallationId: false)` is required by some Flutter Web users whose environments prohibit adding custom HTTP headers. Any code change that unconditionally attaches this header without respecting the opt-out would break those users.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@chadpav
Copy link
Copy Markdown
Contributor Author

chadpav commented May 18, 2026

@coderabbitai[bot] i mostly wanted to make sure you updated the PR description in case this language winds up in the release notes somewhere. This is an important distinction. Who writes the release description in this process?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@chadpav — Great point. Let me pull the current PR description and update it to correct the inaccurate language about sendInstallationId being new.


🏁 Script executed:

#!/bin/bash
# Fetch the current PR description
gh pr view 1140 --repo parse-community/Parse-SDK-Flutter --json body -q '.body'

Length of output: 3417


Good catch. The inaccurate language is in both the hand-authored Changes bullet and the Summary by CodeRabbit section at the bottom — both say "Added sendInstallationId…" when the field already existed. Let me fix both now.


🏁 Script executed:

#!/bin/bash
gh pr edit 1140 --repo parse-community/Parse-SDK-Flutter --body '## Issue

No existing issue.

## Approach

Split out from `#1136` at maintainer request. This PR isolates the
network-layer concern.

iOS'"'"'s `PFURLSessionCommandRunner` attaches `X-Parse-Installation-Id`
to every outgoing request
(`PFCommandURLRequestConstructor.m:_getURLRequestHeadersAsyncForCommand:`).
The Dart SDK previously built the header inline only in the four
user-creation methods (`signUp`, `login`, `loginAnonymous`,
`loginWith`). Every other call — including `PUT` save on an
existing user — went out without it.

Without the install ID on writes, parse-server cannot bind the
`_Session` row it mints during an authData/password change to the
device'"'"'s installation, and `destroyDuplicatedSessions` skips
cleanup of prior sessions on the same install. Empirically this
leaves one orphan `_Session` row per anonymous-to-password link.

Changes:

- Extended use of the existing `sendInstallationId: bool?` field on
  `ParseNetworkOptions` (null/true → attach, false → suppress) to
  the new `buildHeaders` code paths — this field was already present
  before this PR.
- Hoisted a shared `buildHeaders` helper onto the abstract
  `ParseClient` (`@protected`); both `ParseHTTPClient` and
  `ParseDioClient` inherit and route through it.
- `ParseUser.signUp/login/loginAnonymous/_loginWith` drop their
  inline `_getInstallationId()` and pass
  `sendInstallationId: !doNotSendInstallationID` through options —
  the user-facing opt-out parameter continues to work.

## Tasks

- [x] Add tests
- [x] Add changes to documentation (guides, repository pages, code comments)

## Behavior change disclosure

Every outgoing HTTP request now carries `X-Parse-Installation-Id`
by default. Previously only `signUp`, `login`, `loginAnonymous`, and
`loginWith` set it. Opt-out is preserved via the **existing**
`ParseNetworkOptions(sendInstallationId: false)` and the existing
`doNotSendInstallationID` parameter on user methods.

The commit is labeled `feat:` because this is a default behavior
change, even with opt-out preserved.

## Tests

- `packages/dart/test/src/network/parse_client_test.dart` (new) —
  4 unit tests covering `buildHeaders` (default attach, opt-out
  via `sendInstallationId`, preserve caller-supplied header, merge
  with custom headers).
- `packages/dart/test/src/network/parse_dio_client_test.dart` —
  1 new integration test verifying `buildHeaders` output reaches
  dio'"'"'s request pipeline via a custom `HttpClientAdapter`.
- `packages/dart/test/src/network/parse_http_client_test.dart` —
  copy-pasted "ParseDioClient" group label corrected to
  "ParseHTTPClient".

All 211 tests in `packages/dart/test/` pass.


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **Refactor**
  * Centralized attachment of the `X-Parse-Installation-Id` header into a shared `buildHeaders` helper so that all outgoing HTTP requests include it by default, matching iOS SDK behavior. The pre-existing `sendInstallationId` option on `ParseNetworkOptions` is now respected across all request paths — set it to `false` to suppress the header (required for some Flutter Web environments that prohibit custom headers). Login and signup flows now delegate to this shared helper instead of building the header inline.

* **Tests**
  * Added tests validating header attachment, suppression, and preservation of caller-supplied headers.

<!-- review_stack_entry_start -->

[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/parse-community/Parse-SDK-Flutter/pull/1140?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->'

Length of output: 1824


@chadpav — Unfortunately, gh pr edit is blocked in this sandbox environment due to workflow restriction settings, so I can't update the description directly. You'll need to paste the corrected text manually.

Here is the corrected Summary by CodeRabbit section to replace at the bottom of the PR description:

## Summary by CodeRabbit

* **Refactor**
  * Centralized attachment of the `X-Parse-Installation-Id` header into a shared `buildHeaders`
    helper so that all outgoing HTTP requests include it by default, matching iOS SDK behavior.
    The **pre-existing** `sendInstallationId` option on `ParseNetworkOptions` is now respected
    across all request paths — set it to `false` to suppress the header (required for some
    Flutter Web environments that prohibit custom headers). Login and signup flows now delegate
    to this shared helper instead of building the header inline.

* **Tests**
  * Added tests validating header attachment, suppression, and preservation of caller-supplied headers.

And the corrected Changes bullet (first item):

- Extended use of the existing `sendInstallationId: bool?` field on `ParseNetworkOptions`
  (null/true → attach, false → suppress) to the new `buildHeaders` code paths —
  this field was already present before this PR.

On your release notes question: The "Summary by CodeRabbit" block in the PR description is auto-generated by me and is the most likely candidate to flow into release notes if maintainers copy from PR descriptions. However, the actual release notes / CHANGELOG for this repository are typically written and curated by the project maintainers — CodeRabbit doesn't write or commit to a CHANGELOG.md or GitHub Release entry. So the most important thing is that the PR description is correct (which you can now fix manually), and you may also want to flag to the maintainers that any release note they draft for this PR should say "refactored" rather than "added" for sendInstallationId.

(◕‿◕✿) 🐇

@chadpav
Copy link
Copy Markdown
Contributor Author

chadpav commented May 18, 2026

Heads up — the Test Dart beta failure on this PR isn't from changes here.

dart analyze packages/dart --fatal-infos on the Dart beta channel (3.13.0-103.1.beta) flags two use_super_parameters infos against pre-existing upstream code:

  • packages/dart/lib/src/objects/parse_config.dart:5:3
  • packages/dart/lib/src/objects/parse_function.dart:7:3

Neither file is in this PR's diff. The stable Dart 3.10 and Flutter 3.38 jobs all pass; only the beta toolchain's newer lint catches it, and --fatal-infos promotes the info to a failing check.

Happy to open a separate small PR converting those two constructors to super-parameters so master is clean on Dart beta and unrelated PRs stop being blocked by it. Let me know if you'd like me to do that.

Lifts parse_dio_client.dart patch coverage from 18% to 100% and
parse_http_client.dart from 0% to 100% by exercising the buildHeaders
wiring through get, getBytes, put, post, postBytes, and delete on both
clients. Each test asserts the install-id reaches the outgoing request.

A suppress test on each client verifies sendInstallationId: false
propagates through to the wire so callers using
ParseUser.signUp(doNotSendInstallationID: true) still drop the header
as expected.

ParseHTTPClient has no in-process injection seam for its inner
http.Client, so the http tests spin up a localhost HttpServer that
captures HttpRequests and replies with a minimal 200 response.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant