Skip to content

feat: add Windows and Linux support - #621

Open
SnowLukin wants to merge 26 commits into
PostHog:mainfrom
SnowLukin:feat/desktop-platform-support
Open

SnowLukin wants to merge 26 commits into
PostHog:mainfrom
SnowLukin:feat/desktop-platform-support

Conversation

@SnowLukin

@SnowLukin SnowLukin commented Sep 29, 2026 •

Copy link
Copy Markdown

Motivation and context

Adds Windows and Linux support to the Flutter SDK. Related to #51.

Applications use the existing Posthog().setup(config) API. Flutter registers the plugin automatically; no separate package or platform-specific setup is needed. The implementation ports PostHog core to Dart inside posthog_flutter.

  • Event, screen and exception capture, Flutter/Dart error autocapture, identity, alias, groups, super properties, feature flags, bootstrap and consent.
  • Persistent event queues and state, batching, retries, configurable compression, sessions, lifecycle events and app/OS context. Desktop storage uses the official Windows/Linux path_provider implementations with a separate directory per project.
  • Windows/Linux example runners, CI tests and release builds, documentation and a minor changeset.

Existing public APIs remain compatible. The only public API addition is PosthogFlutterDart.registerWith(), used by Flutter's plugin registrant.

Session replay, surveys, structured logs, push notifications and native crash capture are outside this implementation.

How did you test it?

I manually tested this on Windows and am already using it in an application.

  • Local checks: 1,194 tests passed, one skipped, on Flutter 3.47.0. 84 browser tests, one WebAssembly test and 25 compliance-adapter tests also passed. Analysis, formatting and the public API snapshot check passed.
  • Integration tests: the public Flutter API and desktop implementation send requests to a real local HTTP server. Coverage includes batching, metadata, compression, feature flag context and opt-out, with additional regressions for pending callbacks during project or identity changes, consent changes, reset during flag loading, disabled-only bootstrap and exposure deduplication. Storage tests cover application-support directory lookup, restart persistence, memory fallback, overlapping setup/close calls and consent restored before early captures.
  • Desktop integration: compiled the example's debug Dart bundles for Windows x64 and Linux x64 and verified Flutter registers both path-provider implementations automatically. Native release builds are covered by the Windows/Linux CI jobs.
  • CI baseline (39a7398): Windows/Linux tests and release example builds, Android/iOS/macOS/web builds, iOS plugin tests, general/browser tests, analysis, formatting, publish dry run and the public API snapshot all passed.

The Windows CI job also tests version-resource extraction from the built example executable under a Unicode path. The current CI run is awaiting approval to run.

Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the documentation.
  • No breaking changes; a minor changeset is included.

Agent context

Autonomy: Human-driven (agent-assisted)
DRI: @SnowLukin

Used Codex, GitHub CLI and Flutter tools for implementation, testing and review. Kept desktop support within the existing package and API.

@SnowLukin
SnowLukin requested a review from a team as a code owner September 29, 2026 11:29
@SnowLukin SnowLukin changed the title feat: поддержка Windows и Linux feat: add Windows and Linux support Sep 29, 2026
@dustinbyrne

Copy link
Copy Markdown
Contributor

thanks @SnowLukin, this is very exciting! we'll take a look at this 🙌

@dustinbyrne dustinbyrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looking great! i reviewed this with my agent and some findings below. we're happy to help get this over the line too - let us know if you'd like us to resolve any of these. thanks again!


Agent notes

The overall desktop implementation looks reasonable, but I would address the asynchronous client/consent/reset boundaries before shipping, and settle the persistence scope before applications start depending on it. I reproduced the event-routing, consent, stale-flags, bootstrap, exposure and close behaviors through the public Dart entry points with unchanged SDK source and loopback HTTP. These tests ran in a standalone harness on macOS, not native Windows/Linux. The storage-scope observation is source-supported; Windows FFI still needs direct runtime coverage.

Shared-debt follow-up: exposure invalidation

The exposure tracker retains all previously seen values across reloads. I reproduced two exposures for true→false→true, and no new exposure when experiment metadata changed without changing the value. This conflicts with the canonical tracker invalidation rule and is shared with other SDKs. I would track a coordinated follow-up rather than make a broad dedupe redesign a gate for this PR; unchanged-state reloads should remain deduplicated.

