@mpgd/adapter-capacitor 0.10.0 → 0.11.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/README.md CHANGED
@@ -184,25 +184,132 @@ Games do not call the provider from a scene: `createGameServicesClient` from
184
184
 
185
185
  ## Notifications v2
186
186
 
187
- The notifications v2 provider arrives in PR P2 of the
188
- [notifications RFC](../../docs/specs/notifications/notifications-v2-rfc.md),
189
- together with the registry surface through which a provider serves the
190
- `notifications.v2.*` methods. Until then the gateway exposes no
191
- `notificationsV2`, and its request path never forwards a `notifications.v2.*`
192
- method to the base plugin: it answers `NOTIFICATIONS_V2_UNAVAILABLE`, which is
193
- not retryable, before it pauses the game. A provider cannot declare those
194
- methods in `methods`.
195
-
196
- Once P2 lets a provider serve `notifications.v2.*` methods, these rules apply:
197
-
198
- - `notifications.v2.requestPermission` and `notifications.v2.openSettings`
199
- pause the game while they run, like `notifications.requestSubscription` and
200
- `commerce.v2.requestPurchase`.
201
- - The provider registry passes `notifications.v2.*` through `assertMethodReady`
202
- without a readiness check, as it does `commerce.v2.*`, so the provider's own
203
- `unsupported` or `action-required` answers reach the game as answers.
204
- - The gateway validates each answer before the game sees it. Until P2 adds the
205
- notifications v2 answers to that check, it rejects every one of them.
187
+ `@mpgd/adapter-capacitor/notifications` is the Android and iOS provider of the
188
+ [notifications RFC](../../docs/specs/notifications/notifications-v2-rfc.md#profile-capacitor).
189
+ Only this subpath loads the notifications validators and the two plugins, so a
190
+ game without notifications bundles neither.
191
+
192
+ ```ts
193
+ import { resolveNotificationTopicsForTarget } from '@mpgd/catalog';
194
+ import {
195
+ createCapacitorNotificationServiceProvider,
196
+ loadCapacitorNotificationSdks,
197
+ } from '@mpgd/adapter-capacitor/notifications';
198
+
199
+ const sdks = await loadCapacitorNotificationSdks();
200
+ const notifications = createCapacitorNotificationServiceProvider({
201
+ platform: 'android',
202
+ topics: resolveNotificationTopicsForTarget(catalog, 'android'),
203
+ ...sdks,
204
+ // Optional: opens this app's notification settings (see below).
205
+ openSettings,
206
+ });
207
+ const gateway = createCapacitorPlatformGateway({ ..., providers: [notifications] });
208
+ ```
209
+
210
+ Create it once, early in startup: it registers its native listeners when it
211
+ is created, which is how a cold-start open reaches it.
212
+
213
+ - **Plugins.** Install `@capacitor-firebase/messaging` (with the `firebase`
214
+ package its web fallback imports) and `@capacitor/local-notifications` 8.x,
215
+ both optional peers of this package. Do not also install
216
+ `@capacitor/push-notifications`: on iOS both claim Capacitor's single push
217
+ handler. `loadCapacitorNotificationSdks` imports them dynamically and leaves
218
+ one out when the app is not native, the shell has no native implementation,
219
+ or the import fails. Without both the provider is `unsupported`; without the
220
+ push plugin it declares no remote delivery; without the local plugin it
221
+ declares no local reminders and reads the permission through the push
222
+ plugin, which answers `granted` on Android 12 and lower whatever the setting
223
+ is. **Advertising needs both plugins:** the opt-out action every advertising
224
+ push carries is registered through the local plugin, so a push-only shell
225
+ whose catalog maps an advertising topic is `configuration-required` (its
226
+ reads, requests, and `register()` throw `NOTIFICATIONS_CONFIGURATION_REQUIRED`).
227
+ iOS remote notifications go through FCM, which relays to APNs.
228
+ - **Gateway.** `createCapacitorNotificationServiceProvider` (or
229
+ `createNotificationServiceProvider` over another provider) registers the
230
+ nine `notifications.v2.*` methods. The gateway then exposes
231
+ `notificationsV2`, validates every payload and answer on both sides of its
232
+ in-process bridge, pauses the game under `requestPermission` and until the
233
+ player is back from `openSettings`, and serves the v1 `notifications`
234
+ adapter from the same provider. `getCapabilities()` carries the provider's
235
+ `notifications` block, and `providerAvailability.pushNotifications` equals
236
+ its availability. A v1 `pushNotifications` provider cannot be registered
237
+ next to it. Without a notifications service the gateway exposes no
238
+ `notificationsV2` and answers every `notifications.v2.*` request with
239
+ `NOTIFICATIONS_V2_UNAVAILABLE` before it pauses the game.
240
+ - **Permission.** One OS permission for the app. The provider reads it through
241
+ the local plugin, which reflects the system setting on Android 12 and lower
242
+ where the push plugin answers `granted` regardless. The OS `denied` reads
243
+ `blocked`: after one denial on iOS (`promptLimit` 1) and after the second on
244
+ Android 13+ (`promptLimit` 2; pass `androidPromptLimit: 1` for an app that
245
+ targets SDK 32 or lower). Only `requestPermission` shows the dialog, and a
246
+ request made while the dialog is up shares its answer. A change made in
247
+ system settings is read on the next read and announced as
248
+ `permission-changed` when the app returns to the foreground.
249
+ - **Local reminders.** `scheduleLocal` answers `unsupported` for a topic
250
+ outside the resolved catalog and for an advertising topic, which never
251
+ schedules locally. Otherwise it reads the permission first and answers
252
+ `permission-required` without calling the plugin, whose own `schedule` would
253
+ prompt on Android 13+.
254
+ - Reminders are inexact (`isExactNotification: false`; the plugin's default
255
+ would open the exact-alarm settings screen) but may fire in Android's Doze
256
+ mode (`allowWhileIdle: true`; Android lets such alarms fire about once per
257
+ nine minutes per app). Remove `SCHEDULE_EXACT_ALARM`, which the
258
+ plugin's manifest adds, from the merged manifest (`tools:node="remove"`).
259
+ - Each is keyed by a stable 32-bit native id, replaced by key, and posted on
260
+ the topic's Android channel. Operations run one at a time, in call order.
261
+ - iOS keeps at most 64 pending, so the provider keeps the nearest: at the
262
+ ceiling it cancels the farthest of its reminders before it schedules a
263
+ nearer one, and puts the farthest back when that schedule fails. Android
264
+ declares no ceiling, but its alarm manager refuses an app with about 500
265
+ pending alarms.
266
+ - **Channels.** Each topic posts to its mapping's `channelId`, or to
267
+ `mpgd-service`, `mpgd-requested`, or `mpgd-advertising` by purpose
268
+ (`capacitorNotificationChannelId`). Creating the provider throws when a
269
+ channel would mix advertising with other topics, so a player can always turn
270
+ advertising off in system settings. Name the channels with the `channels`
271
+ option.
272
+ - **Topics.** Only topics this target maps count. A topic the catalog does
273
+ not declare, or declares without a mapping for the target, reads
274
+ `unsupported`, answers `unavailable` to a request and `unsupported` to
275
+ `scheduleLocal`, gets no channel, and an open naming it carries no topic.
276
+ - **Opens.** A tap becomes one `opened` event, kept whenever no listener is
277
+ attached (before the first subscriber, and between two) for the next
278
+ subscriber, as are endpoint and permission changes; a receipt that arrives
279
+ between two subscribers is dropped. A push carries its topic and route in
280
+ the data keys `topic` and `route` (`capacitorNotificationDataKeys`); its
281
+ other string entries, except
282
+ the transport's own keys (`from`, `collapse_key`, `google.*`, `gcm.*`, and
283
+ the like), reach the game as untrusted `data`. A route is kept only when
284
+ `isRootRelativeNotificationRoute` accepts it, and a topic only when the
285
+ catalog declares it. Nothing is granted from an open. An open that arrives
286
+ before the first subscriber is marked `cold`; Capacitor replays a cold-start
287
+ tap asynchronously, so one replayed after the game subscribed reads `warm`.
288
+ - **Advertising opt-out.** For a catalog with an advertising topic, the
289
+ provider registers the action type `mpgd-advertising`
290
+ (`capacitorAdvertisingActionTypeId`) with the `advertising-opt-out` action
291
+ (title `advertisingOptOutTitle`, '수신거부' by default) through the local
292
+ plugin.
293
+ - On iOS that sets the app's notification categories, all at once, so it
294
+ replaces categories the shell registered through the same plugin. An FCM
295
+ send names the category in `apns.payload.aps.category`.
296
+ - The action opens the `optOutRoute` of the advertising topic the push names
297
+ in its `topic` data key; without that key it opens nothing.
298
+ - An Android FCM notification message cannot carry actions, so an Android
299
+ advertising push needs the shell to render it natively with the action;
300
+ the template work for that is C1's.
301
+ - **Settings.** Neither plugin opens the notification settings, so the shell
302
+ passes `openSettings` (for example a small native method); without it
303
+ `permission.settings` is `false` and `openSettings` answers `unavailable`.
304
+ The provider resolves `openSettings` only once the app is back in the
305
+ foreground, after at most two minutes (`settingsReturnTimeoutMs`), or after
306
+ five seconds when the app never left.
307
+ - **Endpoints.** `register()` answers the FCM token with the device
308
+ permission, locale, and time zone, or `null` while Firebase cannot issue one
309
+ yet; a token Firebase issues later arrives as `endpoint-changed`. It
310
+ registers FCM tokens only: `fcm-installation` addresses are reserved,
311
+ because FCM HTTP v1 cannot send to a Firebase installation id and the
312
+ game-services FCM sender refuses them.
206
313
 
