Repository navigation
feat(microsoft-store): Microsoft Store purchases, app license, trial and updates - #891
Merged
Merged
Conversation
Windows backend over Windows.Services.Store, forked from tauri-plugin-iap and decoupled from Tauri: products, purchase, consumables, subscriptions, license change updates. macOS is not supported yet and Linux has no store; both answer Unsupported from Kotlin, with no native library.
InAppPurchase.appLicense() reads StoreAppLicense (active, trial, time left, trial unique id, SKU), purchaseApp() buys the app itself to convert a trial, and appLicenseUpdates reports license state changes from OfflineLicensesChanged. An install the Store did not license reads as an active non-trial license without a SKU, so isFullLicense also requires one.
… Microsoft Store system.purchases drives in-app-purchase on Windows: app license and trial, full-version purchase, add-ons, consumables, purchases and the updates the Store sends unasked. The Lab's AppX identity moves to its own reservation, KDroidFilter.NucleusLab, which the probe is verified against.
javaaccessbridge.dll imports jawt.dll, which Windows resolves from the executable's directory, but the plugin only copied it to bin/ for Skiko. With Java Access Bridge enabled (screen readers, jabswitch -enable) AWT failed at startup with UnsatisfiedLinkError: javaaccessbridge, unless a JDK on PATH happened to provide a jawt.dll of its own. Seen on the Nucleus Lab installed from the Microsoft Store.
Verified on the Nucleus Lab installed from the Microsoft Store (7-day trial): TrialTimeRemaining reports the unlimited sentinel for a time-limited trial, so trialTimeRemaining read null and the trial looked unlimited. It is now derived from ExpirationDate whenever there is one. The same install shows TrialUniqueId empty and IsTrialOwnedByThisUser false: both only describe usage-limited trials, which the KDoc now says. It also confirms a Store install carries a SkuStoreId (9MV8ZRKS4KT4/0011).
On the Nucleus Lab installed from the Microsoft Store, converting the trial to a full license never produced an app license update. The OfflineLicensesChanged subscription was made on the thread that first collected an update flow, the UI thread, an STA (tao's OleInitialize), where RoInitialize(MULTITHREADED) fails and the StoreContext stayed in that apartment. It is now made from a dedicated MTA thread, and a Rust test subscribes from an STA and checks the apartment. Every license notification is also logged at FINE, and the Lab probe lists those lines, so a real install shows whether notifications arrive.
StoreUpdates checks the Store for a newer version of the app (GetAppAndOptionalStorePackageUpdatesAsync: version and mandatory flag), installs it from inside the app with the Store's dialog or silently, streaming StorePackageUpdateStatus progress as a Flow, and opens the app's Store page. It shares the purchase bridge: progress travels as updateProgress events routed to the request that asked, and cancelling the collection cancels the download. New StoreErrorCode.UpdateBlocked for an update the Store holds until charging or Wi-Fi. The Lab gets a Store updates probe to verify it on the real listing.
On the Nucleus Lab installed from the Microsoft Store, the purchase dialog offered an owned durable add-on, and the owned app itself (full license, SKU 0010), for sale again, asking to pay a second time. Microsoft documents that a non-expiring durable can only be purchased once and recommends checking the license before offering a purchase; no other library checks first. purchase() (non-consumables and subscriptions) and purchaseApp() now read this device's licenses before any window and answer Success(alreadyOwned = true). A device whose licenses have not synced yet still gets the Store's dialog.
The first in-app update of the Nucleus Lab from the Microsoft Store (1.0.4 -> 1.0.5) installed, closed the app and left it closed. Microsoft documents the relaunch of a desktop app the Store updates as opt-in through RegisterApplicationRestart. StoreUpdates.install now registers the app right before installing, for updates only (no crash, hang or reboot restarts), and unregisters when the install returns without replacing the process. Windows skips it for an app that ran under 60 s. The KDoc no longer claims Windows relaunches the app on its own.
The module covers more than purchases now (app license, trial, Store updates) and only ever targets the Microsoft Store, so it is named after it: - module microsoft-store, artifact nucleus.microsoft-store - package dev.nucleusframework.store.microsoft, native lib nucleus_msstore - one root object: MicrosoftStore.purchases (list/restore replace purchases/restorePurchases) and MicrosoftStore.updates
kdroidFilter
marked this pull request as ready for review
October 31, 2026 21:23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #566 — Windows only. Draft until the last Store checks below are done.
What
New runtime module
nucleus.microsoft-store, backed byWindows.Services.Store(Rust, forked from tauri-plugin-iap, MIT, notice inTHIRD_PARTY_NOTICES.md§8). One entry point,MicrosoftStore:MicrosoftStore.purchasesproducts,purchase,list/restore,entitlement,consume(crash-safe, idempotent fulfilment),purchaseUpdates(renewals, refunds, purchases made elsewhere).appLicense()(active / trial / time left / SKU),purchaseApp()to convert a trial,appLicenseUpdates.Success(alreadyOwned = true)without showing the Store, which was seen offering them for sale again.MicrosoftStore.updates:check(),install()(progress flow, dialog or silent, registers the app for restart so Windows relaunches it),openStorePage().Unsupported.Also in this PR: the GraalVM Windows image now ships
jawt.dllnext to the exe (javaaccessbridge.dllimports it; without it a Store-installed native image crashed at launch), and the Lab getssystem.purchasesandsystem.store-updatesprobes and an MSIX identity for its Store listing.Notable
CoIncrementMTAUsage), Rust workers attach to the JVM with a scoped guard, and the license listener is registered from an MTA thread (the Tao UI thread is an STA). Details indocs/internals/microsoft-store.md.AppLicense.isFullLicenserequires askuStoreId.TrialTimeRemainingonly applies to usage-limited trials; a time-limited trial's time left comes fromExpirationDate.Verification
./gradlew :microsoft-store:check, includingWindowsNativeTortureTeston the real DLL and Store API.alreadyOwnedguard, consumables, subscription purchase, refunds.