Comment thread posthog_flutter/lib/src/posthog_flutter_desktop.dart Outdated
Comment thread posthog_flutter/lib/src/posthog_flutter_desktop.dart Outdated
Comment thread posthog_flutter/lib/src/core/posthog_core.dart
Comment thread posthog_flutter/lib/src/core/posthog_core.dart Outdated
Comment thread posthog_flutter/lib/src/core/posthog_core.dart
Comment thread posthog_flutter/lib/src/posthog_desktop_storage.dart Outdated
Comment thread posthog_flutter/lib/src/core/posthog_core_stateless.dart
Comment thread posthog_flutter/lib/src/core/feature_flag_utils.dart
Comment thread posthog_flutter/lib/src/posthog_desktop_version_resource.dart
@dustinbyrne
dustinbyrne requested a review from a team September 29, 2026 19:03

@marandaneto marandaneto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Advisory review follow-up: adding the beforeSend reuse suggestion. The two blocking findings already have inline comments, so they are not duplicated here.

Comment thread posthog_flutter/lib/src/posthog_flutter_desktop.dart Outdated
@marandaneto
marandaneto requested a review from a team September 30, 2026 11:00

@marandaneto marandaneto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Non-blocking desktop persistence performance consideration.

void _writeAtomically(File file, String contents) {
final tmp = File('${file.path}.tmp');
try {
tmp.writeAsStringSync(contents);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: Consider asynchronous persistence to avoid blocking the UI isolate — these synchronous file writes/renames, together with queue reads/deletions, run on the calling isolate (normally Flutter's UI isolate). Slow disks, antivirus scanning or event bursts could delay frames; a Future-returning capture/flush method or timer does not move synchronous IO off that isolate. Dart already offers async writeAsString/readAsBytes/rename/delete APIs; awaiting them would yield to UI work without requiring a custom worker isolate. The change would need serialized snapshot writes (the temp filename is shared), coordinated queue reads/deletions, and explicit persistence-completion semantics for capture, consent and close. Alternatively, profile representative Windows/Linux workloads and document acceptance of the current tradeoff. Reproduction: not reproduced — no desktop frame-timing benchmark was run; this is a potential performance risk, not a measured UI-stall defect.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I kept disk I/O synchronous in this update and focused on correctness and directory discovery. Async persistence remains deferred; it needs coordinated writes and clear completion semantics for consent and shutdown.

Comment thread posthog_flutter/lib/src/core/PORTING_NOTES.md Outdated
import 'dart:math';
import 'dart:typed_data';

/// Generates a UUID version 7 (RFC 9562): a millisecond Unix timestamp

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

or we vendor this one https://github.com/daegalus/dart-uuid/blob/main/lib/v7.dart which is the most used in the dart lang
copying the license and header on top of the file

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I kept the current generator because queue filenames rely on UUIDs sorting in generation order, including within one millisecond and when the clock moves backwards. The linked implementation uses a random tail and does not provide that ordering guarantee, so replacing it directly would change queue ordering.

@marandaneto

Copy link
Copy Markdown
Member

i have a linux machine and can test this on linux EoW

@marandaneto

Copy link
Copy Markdown
Member

@PostHog/team-client-libraries any of you have a WIN machine?

@marandaneto
marandaneto requested a review from a team September 30, 2026 11:22

@marandaneto marandaneto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Non-blocking suggestion for desktop application-directory discovery.

Comment thread posthog_flutter/lib/src/posthog_desktop_storage.dart
@SnowLukin

Copy link
Copy Markdown
Author

@dustinbyrne @marandaneto

I've fixed the event, consent and feature-flag issues and added regression tests, including coverage for storage recovery and pending events across close/setup. Directory discovery now uses the official Windows/Linux path_provider implementations.

I left disk I/O synchronous and focused this update on correctness and directory discovery.

Maintainer edits are enabled, so feel free to push adjustments directly to this branch.

@SnowLukin

Copy link
Copy Markdown
Author

Regarding the shared exposure-invalidation follow-up:

I left metadata-aware exposure invalidation unchanged, following your suggestion to handle it as a coordinated SDK follow-up. The opted-out-read bug is fixed separately in this PR.

close() now makes one attempt to send the queued events, bounded by two
seconds, before it closes the client. A setup() that follows waits for that
close, so the next client gets the storage lock and the queue on disk instead
of falling back to memory.
Without beforeSend callbacks the event was still awaited, so an identify(),
reset() or opt-out made right after an unawaited capture, screen or
captureException ran first, and the event was dropped as belonging to another
user. Now it is captured synchronously, as on the other platforms. An event
dropped after its callbacks is now logged in debug mode.
Where /etc/localtime is a copy rather than a link into the zoneinfo
directory, as in many containers, $timezone was left out. Debian-based
systems still name the zone in /etc/timezone, which is now read as a
fallback.
The public close() docs now say that desktop sends the queue first, bounded
by two seconds, and what happens to the events left unsent. The core client
docs no longer promise that queued events survive close() with an in-memory
storage.
An event whose async beforeSend callbacks finished after close() and
before the next setup() completed found no client and was dropped, although
it belongs to the same project and user. It now waits for the setup in
progress. A repeated close() also waits until the queue is sent instead of
returning at once.
The three capture paths no longer repeat the wait for a setup in progress:
the helper that applies the callbacks does it, and the call sites only
await when callbacks ran.
PostHog always answers /flags/?v=2 with the flags key. The v1 fallback is
kept for servers that ignore v=2, matching posthog-js, iOS, Android and
Python, and now says so.
Tear-downs run in reverse order, so a directory created after the platform
was deleted while the platform still held its lock file open. Windows refuses
that deletion, which failed two setup tests there. The directory is now
created first.
@StarProxima

Copy link
Copy Markdown

Since CI here is waiting for approval, ran it on the fork. Windows and Linux are green, including a new integration test that runs the SDK inside the example app on both (context props, flag person properties, close, restart with a queued event): https://github.com/SnowLukin/posthog-flutter/actions/runs/37109227395

Two setup tests were failing on Windows only, the temp dir got deleted before the platform closed and released its lock file. Fixed the order

@marandaneto marandaneto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-review follow-up: one reproduced blocking issue remains.

Comment thread posthog_flutter/lib/src/core/posthog_core.dart
@marandaneto
marandaneto requested review from a team and dustinbyrne October 5, 2026 10:43
@marandaneto

Copy link
Copy Markdown
Member

Commits must have verified signatures.

mind signing your commits and force push?

group('company', 'free-company') after group('company', 'paid-company',
groupProperties: {'plan': 'pro'}) kept the paid company's properties, so the
next /flags request evaluated free-company with plan = pro. A new key for a
group type now clears the flag properties stored for that type before the
new ones are applied, as posthog-js does.
@StarProxima
StarProxima force-pushed the feat/desktop-platform-support branch from 8fdd633 to 266a0f5 Compare October 5, 2026 15:46
…rm-support

# Conflicts:
#	posthog_flutter/pubspec.yaml
@StarProxima

Copy link
Copy Markdown

@marandaneto done, all commits are signed and force pushed. The group properties issue is fixed too, and main is merged in

…rm-support

# Conflicts:
#	posthog_flutter/pubspec.yaml
@marandaneto

Copy link
Copy Markdown
Member

@marandaneto done, all commits are signed and force pushed. The group properties issue is fixed too, and main is merged in

thanks, i am attending nextappcon this week but @PostHog/team-client-libraries can have a final look and merge/release if all good
not sure if its yet but if not this should be marked as experimental since i believe none of us have a win machine to test evertything besides the unit/integration tests

@dustinbyrne

Copy link
Copy Markdown
Contributor

will verify this on windows tomorrow, otherwise this is looking good to me 👍

@dustinbyrne

Copy link
Copy Markdown
Contributor

will verify this on windows

just to update - still working on this

@dustinbyrne

Copy link
Copy Markdown
Contributor

I integrated this SDK into a separate Flutter application using a local path dependency on PR head 7f1721e3, then built and ran it on Windows Server 2025 x64 in debug and release modes. I verified captured events through PostHog’s query API, rather than relying only on client logs.

Windows smoke test passed. Auto-registration, capture, identity persistence, offline queue delivery, feature flags, and error capture worked. The PR’s unit tests also completed: 1,206 passed, 14 skipped.

Two non-blocking follow-ups:

  • Windows failure-path coverage: 13 tests skip because they simulate storage failures using POSIX chmod. Windows-compatible fixtures would improve coverage of storage and consent protection.
  • Degraded-storage diagnostics: an unreadable state file at initialization leaves tracking disabled for that storage instance. Failing closed protects persisted consent, but a clear warning and documentation would make this easier to diagnose.

One environment-specific observation: Application Opened wasn’t emitted at initial launch because Flutter’s lifecycle state remained null. Subsequent focus transitions worked correctly. This needs confirmation on a normal Windows 10/11 desktop before treating it as an SDK defect.

@dustinbyrne dustinbyrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

looks like everything's been addressed and i'd consider the findings from the windows smoke test to be non-blocking (we can always follow up with small fixes, improvements to test coverage, etc.)

thank you again @SnowLukin!!

This branch has not been deployed

No deployments
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.

4 participants