207
314
  ## App Store server-side transaction signatures
208
315
 
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Whether `envelope` names a protocol other than `protocol`. A channel may
3
+ * carry several protocols, and another protocol is not this consumer's to
4
+ * judge, so it is skipped. An envelope without a protocol string is
5
+ * malformed and is not skipped: it must reach the consumer's validator.
6
+ */
7
+ export declare function namesOtherProtocol(envelope: unknown, protocol: string): boolean;
@@ -0,0 +1,14 @@
1
+ // How the adapter's event channels filter envelopes. Internal: the package
2
+ // entry does not export this module.
3
+ /**
4
+ * Whether `envelope` names a protocol other than `protocol`. A channel may
5
+ * carry several protocols, and another protocol is not this consumer's to
6
+ * judge, so it is skipped. An envelope without a protocol string is
7
+ * malformed and is not skipped: it must reach the consumer's validator.
8
+ */
9
+ export function namesOtherProtocol(envelope, protocol) {
10
+ const named = typeof envelope === 'object' && envelope !== null
11
+ ? envelope.protocol
12
+ : undefined;
13
+ return typeof named === 'string' && named !== protocol;
14
+ }
@@ -1,6 +1,7 @@
1
1
  import { bridgeCommerceEventProtocol, createBridgeError, isBridgeCommerceV2Method, } from '@mpgd/bridge';
