@mpgd/adapter-capacitor 0.10.1 → 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 +126 -19
- package/dist/bridge-events.d.ts +7 -0
- package/dist/bridge-events.js +14 -0
- package/dist/commerce-bridge.js +2 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +33 -12
- package/dist/notifications-bridge.d.ts +82 -0
- package/dist/notifications-bridge.js +333 -0
- package/dist/notifications-provider.d.ts +118 -0
- package/dist/notifications-provider.js +1023 -0
- package/dist/notifications-sdk.d.ts +166 -0
- package/dist/notifications-sdk.js +47 -0
- package/dist/notifications.d.ts +27 -0
- package/dist/notifications.js +35 -0
- package/dist/providers.d.ts +56 -3
- package/dist/providers.js +98 -18
- package/package.json +22 -8
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
|
-
|
|
188
|
-
[notifications RFC](../../docs/specs/notifications/notifications-v2-rfc.md)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
+
}
|
package/dist/commerce-bridge.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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;
|