@symbiote-native/background-fetch 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A. Prokopenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # @symbiote-native/background-fetch
2
+
3
+ A wrapper package for [SymbioteNative](../../README.md) that makes
4
+ [`expo-background-fetch`](https://github.com/expo/expo/tree/main/packages/expo-background-fetch)
5
+ usable from **every** adapter — React, Vue, Svelte, Solid, and Angular. Like
6
+ [`@symbiote-native/task-manager`](../task-manager), every export is a plain async function, so
7
+ there is no hook/composable/service to wrap: the React, Vue, Svelte, Solid, and Angular entry
8
+ points are plain re-exports of the same `core`.
9
+
10
+ **Upstream deprecated this API in favor of `expo-background-task`** — Apple and Google are both
11
+ moving away from periodic-fetch-style scheduling toward task-scheduling APIs
12
+ (`BGTaskScheduler` / `WorkManager`). It is ported here anyway, alongside
13
+ [`@symbiote-native/background-task`](../background-task), because Expo still ships both
14
+ simultaneously as of sdk-57 and an app already built against the old API needs a path to run on
15
+ SymbioteNative too. Prefer `@symbiote-native/background-task` for new code.
16
+
17
+ This package registers a task with native so it fires **periodically in the background** — it
18
+ does not define what the task does. Define the task first via
19
+ [`@symbiote-native/task-manager`](../task-manager)'s `defineTask`, then register it for periodic
20
+ execution via this package's `registerTaskAsync`.
21
+
22
+ ## Install
23
+
24
+ **New app:**
25
+
26
+ ```bash
27
+ npx @symbiote-native/cli new my-app --background-fetch
28
+ ```
29
+
30
+ **Existing SymbioteNative app:**
31
+
32
+ ```bash
33
+ npx @symbiote-native/cli add --background-fetch
34
+ ```
35
+
36
+ Either way: installs `@symbiote-native/background-fetch` + `@symbiote-native/task-manager` and
37
+ wires the native autolinking automatically — see [`@symbiote-native/cli`](../cli).
38
+
39
+ <details>
40
+ <summary>Manual install (no CLI — installing and wiring native autolinking by hand)</summary>
41
+
42
+ ```bash
43
+ npm install @symbiote-native/background-fetch @symbiote-native/task-manager
44
+ ```
45
+
46
+ `expo-background-fetch` and `expo-modules-core` come along as regular dependencies, pinned to
47
+ exact versions — never install them yourself, and never add the `expo` meta-package to your
48
+ project.
49
+
50
+ ## Required one-time step: native autolinking wiring
51
+
52
+ Same one-time step as every other `expo-modules-core` package this project ships — see
53
+ [`@symbiote-native/local-auth`'s README](../local-auth/README.md#required-one-time-step-native-autolinking-wiring)
54
+ and the `symbiote-expo-native-module` project skill.
55
+
56
+ iOS also needs `UIBackgroundModes: fetch` in the app's Info.plist —
57
+ `native-link.json`'s `ios.infoPlistArrayKeys` covers this ARRAY-valued case (see
58
+ `@symbiote-native/expo-modules-link`), so it's wired automatically by the same postinstall step,
59
+ no manual edit needed. Android needs no manual step — `RECEIVE_BOOT_COMPLETED` and `WAKE_LOCK`
60
+ ship in `expo-background-fetch`'s own `AndroidManifest.xml` and merge automatically once the
61
+ package is installed.
62
+
63
+ </details>
64
+
65
+ ## Shape
66
+
67
+ ```
68
+ src/core/ getStatusAsync / setMinimumIntervalAsync / registerTaskAsync / unregisterTaskAsync,
69
+ plus BackgroundFetchResult / BackgroundFetchStatus / IBackgroundFetchOptions.
70
+ native-module.ts resolves the native module via expo-modules-core's
71
+ requireNativeModule.
72
+ src/angular/ @symbiote-native/background-fetch/angular — export * from '../core'
73
+ ```
74
+
75
+ `./react`, `./vue`, `./svelte`, and `./solid` are `exports`-map aliases straight onto
76
+ `src/core/`. `./angular` stays a physical file/subpath since Angular ships through a separate
77
+ `ngc`/AOT build (`build-ngc/`).
78
+
79
+ ## Use it
80
+
81
+ ```ts
82
+ // index.ts, alongside AppRegistry.registerComponent — identical on every adapter
83
+ import { defineTask } from '@symbiote-native/task-manager';
84
+ import {
85
+ registerTaskAsync,
86
+ BackgroundFetchResult,
87
+ } from '@symbiote-native/background-fetch';
88
+
89
+ const SYNC_TASK = 'background-sync';
90
+
91
+ defineTask(SYNC_TASK, async ({ data, error }) => {
92
+ if (error) {
93
+ console.error('background-sync failed:', error);
94
+ return BackgroundFetchResult.Failed;
95
+ }
96
+ const receivedNewData = await runSync(data);
97
+ return receivedNewData
98
+ ? BackgroundFetchResult.NewData
99
+ : BackgroundFetchResult.NoData;
100
+ });
101
+
102
+ // Somewhere after the task is defined (a settings screen, app bootstrap, …):
103
+ await registerTaskAsync(SYNC_TASK, { minimumInterval: 900 });
104
+ ```
105
+
106
+ `@symbiote-native/task-manager` is the primitive both this package and
107
+ [`@symbiote-native/background-task`](../background-task) build on — `defineTask` lives there,
108
+ `registerTaskAsync`/`unregisterTaskAsync` live here. `defineTask` must run at the top of the JS
109
+ bundle, outside any component, for the same reason documented in task-manager's own README: the
110
+ app can be launched headlessly to run a background task, with no views mounted.
111
+
112
+ ```ts
113
+ import {
114
+ getStatusAsync,
115
+ unregisterTaskAsync,
116
+ } from '@symbiote-native/background-fetch';
117
+
118
+ const status = await getStatusAsync();
119
+ await unregisterTaskAsync(SYNC_TASK); // stop receiving background-fetch callbacks for it
120
+ ```
121
+
122
+ Identical import surface on every adapter — `@symbiote-native/background-fetch/react`,
123
+ `/vue`, `/svelte`, `/solid`, `/angular` all re-export the same functions.
124
+
125
+ ## API
126
+
127
+ ```ts
128
+ getStatusAsync(): Promise<BackgroundFetchStatus | null>
129
+ setMinimumIntervalAsync(minimumInterval: number): Promise<void>
130
+ registerTaskAsync(taskName: string, options?: IBackgroundFetchOptions): Promise<void>
131
+ unregisterTaskAsync(taskName: string): Promise<void>
132
+ ```
133
+
134
+ Plus `BackgroundFetchResult`, `BackgroundFetchStatus`, `IBackgroundFetchOptions` — ported from
135
+ upstream's `BackgroundFetch.types.ts`, the options type renamed with this repo's `I`-prefix
136
+ convention for exported types (`ts-js-best-practices`).
137
+
138
+ ## Notes
139
+
140
+ - **Every function warns once, on first call, that this API is deprecated** — matching upstream's
141
+ own `showDeprecationWarning`. It still works; the warning is a nudge toward
142
+ `@symbiote-native/background-task`, not a functional restriction.
143
+ - **`registerTaskAsync` requires the task to already be defined.** It throws if
144
+ `@symbiote-native/task-manager`'s `isTaskDefined(taskName)` is `false` — call `defineTask`
145
+ first.
146
+ - **`getStatusAsync` shortcuts to `Available` on Android without calling native at all** —
147
+ matches upstream, which has no Android-side status concept (the native call exists only on
148
+ iOS).
149
+ - **`setMinimumIntervalAsync` silently no-ops when native lacks the method** (Android has no
150
+ equivalent call) rather than throwing — matches upstream.
151
+ - **Expo Go is out of scope.** Upstream also warns when running inside Expo Go
152
+ (`isRunningInExpoGo`, imported from the `expo` meta-package). This project never installs
153
+ `expo` — every app here is a bare/dev-client build, never Expo Go — so that check has no
154
+ equivalent here and is intentionally not ported.
155
+
156
+ ## Test it
157
+
158
+ No Fabric/Descriptor angle at all — every function here is a pure async-function surface, never a
159
+ view or per-instance state. Tests inject a fake native-module object in place of the real
160
+ `requireNativeModule` resolution and a fake `@symbiote-native/task-manager` module
161
+ (`src/core/background-fetch.test.ts`) — no `installFabric()`, no ViewConfig.
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/background-fetch/angular: the Angular entry over the framework-agnostic core.
2
+ // Same reasoning as the React/Vue entries — no per-instance state or event stream to wrap in a
3
+ // service, so this is a plain re-export.
4
+ export * from '../core/index.js';
@@ -0,0 +1,18 @@
1
+ import { BackgroundFetchStatus } from './types';
2
+ import type { IBackgroundFetchOptions } from './types';
3
+ /** Gets the current background-fetch status, or `null` if the native module is unreachable. */
4
+ export declare function getStatusAsync(): Promise<BackgroundFetchStatus | null>;
5
+ /**
6
+ * Sets the minimum number of seconds that must elapse before another background fetch can be
7
+ * initiated. Advisory only — iOS treats it as a floor, not an exact schedule. No effect on
8
+ * Android.
9
+ */
10
+ export declare function setMinimumIntervalAsync(minimumInterval: number): Promise<void>;
11
+ /**
12
+ * Registers a background-fetch task with the given name. The task must already be defined via
13
+ * `@symbiote-native/task-manager`'s `defineTask` — registration is driven by this package, but
14
+ * execution dispatch is task-manager's job.
15
+ */
16
+ export declare function registerTaskAsync(taskName: string, options?: IBackgroundFetchOptions): Promise<void>;
17
+ /** Unregisters a background-fetch task, so the app stops receiving fetch callbacks for it. */
18
+ export declare function unregisterTaskAsync(taskName: string): Promise<void>;
@@ -0,0 +1,70 @@
1
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
2
+ import { isTaskDefined } from '@symbiote-native/task-manager';
3
+ import { expoBackgroundFetch } from './native-module.js';
4
+ import { BackgroundFetchStatus } from './types.js';
5
+ const NATIVE_MODULE_NAME = 'BackgroundFetch';
6
+ // expo-background-fetch is upstream-deprecated in favor of expo-background-task (still shipped
7
+ // in sdk-57 alongside it, same as this project's file-system legacy+modern split) — carry the
8
+ // same one-time console warning upstream shows, so an app that adopted the old API notices.
9
+ let didShowDeprecationWarning = false;
10
+ function warnDeprecated() {
11
+ if (didShowDeprecationWarning)
12
+ return;
13
+ didShowDeprecationWarning = true;
14
+ console.warn('@symbiote-native/background-fetch: this API is deprecated. Use @symbiote-native/background-task instead.');
15
+ }
16
+ function assertValidTaskName(taskName) {
17
+ if (!taskName || typeof taskName !== 'string') {
18
+ throw new TypeError('`taskName` must be a non-empty string.');
19
+ }
20
+ }
21
+ // Upstream also warns when running inside Expo Go (`isRunningInExpoGo`, imported from the `expo`
22
+ // meta-package). This project never installs `expo` — every app here is a bare/dev-client build,
23
+ // never Expo Go — so that check has no equivalent and is intentionally not ported.
24
+ /** Gets the current background-fetch status, or `null` if the native module is unreachable. */
25
+ export async function getStatusAsync() {
26
+ warnDeprecated();
27
+ if (Platform.OS === 'android') {
28
+ return BackgroundFetchStatus.Available;
29
+ }
30
+ if (!expoBackgroundFetch.getStatusAsync) {
31
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getStatusAsync');
32
+ }
33
+ return expoBackgroundFetch.getStatusAsync();
34
+ }
35
+ /**
36
+ * Sets the minimum number of seconds that must elapse before another background fetch can be
37
+ * initiated. Advisory only — iOS treats it as a floor, not an exact schedule. No effect on
38
+ * Android.
39
+ */
40
+ export async function setMinimumIntervalAsync(minimumInterval) {
41
+ warnDeprecated();
42
+ if (!expoBackgroundFetch.setMinimumIntervalAsync)
43
+ return;
44
+ await expoBackgroundFetch.setMinimumIntervalAsync(minimumInterval);
45
+ }
46
+ /**
47
+ * Registers a background-fetch task with the given name. The task must already be defined via
48
+ * `@symbiote-native/task-manager`'s `defineTask` — registration is driven by this package, but
49
+ * execution dispatch is task-manager's job.
50
+ */
51
+ export async function registerTaskAsync(taskName, options = {}) {
52
+ warnDeprecated();
53
+ if (!expoBackgroundFetch.registerTaskAsync) {
54
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'registerTaskAsync');
55
+ }
56
+ if (!isTaskDefined(taskName)) {
57
+ throw new Error(`Task '${taskName}' is not defined. You must define a task using defineTask (from @symbiote-native/task-manager) before registering.`);
58
+ }
59
+ assertValidTaskName(taskName);
60
+ await expoBackgroundFetch.registerTaskAsync(taskName, options);
61
+ }
62
+ /** Unregisters a background-fetch task, so the app stops receiving fetch callbacks for it. */
63
+ export async function unregisterTaskAsync(taskName) {
64
+ warnDeprecated();
65
+ if (!expoBackgroundFetch.unregisterTaskAsync) {
66
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'unregisterTaskAsync');
67
+ }
68
+ assertValidTaskName(taskName);
69
+ await expoBackgroundFetch.unregisterTaskAsync(taskName);
70
+ }
@@ -0,0 +1,3 @@
1
+ export { getStatusAsync, setMinimumIntervalAsync, registerTaskAsync, unregisterTaskAsync, } from './background-fetch';
2
+ export { BackgroundFetchResult, BackgroundFetchStatus } from './types';
3
+ export type { IBackgroundFetchOptions } from './types';
@@ -0,0 +1,2 @@
1
+ export { getStatusAsync, setMinimumIntervalAsync, registerTaskAsync, unregisterTaskAsync, } from './background-fetch.js';
2
+ export { BackgroundFetchResult, BackgroundFetchStatus } from './types.js';
@@ -0,0 +1,8 @@
1
+ export type INativeBackgroundFetchModule = {
2
+ getStatusAsync?(): Promise<number>;
3
+ /** iOS only — Android has no equivalent native call. */
4
+ setMinimumIntervalAsync?(minimumInterval: number): Promise<void>;
5
+ registerTaskAsync?(taskName: string, options: Record<string, unknown>): Promise<void>;
6
+ unregisterTaskAsync?(taskName: string): Promise<void>;
7
+ };
8
+ export declare const expoBackgroundFetch: INativeBackgroundFetchModule;
@@ -0,0 +1,3 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_BACKGROUND_FETCH_MODULE_NAME = 'ExpoBackgroundFetch';
3
+ export const expoBackgroundFetch = requireNativeModule(EXPO_BACKGROUND_FETCH_MODULE_NAME);
@@ -0,0 +1,47 @@
1
+ /**
2
+ * What iOS does with the return value of a background-fetch task executor — lets the platform
3
+ * schedule future fetches more intelligently. Android ignores it (it has no equivalent concept).
4
+ */
5
+ export declare enum BackgroundFetchResult {
6
+ /** There was no new data to download. */
7
+ NoData = 1,
8
+ /** New data was successfully downloaded. */
9
+ NewData = 2,
10
+ /** An attempt to download data was made but that attempt failed. */
11
+ Failed = 3
12
+ }
13
+ /** Whether the app can currently receive background-fetch callbacks. */
14
+ export declare enum BackgroundFetchStatus {
15
+ /** The user explicitly disabled background behavior for this app or for the whole system. */
16
+ Denied = 1,
17
+ /**
18
+ * Background updates are unavailable and the user cannot enable them again — e.g. parental
19
+ * controls are in effect for the current user.
20
+ */
21
+ Restricted = 2,
22
+ /** Background updates are available for the app. */
23
+ Available = 3
24
+ }
25
+ /** Options accepted by {@link registerTaskAsync}. */
26
+ export type IBackgroundFetchOptions = {
27
+ /**
28
+ * Inexact interval in seconds between subsequent repeats of the background fetch alarm. The
29
+ * final interval may differ from the specified one to minimize wakeups and battery usage.
30
+ * - Android defaults to 10 minutes.
31
+ * - iOS calls `setMinimumIntervalAsync` behind the scenes; the platform default is the
32
+ * smallest fetch interval it supports (10-15 minutes).
33
+ */
34
+ minimumInterval?: number;
35
+ /**
36
+ * Whether to stop receiving background fetch events after the user terminates the app.
37
+ * @default true
38
+ * @platform android
39
+ */
40
+ stopOnTerminate?: boolean;
41
+ /**
42
+ * Whether to restart background fetch events when the device has finished booting.
43
+ * @default false
44
+ * @platform android
45
+ */
46
+ startOnBoot?: boolean;
47
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * What iOS does with the return value of a background-fetch task executor — lets the platform
3
+ * schedule future fetches more intelligently. Android ignores it (it has no equivalent concept).
4
+ */
5
+ export var BackgroundFetchResult;
6
+ (function (BackgroundFetchResult) {
7
+ /** There was no new data to download. */
8
+ BackgroundFetchResult[BackgroundFetchResult["NoData"] = 1] = "NoData";
9
+ /** New data was successfully downloaded. */
10
+ BackgroundFetchResult[BackgroundFetchResult["NewData"] = 2] = "NewData";
11
+ /** An attempt to download data was made but that attempt failed. */
12
+ BackgroundFetchResult[BackgroundFetchResult["Failed"] = 3] = "Failed";
13
+ })(BackgroundFetchResult || (BackgroundFetchResult = {}));
14
+ /** Whether the app can currently receive background-fetch callbacks. */
15
+ export var BackgroundFetchStatus;
16
+ (function (BackgroundFetchStatus) {
17
+ /** The user explicitly disabled background behavior for this app or for the whole system. */
18
+ BackgroundFetchStatus[BackgroundFetchStatus["Denied"] = 1] = "Denied";
19
+ /**
20
+ * Background updates are unavailable and the user cannot enable them again — e.g. parental
21
+ * controls are in effect for the current user.
22
+ */
23
+ BackgroundFetchStatus[BackgroundFetchStatus["Restricted"] = 2] = "Restricted";
24
+ /** Background updates are available for the app. */
25
+ BackgroundFetchStatus[BackgroundFetchStatus["Available"] = 3] = "Available";
26
+ })(BackgroundFetchStatus || (BackgroundFetchStatus = {}));
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,5 @@
1
+ // @symbiote-native/background-fetch/angular: the Angular entry over the framework-agnostic core.
2
+ // Same reasoning as the React/Vue entries — no per-instance state or event stream to wrap in a
3
+ // service, so this is a plain re-export.
4
+ export * from '../core';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,+FAA+F;AAC/F,yCAAyC;AACzC,cAAc,SAAS,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { BackgroundFetchStatus } from './types';
2
+ import type { IBackgroundFetchOptions } from './types';
3
+ /** Gets the current background-fetch status, or `null` if the native module is unreachable. */
4
+ export declare function getStatusAsync(): Promise<BackgroundFetchStatus | null>;
5
+ /**
6
+ * Sets the minimum number of seconds that must elapse before another background fetch can be
7
+ * initiated. Advisory only — iOS treats it as a floor, not an exact schedule. No effect on
8
+ * Android.
9
+ */
10
+ export declare function setMinimumIntervalAsync(minimumInterval: number): Promise<void>;
11
+ /**
12
+ * Registers a background-fetch task with the given name. The task must already be defined via
13
+ * `@symbiote-native/task-manager`'s `defineTask` — registration is driven by this package, but
14
+ * execution dispatch is task-manager's job.
15
+ */
16
+ export declare function registerTaskAsync(taskName: string, options?: IBackgroundFetchOptions): Promise<void>;
17
+ /** Unregisters a background-fetch task, so the app stops receiving fetch callbacks for it. */
18
+ export declare function unregisterTaskAsync(taskName: string): Promise<void>;
@@ -0,0 +1,71 @@
1
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
2
+ import { isTaskDefined } from '@symbiote-native/task-manager';
3
+ import { expoBackgroundFetch } from './native-module';
4
+ import { BackgroundFetchStatus } from './types';
5
+ const NATIVE_MODULE_NAME = 'BackgroundFetch';
6
+ // expo-background-fetch is upstream-deprecated in favor of expo-background-task (still shipped
7
+ // in sdk-57 alongside it, same as this project's file-system legacy+modern split) — carry the
8
+ // same one-time console warning upstream shows, so an app that adopted the old API notices.
9
+ let didShowDeprecationWarning = false;
10
+ function warnDeprecated() {
11
+ if (didShowDeprecationWarning)
12
+ return;
13
+ didShowDeprecationWarning = true;
14
+ console.warn('@symbiote-native/background-fetch: this API is deprecated. Use @symbiote-native/background-task instead.');
15
+ }
16
+ function assertValidTaskName(taskName) {
17
+ if (!taskName || typeof taskName !== 'string') {
18
+ throw new TypeError('`taskName` must be a non-empty string.');
19
+ }
20
+ }
21
+ // Upstream also warns when running inside Expo Go (`isRunningInExpoGo`, imported from the `expo`
22
+ // meta-package). This project never installs `expo` — every app here is a bare/dev-client build,
23
+ // never Expo Go — so that check has no equivalent and is intentionally not ported.
24
+ /** Gets the current background-fetch status, or `null` if the native module is unreachable. */
25
+ export async function getStatusAsync() {
26
+ warnDeprecated();
27
+ if (Platform.OS === 'android') {
28
+ return BackgroundFetchStatus.Available;
29
+ }
30
+ if (!expoBackgroundFetch.getStatusAsync) {
31
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getStatusAsync');
32
+ }
33
+ return expoBackgroundFetch.getStatusAsync();
34
+ }
35
+ /**
36
+ * Sets the minimum number of seconds that must elapse before another background fetch can be
37
+ * initiated. Advisory only — iOS treats it as a floor, not an exact schedule. No effect on
38
+ * Android.
39
+ */
40
+ export async function setMinimumIntervalAsync(minimumInterval) {
41
+ warnDeprecated();
42
+ if (!expoBackgroundFetch.setMinimumIntervalAsync)
43
+ return;
44
+ await expoBackgroundFetch.setMinimumIntervalAsync(minimumInterval);
45
+ }
46
+ /**
47
+ * Registers a background-fetch task with the given name. The task must already be defined via
48
+ * `@symbiote-native/task-manager`'s `defineTask` — registration is driven by this package, but
49
+ * execution dispatch is task-manager's job.
50
+ */
51
+ export async function registerTaskAsync(taskName, options = {}) {
52
+ warnDeprecated();
53
+ if (!expoBackgroundFetch.registerTaskAsync) {
54
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'registerTaskAsync');
55
+ }
56
+ if (!isTaskDefined(taskName)) {
57
+ throw new Error(`Task '${taskName}' is not defined. You must define a task using defineTask (from @symbiote-native/task-manager) before registering.`);
58
+ }
59
+ assertValidTaskName(taskName);
60
+ await expoBackgroundFetch.registerTaskAsync(taskName, options);
61
+ }
62
+ /** Unregisters a background-fetch task, so the app stops receiving fetch callbacks for it. */
63
+ export async function unregisterTaskAsync(taskName) {
64
+ warnDeprecated();
65
+ if (!expoBackgroundFetch.unregisterTaskAsync) {
66
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'unregisterTaskAsync');
67
+ }
68
+ assertValidTaskName(taskName);
69
+ await expoBackgroundFetch.unregisterTaskAsync(taskName);
70
+ }
71
+ //# sourceMappingURL=background-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"background-fetch.js","sourceRoot":"","sources":["../../src/core/background-fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAGhD,MAAM,kBAAkB,GAAG,iBAAiB,CAAC;AAE7C,+FAA+F;AAC/F,8FAA8F;AAC9F,4FAA4F;AAC5F,IAAI,yBAAyB,GAAG,KAAK,CAAC;AACtC,SAAS,cAAc;IACrB,IAAI,yBAAyB;QAAE,OAAO;IACtC,yBAAyB,GAAG,IAAI,CAAC;IACjC,OAAO,CAAC,IAAI,CACV,0GAA0G,CAC3G,CAAC;AACJ,CAAC;AAED,SAAS,mBAAmB,CAAC,QAAiB;IAC5C,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC9C,MAAM,IAAI,SAAS,CAAC,wCAAwC,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,iGAAiG;AACjG,iGAAiG;AACjG,mFAAmF;AAEnF,+FAA+F;AAC/F,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,cAAc,EAAE,CAAC;IACjB,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,qBAAqB,CAAC,SAAS,CAAC;IACzC,CAAC;IACD,IAAI,CAAC,mBAAmB,CAAC,cAAc,EAAE,CAAC;QACxC,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,mBAAmB,CAAC,cAAc,EAAoC,CAAC;AAChF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,eAAuB;IAEvB,cAAc,EAAE,CAAC;IACjB,IAAI,CAAC,mBAAmB,CAAC,uBAAuB;QAAE,OAAO;IACzD,MAAM,mBAAmB,CAAC,uBAAuB,CAAC,eAAe,CAAC,CAAC;AACrE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,UAAmC,EAAE;IAErC,cAAc,EAAE,CAAC;IACjB,IAAI,CAAC,mBAAmB,CAAC,iBAAiB,EAAE,CAAC;QAC3C,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CACb,SAAS,QAAQ,oHAAoH,CACtI,CAAC;IACJ,CAAC;IACD,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAC9B,MAAM,mBAAmB,CAAC,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AACjE,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,QAAgB;IACxD,cAAc,EAAE,CAAC;IACjB,IAAI,CAAC,mBAAmB,CAAC,mBAAmB,EAAE,CAAC;QAC7C,MAAM,IAAI,mBAAmB,CAAC,kBAAkB,EAAE,qBAAqB,CAAC,CAAC;IAC3E,CAAC;IACD,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAC9B,MAAM,mBAAmB,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { getStatusAsync, setMinimumIntervalAsync, registerTaskAsync, unregisterTaskAsync, } from './background-fetch';
2
+ export { BackgroundFetchResult, BackgroundFetchStatus } from './types';
3
+ export type { IBackgroundFetchOptions } from './types';
@@ -0,0 +1,3 @@
1
+ export { getStatusAsync, setMinimumIntervalAsync, registerTaskAsync, unregisterTaskAsync, } from './background-fetch';
2
+ export { BackgroundFetchResult, BackgroundFetchStatus } from './types';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,8 @@
1
+ export type INativeBackgroundFetchModule = {
2
+ getStatusAsync?(): Promise<number>;
3
+ /** iOS only — Android has no equivalent native call. */
4
+ setMinimumIntervalAsync?(minimumInterval: number): Promise<void>;
5
+ registerTaskAsync?(taskName: string, options: Record<string, unknown>): Promise<void>;
6
+ unregisterTaskAsync?(taskName: string): Promise<void>;
7
+ };
8
+ export declare const expoBackgroundFetch: INativeBackgroundFetchModule;
@@ -0,0 +1,4 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_BACKGROUND_FETCH_MODULE_NAME = 'ExpoBackgroundFetch';
3
+ export const expoBackgroundFetch = requireNativeModule(EXPO_BACKGROUND_FETCH_MODULE_NAME);
4
+ //# sourceMappingURL=native-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,MAAM,iCAAiC,GAAG,qBAAqB,CAAC;AAgBhE,MAAM,CAAC,MAAM,mBAAmB,GAC9B,mBAAmB,CACjB,iCAAiC,CAClC,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * What iOS does with the return value of a background-fetch task executor — lets the platform
3
+ * schedule future fetches more intelligently. Android ignores it (it has no equivalent concept).
4
+ */
5
+ export declare enum BackgroundFetchResult {
6
+ /** There was no new data to download. */
7
+ NoData = 1,
8
+ /** New data was successfully downloaded. */
9
+ NewData = 2,
10
+ /** An attempt to download data was made but that attempt failed. */
11
+ Failed = 3
12
+ }
13
+ /** Whether the app can currently receive background-fetch callbacks. */
14
+ export declare enum BackgroundFetchStatus {
15
+ /** The user explicitly disabled background behavior for this app or for the whole system. */
16
+ Denied = 1,
17
+ /**
18
+ * Background updates are unavailable and the user cannot enable them again — e.g. parental
19
+ * controls are in effect for the current user.
20
+ */
21
+ Restricted = 2,
22
+ /** Background updates are available for the app. */
23
+ Available = 3
24
+ }
25
+ /** Options accepted by {@link registerTaskAsync}. */
26
+ export type IBackgroundFetchOptions = {
27
+ /**
28
+ * Inexact interval in seconds between subsequent repeats of the background fetch alarm. The
29
+ * final interval may differ from the specified one to minimize wakeups and battery usage.
30
+ * - Android defaults to 10 minutes.
31
+ * - iOS calls `setMinimumIntervalAsync` behind the scenes; the platform default is the
32
+ * smallest fetch interval it supports (10-15 minutes).
33
+ */
34
+ minimumInterval?: number;
35
+ /**
36
+ * Whether to stop receiving background fetch events after the user terminates the app.
37
+ * @default true
38
+ * @platform android
39
+ */
40
+ stopOnTerminate?: boolean;
41
+ /**
42
+ * Whether to restart background fetch events when the device has finished booting.
43
+ * @default false
44
+ * @platform android
45
+ */
46
+ startOnBoot?: boolean;
47
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * What iOS does with the return value of a background-fetch task executor — lets the platform
3
+ * schedule future fetches more intelligently. Android ignores it (it has no equivalent concept).
4
+ */
5
+ export var BackgroundFetchResult;
6
+ (function (BackgroundFetchResult) {
7
+ /** There was no new data to download. */
8
+ BackgroundFetchResult[BackgroundFetchResult["NoData"] = 1] = "NoData";
9
+ /** New data was successfully downloaded. */
10
+ BackgroundFetchResult[BackgroundFetchResult["NewData"] = 2] = "NewData";
11
+ /** An attempt to download data was made but that attempt failed. */
12
+ BackgroundFetchResult[BackgroundFetchResult["Failed"] = 3] = "Failed";
13
+ })(BackgroundFetchResult || (BackgroundFetchResult = {}));
14
+ /** Whether the app can currently receive background-fetch callbacks. */
15
+ export var BackgroundFetchStatus;
16
+ (function (BackgroundFetchStatus) {
17
+ /** The user explicitly disabled background behavior for this app or for the whole system. */
18
+ BackgroundFetchStatus[BackgroundFetchStatus["Denied"] = 1] = "Denied";
19
+ /**
20
+ * Background updates are unavailable and the user cannot enable them again — e.g. parental
21
+ * controls are in effect for the current user.
22
+ */
23
+ BackgroundFetchStatus[BackgroundFetchStatus["Restricted"] = 2] = "Restricted";
24
+ /** Background updates are available for the app. */
25
+ BackgroundFetchStatus[BackgroundFetchStatus["Available"] = 3] = "Available";
26
+ })(BackgroundFetchStatus || (BackgroundFetchStatus = {}));
27
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAN,IAAY,qBAOX;AAPD,WAAY,qBAAqB;IAC/B,yCAAyC;IACzC,qEAAU,CAAA;IACV,4CAA4C;IAC5C,uEAAW,CAAA;IACX,oEAAoE;IACpE,qEAAU,CAAA;AACZ,CAAC,EAPW,qBAAqB,KAArB,qBAAqB,QAOhC;AAED,wEAAwE;AACxE,MAAM,CAAN,IAAY,qBAUX;AAVD,WAAY,qBAAqB;IAC/B,6FAA6F;IAC7F,qEAAU,CAAA;IACV;;;OAGG;IACH,6EAAc,CAAA;IACd,oDAAoD;IACpD,2EAAa,CAAA;AACf,CAAC,EAVW,qBAAqB,KAArB,qBAAqB,QAUhC"}
@@ -0,0 +1,15 @@
1
+ {
2
+ "android": {
3
+ "gradleProjectName": "expo-background-fetch",
4
+ "modules": [
5
+ {
6
+ "importPath": "expo.modules.backgroundfetch.BackgroundFetchModule",
7
+ "className": "BackgroundFetchModule",
8
+ "nativeName": "ExpoBackgroundFetch"
9
+ }
10
+ ]
11
+ },
12
+ "ios": {
13
+ "infoPlistArrayKeys": { "UIBackgroundModes": ["fetch"] }
14
+ }
15
+ }
package/package.json ADDED
@@ -0,0 +1,151 @@
1
+ {
2
+ "name": "@symbiote-native/background-fetch",
3
+ "version": "0.1.0",
4
+ "description": "expo-background-fetch wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Periodic background fetch, registered through @symbiote-native/task-manager. Deprecated upstream in favor of expo-background-task; ported for parity with what Expo still ships.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/OneEyed1366/symbiote-native.git",
9
+ "directory": "packages/background-fetch"
10
+ },
11
+ "homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/background-fetch#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/OneEyed1366/symbiote-native/issues"
14
+ },
15
+ "author": "Andrey Prokopenko <psevdoproger@gmail.com>",
16
+ "keywords": [
17
+ "react-native",
18
+ "symbiote-native",
19
+ "expo-background-fetch",
20
+ "react",
21
+ "vue",
22
+ "svelte",
23
+ "solid",
24
+ "angular",
25
+ "background",
26
+ "fetch"
27
+ ],
28
+ "type": "module",
29
+ "main": "./build/core/index.js",
30
+ "module": "./build/core/index.js",
31
+ "types": "./build/core/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./build/core/index.d.ts",
35
+ "default": "./build/core/index.js"
36
+ },
37
+ "./vue": {
38
+ "types": "./build/core/index.d.ts",
39
+ "default": "./build/core/index.js"
40
+ },
41
+ "./react": {
42
+ "types": "./build/core/index.d.ts",
43
+ "default": "./build/core/index.js"
44
+ },
45
+ "./svelte": {
46
+ "types": "./build/core/index.d.ts",
47
+ "default": "./build/core/index.js"
48
+ },
49
+ "./solid": {
50
+ "types": "./build/core/index.d.ts",
51
+ "default": "./build/core/index.js"
52
+ },
53
+ "./angular": {
54
+ "types": "./build-ngc/angular/index.d.ts",
55
+ "react-native": "./build-ngc/angular/index.js",
56
+ "default": "./src/angular/index.ts"
57
+ }
58
+ },
59
+ "files": [
60
+ "src",
61
+ "build",
62
+ "build-ngc",
63
+ "native-link.json",
64
+ "!src/**/*.test.*",
65
+ "!src/**/*.spec.*",
66
+ "!src/**/*.detox.*"
67
+ ],
68
+ "publishConfig": {
69
+ "access": "public"
70
+ },
71
+ "dependencies": {
72
+ "expo-background-fetch": "57.0.15",
73
+ "expo-modules-core": "57.0.5",
74
+ "@symbiote-native/task-manager": "0.1.0"
75
+ },
76
+ "peerDependencies": {
77
+ "@angular/core": ">=20",
78
+ "@vue/runtime-core": "^3.5.13",
79
+ "react": ">=19.0.0",
80
+ "react-native": ">=0.86",
81
+ "solid-js": ">=1.9.0",
82
+ "svelte": ">=5.56.0",
83
+ "vue": ">=3.5.0",
84
+ "@symbiote-native/engine": "^1.3.0",
85
+ "@symbiote-native/angular": "^3.1.0",
86
+ "@symbiote-native/react": "^3.0.2",
87
+ "@symbiote-native/svelte": "^3.0.2",
88
+ "@symbiote-native/vue": "^3.0.2",
89
+ "@symbiote-native/solid": "^3.0.2"
90
+ },
91
+ "peerDependenciesMeta": {
92
+ "@symbiote-native/angular": {
93
+ "optional": true
94
+ },
95
+ "@symbiote-native/react": {
96
+ "optional": true
97
+ },
98
+ "@symbiote-native/solid": {
99
+ "optional": true
100
+ },
101
+ "@symbiote-native/svelte": {
102
+ "optional": true
103
+ },
104
+ "@symbiote-native/vue": {
105
+ "optional": true
106
+ },
107
+ "@angular/core": {
108
+ "optional": true
109
+ },
110
+ "@vue/runtime-core": {
111
+ "optional": true
112
+ },
113
+ "react": {
114
+ "optional": true
115
+ },
116
+ "solid-js": {
117
+ "optional": true
118
+ },
119
+ "svelte": {
120
+ "optional": true
121
+ },
122
+ "vue": {
123
+ "optional": true
124
+ }
125
+ },
126
+ "devDependencies": {
127
+ "@angular/compiler": "~22.0.8",
128
+ "@angular/compiler-cli": "~22.0.8",
129
+ "@angular/core": "~22.0.8",
130
+ "@types/node": "^26.0.0",
131
+ "@types/react": "^19.2.0",
132
+ "@vue/runtime-core": "^3.5.13",
133
+ "react": "19.2.3",
134
+ "solid-js": "^1.9.14",
135
+ "svelte": "^5.56.0",
136
+ "typescript": "~6.0.0",
137
+ "@symbiote-native/engine": "1.3.0",
138
+ "@symbiote-native/svelte": "3.0.2",
139
+ "@symbiote-native/angular": "3.1.0",
140
+ "@symbiote-native/vue": "3.0.2",
141
+ "@symbiote-native/react": "3.0.2",
142
+ "@symbiote-native/solid": "3.0.2",
143
+ "@symbiote-native/test-utils": "0.4.2"
144
+ },
145
+ "scripts": {
146
+ "typecheck": "tsc --build",
147
+ "clean": "rm -rf build-ngc",
148
+ "ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
149
+ "format": "prettier --write \"src/**/*.{ts,tsx}\""
150
+ }
151
+ }
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/background-fetch/angular: the Angular entry over the framework-agnostic core.
2
+ // Same reasoning as the React/Vue entries — no per-instance state or event stream to wrap in a
3
+ // service, so this is a plain re-export.
4
+ export * from '../core';
@@ -0,0 +1,86 @@
1
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
2
+ import { isTaskDefined } from '@symbiote-native/task-manager';
3
+ import { expoBackgroundFetch } from './native-module';
4
+ import { BackgroundFetchStatus } from './types';
5
+ import type { IBackgroundFetchOptions } from './types';
6
+
7
+ const NATIVE_MODULE_NAME = 'BackgroundFetch';
8
+
9
+ // expo-background-fetch is upstream-deprecated in favor of expo-background-task (still shipped
10
+ // in sdk-57 alongside it, same as this project's file-system legacy+modern split) — carry the
11
+ // same one-time console warning upstream shows, so an app that adopted the old API notices.
12
+ let didShowDeprecationWarning = false;
13
+ function warnDeprecated(): void {
14
+ if (didShowDeprecationWarning) return;
15
+ didShowDeprecationWarning = true;
16
+ console.warn(
17
+ '@symbiote-native/background-fetch: this API is deprecated. Use @symbiote-native/background-task instead.',
18
+ );
19
+ }
20
+
21
+ function assertValidTaskName(taskName: unknown): asserts taskName is string {
22
+ if (!taskName || typeof taskName !== 'string') {
23
+ throw new TypeError('`taskName` must be a non-empty string.');
24
+ }
25
+ }
26
+
27
+ // Upstream also warns when running inside Expo Go (`isRunningInExpoGo`, imported from the `expo`
28
+ // meta-package). This project never installs `expo` — every app here is a bare/dev-client build,
29
+ // never Expo Go — so that check has no equivalent and is intentionally not ported.
30
+
31
+ /** Gets the current background-fetch status, or `null` if the native module is unreachable. */
32
+ export async function getStatusAsync(): Promise<BackgroundFetchStatus | null> {
33
+ warnDeprecated();
34
+ if (Platform.OS === 'android') {
35
+ return BackgroundFetchStatus.Available;
36
+ }
37
+ if (!expoBackgroundFetch.getStatusAsync) {
38
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getStatusAsync');
39
+ }
40
+ return expoBackgroundFetch.getStatusAsync() as Promise<BackgroundFetchStatus>;
41
+ }
42
+
43
+ /**
44
+ * Sets the minimum number of seconds that must elapse before another background fetch can be
45
+ * initiated. Advisory only — iOS treats it as a floor, not an exact schedule. No effect on
46
+ * Android.
47
+ */
48
+ export async function setMinimumIntervalAsync(
49
+ minimumInterval: number,
50
+ ): Promise<void> {
51
+ warnDeprecated();
52
+ if (!expoBackgroundFetch.setMinimumIntervalAsync) return;
53
+ await expoBackgroundFetch.setMinimumIntervalAsync(minimumInterval);
54
+ }
55
+
56
+ /**
57
+ * Registers a background-fetch task with the given name. The task must already be defined via
58
+ * `@symbiote-native/task-manager`'s `defineTask` — registration is driven by this package, but
59
+ * execution dispatch is task-manager's job.
60
+ */
61
+ export async function registerTaskAsync(
62
+ taskName: string,
63
+ options: IBackgroundFetchOptions = {},
64
+ ): Promise<void> {
65
+ warnDeprecated();
66
+ if (!expoBackgroundFetch.registerTaskAsync) {
67
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'registerTaskAsync');
68
+ }
69
+ if (!isTaskDefined(taskName)) {
70
+ throw new Error(
71
+ `Task '${taskName}' is not defined. You must define a task using defineTask (from @symbiote-native/task-manager) before registering.`,
72
+ );
73
+ }
74
+ assertValidTaskName(taskName);
75
+ await expoBackgroundFetch.registerTaskAsync(taskName, options);
76
+ }
77
+
78
+ /** Unregisters a background-fetch task, so the app stops receiving fetch callbacks for it. */
79
+ export async function unregisterTaskAsync(taskName: string): Promise<void> {
80
+ warnDeprecated();
81
+ if (!expoBackgroundFetch.unregisterTaskAsync) {
82
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'unregisterTaskAsync');
83
+ }
84
+ assertValidTaskName(taskName);
85
+ await expoBackgroundFetch.unregisterTaskAsync(taskName);
86
+ }
@@ -0,0 +1,8 @@
1
+ export {
2
+ getStatusAsync,
3
+ setMinimumIntervalAsync,
4
+ registerTaskAsync,
5
+ unregisterTaskAsync,
6
+ } from './background-fetch';
7
+ export { BackgroundFetchResult, BackgroundFetchStatus } from './types';
8
+ export type { IBackgroundFetchOptions } from './types';
@@ -0,0 +1,22 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+
3
+ const EXPO_BACKGROUND_FETCH_MODULE_NAME = 'ExpoBackgroundFetch';
4
+
5
+ // Every method is optional — each call site checks for its presence before calling through and
6
+ // throws an UnavailabilityError itself, matching upstream's own per-platform capability checks
7
+ // (same pattern as packages/task-manager/src/core/native-module.ts's INativeTaskManagerModule).
8
+ export type INativeBackgroundFetchModule = {
9
+ getStatusAsync?(): Promise<number>;
10
+ /** iOS only — Android has no equivalent native call. */
11
+ setMinimumIntervalAsync?(minimumInterval: number): Promise<void>;
12
+ registerTaskAsync?(
13
+ taskName: string,
14
+ options: Record<string, unknown>,
15
+ ): Promise<void>;
16
+ unregisterTaskAsync?(taskName: string): Promise<void>;
17
+ };
18
+
19
+ export const expoBackgroundFetch =
20
+ requireNativeModule<INativeBackgroundFetchModule>(
21
+ EXPO_BACKGROUND_FETCH_MODULE_NAME,
22
+ );
@@ -0,0 +1,49 @@
1
+ /**
2
+ * What iOS does with the return value of a background-fetch task executor — lets the platform
3
+ * schedule future fetches more intelligently. Android ignores it (it has no equivalent concept).
4
+ */
5
+ export enum BackgroundFetchResult {
6
+ /** There was no new data to download. */
7
+ NoData = 1,
8
+ /** New data was successfully downloaded. */
9
+ NewData = 2,
10
+ /** An attempt to download data was made but that attempt failed. */
11
+ Failed = 3,
12
+ }
13
+
14
+ /** Whether the app can currently receive background-fetch callbacks. */
15
+ export enum BackgroundFetchStatus {
16
+ /** The user explicitly disabled background behavior for this app or for the whole system. */
17
+ Denied = 1,
18
+ /**
19
+ * Background updates are unavailable and the user cannot enable them again — e.g. parental
20
+ * controls are in effect for the current user.
21
+ */
22
+ Restricted = 2,
23
+ /** Background updates are available for the app. */
24
+ Available = 3,
25
+ }
26
+
27
+ /** Options accepted by {@link registerTaskAsync}. */
28
+ export type IBackgroundFetchOptions = {
29
+ /**
30
+ * Inexact interval in seconds between subsequent repeats of the background fetch alarm. The
31
+ * final interval may differ from the specified one to minimize wakeups and battery usage.
32
+ * - Android defaults to 10 minutes.
33
+ * - iOS calls `setMinimumIntervalAsync` behind the scenes; the platform default is the
34
+ * smallest fetch interval it supports (10-15 minutes).
35
+ */
36
+ minimumInterval?: number;
37
+ /**
38
+ * Whether to stop receiving background fetch events after the user terminates the app.
39
+ * @default true
40
+ * @platform android
41
+ */
42
+ stopOnTerminate?: boolean;
43
+ /**
44
+ * Whether to restart background fetch events when the device has finished booting.
45
+ * @default false
46
+ * @platform android
47
+ */
48
+ startOnBoot?: boolean;
49
+ };