2
2
  import { PlatformOperationError } from '@mpgd/platform';
3
3
  import { assertCommerceBridgeFetchProductsPayload, assertCommerceBridgeFetchProductsResult, assertCommerceBridgeFinishTransactionPayload, assertCommerceBridgeFinishTransactionResult, assertCommerceBridgeGetAvailablePurchasesPayload, assertCommerceBridgeGetAvailablePurchasesResult, assertCommerceBridgeGetCapabilitiesPayload, assertCommerceBridgeGetCapabilitiesResult, assertCommerceBridgeRequestPurchasePayload, assertCommerceBridgeRequestPurchaseResult, assertCommerceBridgeRestorePurchasesPayload, assertCommerceBridgeRestorePurchasesResult, assertCommerceEventEnvelope, CommerceError, commerceErrorInfoOf, commerceProtocol, commerceProtocolVersion, } from '@mpgd/platform/commerce';
4
+ import { namesOtherProtocol } from './bridge-events.js';
4
5
  import { normalizeStoreSdkError } from './store-sdk.js';
5
6
  /** Bridge error for a commerce v2 payload the typia assert rejected. */
6
7
  export const commerceV2InvalidPayloadBridgeErrorCode = 'COMMERCE_V2_INVALID_PAYLOAD';
@@ -209,10 +210,7 @@ export function createBridgeCommerceProvider(input) {
209
210
  // Another protocol on a shared event channel is not ours to judge; an
210
211
  // envelope without a protocol is malformed and must reach the
211
212
  // validator, as in the Apps in Toss provider.
212
- const protocol = typeof envelope === 'object' && envelope !== null
213
- ? envelope.protocol
214
- : undefined;
215
- if (typeof protocol === 'string' && protocol !== bridgeCommerceEventProtocol) {
213
+ if (namesOtherProtocol(envelope, bridgeCommerceEventProtocol)) {
216
214
  return;
217
215
  }
218
216
  let event;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { type PlatformGateway, type PlatformTarget, type ViewportAdapter } from '@mpgd/platform';
2
2
  import { type CapacitorAppEventsApi, type CapacitorIncomingUrlKind, type CapacitorVisibilitySource } from './app-events.js';
3
3
  import { type CapacitorServiceProvider, type NativeBridge } from './providers.js';
4
- export { createCapacitorProviderRegistry, createDegradedCommerceCapabilities, type CapacitorCommerceServiceProvider, type CapacitorProviderCapabilitySnapshot, type CapacitorProviderRegistry, type CapacitorServiceProvider, type NativeBridge, } from './providers.js';
4
+ export { createCapacitorProviderRegistry, createDegradedCommerceCapabilities, createDegradedNotificationCapabilities, type CapacitorCommerceServiceProvider, type CapacitorNotificationServiceProvider, type CapacitorNotificationsV2Service, type CapacitorProviderCapabilitySnapshot, type CapacitorProviderRegistry, type CapacitorServiceProvider, type NativeBridge, } from './providers.js';
5
5
  export { assertCommerceBridgeResult, commerceV2InvalidPayloadBridgeErrorCode, commerceV2MethodUnsupportedBridgeErrorCode, createBridgeCommerceProvider, createCommerceProviderBridge, createCommerceServiceProvider, type CommerceProviderBridge, type CreateBridgeCommerceProviderInput, type CreateCommerceProviderBridgeInput, type CreateCommerceServiceProviderInput, } from './commerce-bridge.js';
6
6
  export { createStoreCommerceProvider, recoverableErrorCodes, type CreateStoreCommerceProviderInput, type StoreCommerceCapabilityOverrides, type StoreCommerceProductMapping, type StoreCommerceProvider, } from './store-commerce-provider.js';
7
7
  export { defaultStoreSdkErrorCodeMap, normalizeStoreSdkError, type StoreSdk, type StoreSdkCapabilities, type StoreSdkError, type StoreSdkErrorCodeMap, type StoreSdkEvent, type StoreSdkEventName, type StoreSdkEventOf, type StoreSdkFetchProductsInput, type StoreSdkFinishTransactionInput, type StoreSdkFinishTransactionResult, type StoreSdkGetAvailablePurchasesInput, type StoreSdkProduct, type StoreSdkPurchase, type StoreSdkRequestPurchaseInput, type StoreSdkRequestPurchaseResult, } from './store-sdk.js';
package/dist/index.js CHANGED
@@ -1,13 +1,14 @@
1
- import { bridgeCommerceEventProtocol, decodeBridgeCredentialLoadData, decodeBridgeStorageLoadData, } from '@mpgd/bridge';
1
+ import { bridgeCommerceEventProtocol, bridgeNotificationsEventProtocol, decodeBridgeCredentialLoadData, decodeBridgeStorageLoadData, } from '@mpgd/bridge';
2
2
  import { CapacitorGameServices } from '@mpgd/capacitor-game-services';
3
3
  import { PlatformOperationError, } from '@mpgd/platform';
4
4
  import { withShareIntentCheck } from '@mpgd/platform/routes';
5
5
  import { createCapacitorAppEvents, } from './app-events.js';
6
+ import { namesOtherProtocol } from './bridge-events.js';
6
7
  import { createBridgeCommerceProvider } from './commerce-bridge.js';
7
8
  import { assertProviderResponseData, createCapacitorProviderRegistry, } from './providers.js';
8
9
  import { assertNotificationsV2Served, externalActivityMethods } from './request-policy.js';
9
10
  import { createCapacitorViewport } from './viewport.js';
10
- export { createCapacitorProviderRegistry, createDegradedCommerceCapabilities, } from './providers.js';
11
+ export { createCapacitorProviderRegistry, createDegradedCommerceCapabilities, createDegradedNotificationCapabilities, } from './providers.js';
11
12
  export { assertCommerceBridgeResult, commerceV2InvalidPayloadBridgeErrorCode, commerceV2MethodUnsupportedBridgeErrorCode, createBridgeCommerceProvider, createCommerceProviderBridge, createCommerceServiceProvider, } from './commerce-bridge.js';
12
13
  export { createStoreCommerceProvider, recoverableErrorCodes, } from './store-commerce-provider.js';
13
14
  export { defaultStoreSdkErrorCodeMap, normalizeStoreSdkError, } from './store-sdk.js';
@@ -66,15 +67,25 @@ export function createCapacitorPlatformGateway(input) {
66
67
  // else, a malformed envelope included, reaches the commerce
67
68
  // provider's validator.
68
69
  subscribe: (listener) => commerceProvider.bridge.subscribe((envelope) => {
69
- const protocol = typeof envelope === 'object' && envelope !== null
70
- ? envelope.protocol
71
- : undefined;
72
- if (typeof protocol === 'string' && protocol !== bridgeCommerceEventProtocol) {
73
- return;
70
+ if (!namesOtherProtocol(envelope, bridgeCommerceEventProtocol)) {
71
+ listener(envelope);
74
72
  }
75
- listener(envelope);
76
73
  }),
77
74
  });
75
+ const notificationsProvider = providers.notifications;
76
+ // Served through the same request path as every other provider method, as
77
+ // commerce is, so the game pauses under the permission dialog and the
78
+ // settings screen, and every answer is validated on the way back.
79
+ const notificationSurface = notificationsProvider?.notificationsV2.createGatewaySurface({
80
+ request: (method, payload) => request(method, payload),
81
+ // Only an envelope that names another protocol is dropped; anything else,
82
+ // a malformed envelope included, reaches the client's validator.
83
+ subscribe: (listener) => notificationsProvider.bridge.subscribe((envelope) => {
84
+ if (!namesOtherProtocol(envelope, bridgeNotificationsEventProtocol)) {
85
+ listener(envelope);
86
+ }
87
+ }),
88
+ });
78
89
  const lifecycle = createCapacitorAppEvents({
79
90
  target: input.target,
80
91
  ...(input.app === undefined ? {} : { app: input.app }),
@@ -226,7 +237,7 @@ export function createCapacitorPlatformGateway(input) {
226
237
  });
227
238
  }
228
239
  if (provider !== undefined) {
229
- assertProviderResponseData(method, response.data);
240
+ assertProviderResponseData(method, response.data, provider);
230
241
  }
231
242
  return response.data;
232
243
  }
@@ -247,12 +258,14 @@ export function createCapacitorPlatformGateway(input) {
247
258
  const base = await request('runtime.getCapabilities', {});
248
259
  // A native shell built before the commerce v2 removals may still send the
249
260
  // retired boolean flags; the commerce block is the only commerce source.
250
- const { nativeIap: _retiredNativeIap, subscriptionIap: _retiredSubscriptionIap, ...native } = (isRecord(base) ? base : {});
261
+ // The notifications block is the registered service's, present exactly
262
+ // when the gateway exposes `notificationsV2`.
263
+ const { nativeIap: _retiredNativeIap, subscriptionIap: _retiredSubscriptionIap, notifications: _nativeNotifications, ...native } = (isRecord(base) ? base : {});
251
264
  // One bounded registry read serves the feature states and the commerce
252
265
  // block, so a stalled or invalid provider degrades the snapshot instead
253
266
  // of rejecting it, and a missing or invalid native `commerce` answer is
254
267
  // always replaced by the provider's block or the unsupported block.
255
- const { availability: states, commerce: commerceCapabilities } = await providers.getCapabilitySnapshot();
268
+ const { availability: states, commerce: commerceCapabilities, notifications: notificationCapabilities, } = await providers.getCapabilitySnapshot();
256
269
  const rewardedAds = states.rewardedAds === 'available';
257
270
  const interstitialAds = states.interstitialAds === 'available';
258
271
  const bannerAds = states.bannerAds === 'available';
@@ -265,6 +278,9 @@ export function createCapacitorPlatformGateway(input) {
265
278
  nativeLeaderboard: states.nativeLeaderboard === 'available',
266
279
  providerAvailability: states,
267
280
  commerce: commerceCapabilities,
281
+ ...(notificationSurface === undefined || notificationCapabilities === undefined
282
+ ? {}
283
+ : { notifications: notificationCapabilities }),
268
284
  };
269
285
  },
270
286
  ...(commerce === undefined ? {} : { commerce }),
@@ -283,10 +299,15 @@ export function createCapacitorPlatformGateway(input) {
283
299
  share: (payload) => request('share.share', payload),
284
300
  readInboundShare: () => request('share.readInboundShare', {}),
285
301
  }),
286
- notifications: {
302
+ // With a notifications v2 service, v1 is served from it (RFC
303
+ // "Capabilities"), so both report the same readiness.
304
+ notifications: notificationSurface?.notifications ?? {
287
305
  getStatus: (topic) => request('notifications.getStatus', { topic }),
288
306
  requestSubscription: (topic) => request('notifications.requestSubscription', { topic }),
289
307
  },
308
+ ...(notificationSurface === undefined
309
+ ? {}
310
+ : { notificationsV2: notificationSurface.notificationsV2 }),
290
311
  ads: {
291
312
  ...(adProvider === undefined ? {} : { provider: adProvider }),
292
313
  preload: (payload) => request('ads.preload', payload),
@@ -0,0 +1,82 @@
1
+ import { type BridgeEvent, type BridgeNotificationsEvent, type BridgeNotificationsV2Method, type BridgeRequest, type BridgeResponse } from '@mpgd/bridge';
2
+ import { type NotificationSubscriptionAdapter } from '@mpgd/platform';
3
+ import { type NotificationProvider, type NotificationProviderId, type NotificationTopicDeclaration } from '@mpgd/platform/notifications';
4
+ import type { CapacitorNotificationServiceProvider } from './providers.js';
5
+ export interface NotificationProviderBridge {
6
+ request(input: BridgeRequest): Promise<BridgeResponse>;
7
+ /** Delivers `{ protocol: 'mpgd.notifications.v2', sequence, event }`, numbered from 0. */
8
+ subscribe(listener: (event: BridgeEvent) => void): () => void;
9
+ }
10
+ export interface CreateNotificationProviderBridgeInput {
11
+ readonly provider: NotificationProvider;
12
+ }
13
+ /**
14
+ * In-process `NativeBridge` over a notifications v2 provider. Each
15
+ * `notifications.v2.*` request validates its payload (a payload the contract
16
+ * rejects answers `NOTIFICATIONS_INVALID_INPUT`) and its answer (one the
17
+ * contract rejects answers `NOTIFICATIONS_CONFIGURATION_REQUIRED`, because the
18
+ * provider broke it). A method the
19
+ * provider does not implement answers `NOTIFICATIONS_UNSUPPORTED`, except
20
+ * `openSettings`, which answers `unavailable`. Events go out through
21
+ * `createBridgeEventSource`, which attaches to the provider only for the
22
+ * first listener, so events the provider buffered reach it. Any other method
23
+ * answers `UNSUPPORTED_METHOD`.
24
+ */
25
+ export declare function createNotificationProviderBridge(input: CreateNotificationProviderBridgeInput): NotificationProviderBridge;
26
+ export interface CreateNotificationServiceProviderInput {
27
+ readonly id: string;
28
+ readonly notifications: NotificationProvider;
29
+ /**
30
+ * The topics resolved for the target, as the provider was created with
31
+ * them: the v1 adapter answers `configuration-required` for a topic outside
32
+ * them, and so for an entry that still carries a catalog's `targets`,
33
+ * which names no single target.
34
+ */
35
+ readonly topics: readonly Pick<NotificationTopicDeclaration, 'id'>[];
36
+ }
37
+ /**
38
+ * Registry entry for a notifications v2 provider, as
39
+ * `createCommerceServiceProvider` is for commerce: it declares no feature and
40
+ * no method, the nine `notifications.v2.*` methods are implied and served in
41
+ * process by `createNotificationProviderBridge`, and the gateway exposes
42
+ * `notificationsV2` and a v1 `notifications` adapter served from it.
43
+ */
44
+ export declare function createNotificationServiceProvider(input: CreateNotificationServiceProviderInput): CapacitorNotificationServiceProvider;
45
+ /**
46
+ * The v1 `NotificationSubscriptionAdapter` served from a v2 provider whose
47
+ * host is readable (RFC "Migration and PR plan" table, through
48
+ * `toNotificationSubscriptionAdapter`): every state comes from the host, a
49
+ * topic outside `topics` reads `configuration-required` and requests
50
+ * `unavailable`.
51
+ */
52
+ export declare function createNotificationSubscriptionAdapter(provider: Pick<NotificationProvider, 'getPermission' | 'requestPermission'>, topics: readonly Pick<NotificationTopicDeclaration, 'id'>[]): NotificationSubscriptionAdapter;
53
+ export interface CreateBridgeNotificationProviderInput {
54
+ readonly provider: NotificationProviderId;
55
+ /** The optional methods the serving provider implements; `openSettings` is always present. */
56
+ readonly serves: {
57
+ readonly revokePermission: boolean;
58
+ readonly register: boolean;
59
+ /** `scheduleLocal`, `cancelLocal`, and `listLocal`. */
60
+ readonly local: boolean;
61
+ };
62
+ /** Sends one bridge request; the gateway's `request` fits. */
63
+ readonly request: <TData>(method: BridgeNotificationsV2Method, payload: unknown) => Promise<TData>;
64
+ readonly subscribe: (listener: (event: BridgeNotificationsEvent) => void) => () => void;
65
+ }
66
+ /**
67
+ * `NotificationProvider` over a bridge that serves the `notifications.v2.*`
68
+ * methods and the event envelope. Answers are validated again on this side,
69
+ * so a transport that forgets to stays fail-closed. A bridge failure becomes
70
+ * a `NotificationError`: one with a notifications code keeps it, anything
71
+ * else is `NOTIFICATIONS_TEMPORARILY_UNAVAILABLE` with the failure's
72
+ * retryable flag. An answer the contract rejects is
73
+ * `NOTIFICATIONS_CONFIGURATION_REQUIRED`, which is not retryable, because the
74
+ * provider broke the contract. An event envelope that
75
+ * names another protocol is skipped; a malformed one is dropped with a
76
+ * warning, because it carries nothing the game could route.
77
+ *
78
+ * Events reach the listeners subscribed when they arrive. The Capacitor
79
+ * provider keeps events that arrive while no listener is attached for the
80
+ * next subscriber, so a game that subscribes per scene loses no open.
81
+ */
82
+ export declare function createBridgeNotificationProvider(input: CreateBridgeNotificationProviderInput): NotificationProvider;