@mpgd/adapter-capacitor 0.4.11 → 0.5.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 ADDED
@@ -0,0 +1,225 @@
1
+ # @mpgd/adapter-capacitor
2
+
3
+ The base adapter uses `@mpgd/capacitor-game-services` for bounded local JSON
4
+ storage and a small fail-closed bridge. It does **not** install a store, ad,
5
+ identity, leaderboard, or push SDK. Pass separately installed modules through
6
+ `createCapacitorPlatformGateway({ providers })`; each module declares its
7
+ owned bridge methods, provider features, and current availability. Duplicate
8
+ method/feature registrations and attempts to replace base storage are errors.
9
+
10
+ ```ts
11
+ import {
12
+ createCapacitorPlatformGateway,
13
+ type CapacitorServiceProvider,
14
+ } from '@mpgd/adapter-capacitor';
15
+
16
+ export function createGameGateway(providers: readonly CapacitorServiceProvider[]) {
17
+ return createCapacitorPlatformGateway({
18
+ target: 'android',
19
+ appVersion: '1.0.0',
20
+ buildId: 'game-build',
21
+ providers,
22
+ classifyIncomingUrl(url) {
23
+ const incoming = new URL(url);
24
+ // Replace this example scheme with one registered by your native host.
25
+ if (incoming.protocol !== 'mygame:') return null;
26
+ if (incoming.hostname === 'oauth') return 'oauth';
27
+ if (incoming.hostname === 'game') return 'game';
28
+ return null;
29
+ },
30
+ });
31
+ }
32
+ ```
33
+
34
+ `getCapabilities()` reports a fresh boolean snapshot and optional
35
+ `providerAvailability` detail. The possible states are `unsupported`,
36
+ `configuration-required`, `action-required`, `temporarily-unavailable`, and
37
+ `available`. A target's `features` configuration is an upper bound, **not**
38
+ proof that a provider is installed or ready. `nativeIap` describes one-time
39
+ purchases; `subscriptionIap` is separate. Rewarded, interstitial, and banner
40
+ ads and native versus remote leaderboards remain distinct. Target-config
41
+ applies the configured upper bound to this live provider state.
42
+
43
+ When native and remote leaderboard routes coexist, the target-config wrapper
44
+ selects a `route` on score/open calls; direct adapter callers can request
45
+ `route: 'remote'` to use the base remote bridge instead of the native provider.
46
+ When one ads provider handles multiple formats, pass `format` to `ads.preload`
47
+ so its readiness check cannot use an available format for an unavailable one.
48
+
49
+ Provider initialization failures and availability reads stalled beyond three
50
+ seconds leave the base bridge and local guest boot available. A registered
51
+ provider never silently falls back for purchase, ad, or native leaderboard
52
+ operations; an explicit remote leaderboard route is a separate path. Errors
53
+ retain a stable code and retry hint. The
54
+ provider bridge must return a method-shaped response. In particular, an ad
55
+ `rewardGranted: true` result requires a backend ledger entry. A native callback
56
+ alone is evidence, not a grant. Game-specific product, consent, entitlement,
57
+ and identity policy belongs to the consuming game and its backend.
58
+
59
+ The source tests and installed-tarball consumer validate composition, types,
60
+ error paths, and fallback behavior. They do not establish that any optional
61
+ SDK works on a physical device or that a store/ad setup is release-ready.
62
+
63
+ `gateway.secureCredentials` uses dedicated native Keychain/Keystore methods for
64
+ opaque session credentials. Its absence or a native error must not be hidden by
65
+ writing the secret into `gateway.storage`. Keys are bounded, values are
66
+ size-limited, and malformed bridge responses fail closed. This is a storage
67
+ boundary, not proof of server authentication: an installation ID or local
68
+ `playerId` is never a server principal by itself.
69
+
70
+ ## App lifecycle and native entry
71
+
72
+ The adapter uses the official Capacitor App plugin for foreground state,
73
+ Android back navigation, and incoming URLs. Install `@capacitor/app` in the
74
+ native shell and run `cap sync` so the native plugin is present. The reference
75
+ shell and generated game template include it. A `classifyIncomingUrl` callback
76
+ must explicitly return `game` or `oauth`; unknown URLs are discarded rather
77
+ than being delivered to gameplay. Register your URL scheme or App/Universal
78
+ Links in the native host separately. See the [Capacitor App API](https://capacitorjs.com/docs/apis/app).
79
+
80
+ `lifecycle.onPause` and `onResume` deduplicate App and web visibility events.
81
+ Purchases, ads, and identity upgrades keep execution paused until their
82
+ provider operation settles. Use `beginExternalActivity` for other external UI.
83
+ On Android, a back handler returns `true` if the game consumed the event;
84
+ otherwise the adapter navigates browser history when possible or exits the
85
+ app. Cold URLs are read through `getInitialGameUrl` or
86
+ `getInitialOAuthRedirect`; warm URLs use separate `onGameUrlOpen` and
87
+ `onOAuthRedirect` callbacks. OAuth responses never enter the game-link
88
+ callback. Call `lifecycle.dispose()` when the host tears down this gateway;
89
+ the adapter removes only its own listener handles, never every App listener.
90
+
91
+ Save progress at checkpoints and transaction boundaries. A pause callback can
92
+ request a best-effort save, but neither pause nor back is guaranteed to run
93
+ before the operating system terminates a process. The lifecycle tests use an
94
+ injected App API and do not claim device-level lifecycle verification.
95
+
96
+ For example, let the game's checkpoint flow own durability; the native pause
97
+ event is only an additional opportunity to flush the latest checkpoint:
98
+
99
+ ```ts
100
+ import type { PlatformGateway } from '@mpgd/platform';
101
+
102
+ export function attachCheckpointSaving(
103
+ gateway: PlatformGateway,
104
+ saveCheckpoint: () => Promise<void>,
105
+ ): () => void {
106
+ return gateway.lifecycle.onPause(() => {
107
+ void saveCheckpoint().catch((error: unknown) => {
108
+ console.error('Best-effort pause save failed.', error);
109
+ });
110
+ });
111
+ }
112
+
113
+ // The game also awaits saveCheckpoint() at level and transaction boundaries;
114
+ // it must never defer its only save until a close, back, or pause event.
115
+ ```
116
+
117
+ ## Scoped native JSON HTTP
118
+
119
+ `createCapacitorNativeJsonTransport` uses the explicit `CapacitorHttp.request`
120
+ helper from `@capacitor/core`; it does not enable global `fetch` or XHR patching.
121
+ Use it with the HTTP JSON path of `createGameServicesRuntime`, not the oRPC
122
+ path. The game imports only public kit APIs:
123
+
124
+ ```ts
125
+ import { createCapacitorNativeJsonTransport } from '@mpgd/adapter-capacitor';
126
+ import { createGameServicesRuntime } from '@mpgd/game-services/runtime';
127
+
128
+ const baseUrl = 'https://api.example.com';
129
+ const httpTransport = createCapacitorNativeJsonTransport({
130
+ target: 'android',
131
+ baseUrl,
132
+ allowedOrigin: 'https://api.example.com',
133
+ });
134
+ const runtime = createGameServicesRuntime({
135
+ gateway,
136
+ playerId,
137
+ authorityMode: 'production',
138
+ baseUrl,
139
+ transport: 'http',
140
+ httpTransport,
141
+ getHeaders: () => ({ authorization: `Bearer ${currentAccessToken()}` }),
142
+ });
143
+ ```
144
+
145
+ The game supplies its real gateway, player ID, and token resolver. The native
146
+ transport permits only its configured HTTPS origin and relative JSON API paths;
147
+ it rejects 3xx responses and changed routes while tolerating equivalent native
148
+ URL encoding of the same path or query. It requests
149
+ `disableRedirects: true` from Capacitor. A native implementation that ignored
150
+ that option could have followed a redirect before JS sees the result, so
151
+ redirect and credential handling still require native integration testing.
152
+ The transport owns `Accept: application/json` and POST `Content-Type`; callers
153
+ supplying either header are rejected instead of having it silently rewritten.
154
+ It supports GET and POST JSON only, not streaming, file uploads, or arbitrary
155
+ Fetch semantics. `AbortSignal` and the overall timeout stop the
156
+ JS wait; they do **not** prove the native request or server operation was
157
+ canceled. Reconcile purchases and reward claims before retrying them.
158
+
159
+ Request size is checked before calling the native bridge. The response size
160
+ limit is checked **after** Capacitor returns data to JS; it is not a native
161
+ receive-memory cap. The injected Http tests and platform staging builds cover
162
+ the contract, not real network behavior on both physical OSes. See the
163
+ [Capacitor HTTP API](https://capacitorjs.com/docs/apis/http) for the native
164
+ helper and its redirect/timeout options.
165
+
166
+ ## Usable viewport and native occupied surfaces
167
+
168
+ `gateway.viewport` reports the full WebView size and separate safe-area,
169
+ system-bar, keyboard, and named occupied-surface measurements in **CSS pixels**.
170
+ Pass the state to `resolveTargetViewportUsableArea` from `@mpgd/target-config`;
171
+ it takes the furthest intrusion per edge rather than summing overlapping
172
+ safe-area, system-bar, keyboard, and banner values. The default host reads the
173
+ starter's `--mpgd-safe-area-*` CSS variables, Capacitor SystemBars'
174
+ `--safe-area-inset-*` fallback, and `visualViewport` changes. On Android,
175
+ SystemBars injects fallback CSS variables for older WebViews whose CSS `env`
176
+ safe-area values are incorrect. The injected `--safe-area-inset-*` values must
177
+ be valid CSS lengths on `documentElement` (`:root`); putting them only on
178
+ `body` or a nested container does not update the root aliases. See the
179
+ [SystemBars API](https://capacitorjs.com/docs/apis/system-bars).
180
+ Keyboard behavior also depends on the native [Keyboard resize mode](https://capacitorjs.com/docs/apis/keyboard),
181
+ so inspect the actual device layout before promising a particular inset.
182
+
183
+ Choose **one layout owner**. The starter's existing CSS padding already
184
+ reserves safe-area space; do not apply `usableArea.contentBounds` to that
185
+ already-padded `#game` element. For a JS-owned full-viewport canvas and DOM
186
+ overlay, remove the host's safe-area padding and apply the same rectangle to
187
+ both elements:
188
+
189
+ ```ts
190
+ import { resolveTargetViewportUsableArea } from '@mpgd/target-config';
191
+ import type { PlatformGateway } from '@mpgd/platform';
192
+
193
+ export function mountUsableViewport(
194
+ gateway: PlatformGateway,
195
+ canvasHost: HTMLElement,
196
+ overlayHost: HTMLElement,
197
+ ): () => void {
198
+ const viewport = gateway.viewport;
199
+ if (viewport === undefined) return () => undefined;
200
+ const apply = () => {
201
+ const state = viewport.getState();
202
+ const bounds = resolveTargetViewportUsableArea(state, state).contentBounds;
203
+ for (const element of [canvasHost, overlayHost]) {
204
+ element.style.position = 'absolute';
205
+ element.style.left = `${bounds.x}px`;
206
+ element.style.top = `${bounds.y}px`;
207
+ element.style.width = `${bounds.width}px`;
208
+ element.style.height = `${bounds.height}px`;
209
+ }
210
+ };
211
+ apply();
212
+ return viewport.onChange(apply);
213
+ }
214
+
215
+ // Call the returned unsubscribe during game teardown. lifecycle.dispose()
216
+ // tears down the gateway-owned native viewport listeners.
217
+ ```
218
+
219
+ Future banner providers can call `createCapacitorViewport()` and pass that
220
+ controller to `createCapacitorPlatformGateway({ viewport })`, then report
221
+ actual `surfaceId` bounds with `setOccupiedSurface`. For physical-pixel
222
+ measurements, the provider supplies `pixelsPerCssPixel`; the game never guesses
223
+ device pixel ratio. A caller-injected controller remains caller-owned and must
224
+ be disposed separately. Tests cover layout, rotation, multiple surfaces, and
225
+ unsubscribe, but do not establish physical-device inset or keyboard accuracy.
@@ -0,0 +1,23 @@
1
+ import { type AppPlugin } from '@capacitor/app';
2
+ import type { LifecycleAdapter } from '@mpgd/platform';
3
+ export type CapacitorIncomingUrlKind = 'game' | 'oauth';
4
+ export interface CapacitorVisibilitySource {
5
+ readonly hidden: boolean;
6
+ addEventListener(type: 'visibilitychange', listener: () => void): void;
7
+ removeEventListener(type: 'visibilitychange', listener: () => void): void;
8
+ }
9
+ export type CapacitorAppEventsApi = Pick<AppPlugin, 'addListener' | 'getState' | 'getLaunchUrl' | 'exitApp'>;
10
+ export interface CreateCapacitorAppEventsInput {
11
+ readonly target: 'android' | 'ios';
12
+ readonly app?: CapacitorAppEventsApi;
13
+ readonly visibility?: CapacitorVisibilitySource | null;
14
+ /** Return null for URLs that this game does not own. Never default OAuth to game. */
15
+ readonly classifyIncomingUrl?: (url: string) => CapacitorIncomingUrlKind | null;
16
+ readonly historyBack?: () => void;
17
+ readonly onError?: (error: unknown) => void;
18
+ }
19
+ /**
20
+ * Owns only handles it registers. Disposing during an awaited addListener
21
+ * removes the eventual handle without touching another gateway's listeners.
22
+ */
23
+ export declare function createCapacitorAppEvents(input: CreateCapacitorAppEventsInput): LifecycleAdapter;
@@ -0,0 +1,332 @@
1
+ import { App } from '@capacitor/app';
2
+ const maxWarmUrlsBeforeInitial = 32;
3
+ // Covers a near-simultaneous duplicate from getLaunchUrl and appUrlOpen, not a later tap.
4
+ const coldUrlDedupeWindowMs = 2_000;
5
+ function defaultVisibility() {
6
+ return typeof document === 'undefined' ? null : document;
7
+ }
8
+ /**
9
+ * Owns only handles it registers. Disposing during an awaited addListener
10
+ * removes the eventual handle without touching another gateway's listeners.
11
+ */
12
+ export function createCapacitorAppEvents(input) {
13
+ const app = input.app ?? App;
14
+ const visibility = input.visibility === undefined ? defaultVisibility() : input.visibility;
15
+ const pauseCallbacks = new Set();
16
+ const resumeCallbacks = new Set();
17
+ const gameUrlCallbacks = new Set();
18
+ const oauthCallbacks = new Set();
19
+ const backHandlers = [];
20
+ const handles = [];
21
+ const warmUrlsBeforeInitial = new Set();
22
+ let disposed = false;
23
+ let initialResolved = false;
24
+ let appActive = true;
25
+ let visibilityActive = visibility?.hidden !== true;
26
+ let currentActive = true;
27
+ let transitionVersion = 0;
28
+ let stateEvents = 0;
29
+ let externalActivities = 0;
30
+ let backInProgress = false;
31
+ let startFailureReported = false;
32
+ let recentColdUrl;
33
+ let recentColdUntil = 0;
34
+ let startPromise;
35
+ let backPromise;
36
+ let initialPromise;
37
+ const reportError = (error) => {
38
+ try {
39
+ if (input.onError !== undefined) {
40
+ input.onError(error);
41
+ }
42
+ else {
43
+ console.error('Capacitor app event failed.', error);
44
+ }
45
+ }
46
+ catch {
47
+ // Reporting must not replace the original listener or registration error.
48
+ }
49
+ };
50
+ function emit(callbacks, version) {
51
+ for (const callback of [...callbacks]) {
52
+ // A callback can open or close external UI and reenter reconcile().
53
+ // Never continue an older resume/pause emission after that transition.
54
+ if (disposed || version !== transitionVersion) {
55
+ return;
56
+ }
57
+ try {
58
+ callback();
59
+ }
60
+ catch (error) {
61
+ reportError(error);
62
+ }
63
+ }
64
+ }
65
+ function reconcile() {
66
+ if (disposed) {
67
+ return;
68
+ }
69
+ const active = appActive && visibilityActive && externalActivities === 0;
70
+ if (active === currentActive) {
71
+ return;
72
+ }
73
+ currentActive = active;
74
+ transitionVersion += 1;
75
+ emit(active ? resumeCallbacks : pauseCallbacks, transitionVersion);
76
+ }
77
+ function classify(url) {
78
+ try {
79
+ const kind = input.classifyIncomingUrl?.(url);
80
+ return kind === 'game' || kind === 'oauth' ? kind : null;
81
+ }
82
+ catch (error) {
83
+ reportError(error);
84
+ return null;
85
+ }
86
+ }
87
+ function dispatchWarmUrl(url) {
88
+ if (disposed || url.trim() === '') {
89
+ return;
90
+ }
91
+ if (url === recentColdUrl && Date.now() <= recentColdUntil) {
92
+ // Capacitor can deliver the same launch through getLaunchUrl and
93
+ // appUrlOpen. Suppress only that immediate duplicate, not a later tap.
94
+ recentColdUrl = undefined;
95
+ return;
96
+ }
97
+ const kind = classify(url);
98
+ if (kind === null) {
99
+ return;
100
+ }
101
+ const callbacks = kind === 'game' ? gameUrlCallbacks : oauthCallbacks;
102
+ if (!initialResolved && callbacks.size > 0) {
103
+ // Keep enough recent entries to suppress a cold/warm duplicate without
104
+ // retaining an unbounded history when the host never requests cold URLs.
105
+ if (warmUrlsBeforeInitial.size >= maxWarmUrlsBeforeInitial) {
106
+ const oldest = warmUrlsBeforeInitial.values().next().value;
107
+ if (oldest !== undefined) {
108
+ warmUrlsBeforeInitial.delete(oldest);
109
+ }
110
+ }
111
+ warmUrlsBeforeInitial.add(url);
112
+ }
113
+ const event = { url, source: 'warm' };
114
+ for (const callback of [...callbacks]) {
115
+ try {
116
+ callback(event);
117
+ }
118
+ catch (error) {
119
+ reportError(error);
120
+ }
121
+ }
122
+ }
123
+ async function own(registration) {
124
+ const handle = await registration;
125
+ if (disposed) {
126
+ await handle.remove();
127
+ }
128
+ else {
129
+ handles.push(handle);
130
+ }
131
+ }
132
+ function onVisibilityChange() {
133
+ visibilityActive = visibility?.hidden !== true;
134
+ reconcile();
135
+ }
136
+ function start() {
137
+ if (disposed) {
138
+ return Promise.reject(new Error('Capacitor app events are disposed.'));
139
+ }
140
+ if (startPromise !== undefined) {
141
+ return startPromise;
142
+ }
143
+ visibility?.addEventListener('visibilitychange', onVisibilityChange);
144
+ onVisibilityChange();
145
+ const beforeRead = stateEvents;
146
+ startPromise = Promise.all([
147
+ own(app.addListener('appStateChange', ({ isActive }) => {
148
+ stateEvents += 1;
149
+ appActive = isActive;
150
+ reconcile();
151
+ })),
152
+ own(app.addListener('appUrlOpen', ({ url }) => dispatchWarmUrl(url))),
153
+ app.getState().then(({ isActive }) => {
154
+ if (!disposed && stateEvents === beforeRead) {
155
+ appActive = isActive;
156
+ reconcile();
157
+ }
158
+ }),
159
+ ]).then(() => undefined);
160
+ return startPromise;
161
+ }
162
+ function startForSubscription() {
163
+ void start().catch((error) => {
164
+ if (startFailureReported) {
165
+ return;
166
+ }
167
+ startFailureReported = true;
168
+ reportError(error);
169
+ });
170
+ }
171
+ async function dispatchBack(event) {
172
+ for (const handler of [...backHandlers].reverse()) {
173
+ try {
174
+ if (await handler(event)) {
175
+ return;
176
+ }
177
+ if (disposed) {
178
+ return;
179
+ }
180
+ }
181
+ catch (error) {
182
+ reportError(error);
183
+ }
184
+ }
185
+ if (disposed) {
186
+ return;
187
+ }
188
+ if (event.canGoBack) {
189
+ if (input.historyBack !== undefined) {
190
+ input.historyBack();
191
+ }
192
+ else if (typeof history !== 'undefined') {
193
+ history.back();
194
+ }
195
+ }
196
+ else {
197
+ await app.exitApp();
198
+ }
199
+ }
200
+ function startBackListener() {
201
+ if (input.target !== 'android' || backPromise !== undefined || disposed) {
202
+ return;
203
+ }
204
+ backPromise = own(app.addListener('backButton', (event) => {
205
+ if (backInProgress || disposed) {
206
+ return;
207
+ }
208
+ backInProgress = true;
209
+ void dispatchBack(event).catch(reportError).finally(() => {
210
+ backInProgress = false;
211
+ });
212
+ }));
213
+ void backPromise.catch(reportError);
214
+ }
215
+ function subscribe(callbacks, callback) {
216
+ if (disposed) {
217
+ return () => { };
218
+ }
219
+ callbacks.add(callback);
220
+ startForSubscription();
221
+ return () => {
222
+ callbacks.delete(callback);
223
+ };
224
+ }
225
+ function getInitial() {
226
+ if (initialPromise !== undefined) {
227
+ return initialPromise;
228
+ }
229
+ startForSubscription();
230
+ initialPromise = app.getLaunchUrl().then((launch) => {
231
+ initialResolved = true;
232
+ const url = launch?.url;
233
+ const seenWarm = url === undefined ? false : warmUrlsBeforeInitial.has(url);
234
+ warmUrlsBeforeInitial.clear();
235
+ if (disposed || url === undefined || url.trim() === '' || seenWarm) {
236
+ return null;
237
+ }
238
+ const kind = classify(url);
239
+ if (kind === null) {
240
+ return null;
241
+ }
242
+ recentColdUrl = url;
243
+ recentColdUntil = Date.now() + coldUrlDedupeWindowMs;
244
+ return { kind, event: { url, source: 'cold' } };
245
+ }, (error) => {
246
+ initialResolved = true;
247
+ warmUrlsBeforeInitial.clear();
248
+ throw error;
249
+ });
250
+ return initialPromise;
251
+ }
252
+ async function dispose() {
253
+ if (disposed) {
254
+ return;
255
+ }
256
+ disposed = true;
257
+ visibility?.removeEventListener('visibilitychange', onVisibilityChange);
258
+ pauseCallbacks.clear();
259
+ resumeCallbacks.clear();
260
+ gameUrlCallbacks.clear();
261
+ oauthCallbacks.clear();
262
+ backHandlers.length = 0;
263
+ warmUrlsBeforeInitial.clear();
264
+ recentColdUrl = undefined;
265
+ // Pending registrations remove themselves in own() if they later resolve.
266
+ // Teardown must not hang on an SDK registration that never does.
267
+ await Promise.allSettled(handles.splice(0).map((handle) => handle.remove()));
268
+ }
269
+ return {
270
+ onPause(callback) {
271
+ // start() may synchronously emit the first pause while subscribing.
272
+ const alreadyPaused = !disposed && currentActive === false;
273
+ const unsubscribe = subscribe(pauseCallbacks, callback);
274
+ if (alreadyPaused) {
275
+ try {
276
+ callback();
277
+ }
278
+ catch (error) {
279
+ reportError(error);
280
+ }
281
+ }
282
+ return unsubscribe;
283
+ },
284
+ onResume(callback) {
285
+ return subscribe(resumeCallbacks, callback);
286
+ },
287
+ onBackButton(handler) {
288
+ if (disposed || input.target !== 'android') {
289
+ return () => { };
290
+ }
291
+ backHandlers.push(handler);
292
+ startBackListener();
293
+ return () => {
294
+ const index = backHandlers.indexOf(handler);
295
+ if (index >= 0) {
296
+ backHandlers.splice(index, 1);
297
+ }
298
+ };
299
+ },
300
+ onGameUrlOpen(callback) {
301
+ return subscribe(gameUrlCallbacks, callback);
302
+ },
303
+ onOAuthRedirect(callback) {
304
+ return subscribe(oauthCallbacks, callback);
305
+ },
306
+ async getInitialGameUrl() {
307
+ const initial = await getInitial();
308
+ return initial?.kind === 'game' ? initial.event : null;
309
+ },
310
+ async getInitialOAuthRedirect() {
311
+ const initial = await getInitial();
312
+ return initial?.kind === 'oauth' ? initial.event : null;
313
+ },
314
+ beginExternalActivity() {
315
+ if (disposed) {
316
+ return () => { };
317
+ }
318
+ externalActivities += 1;
319
+ reconcile();
320
+ let released = false;
321
+ return () => {
322
+ if (released) {
323
+ return;
324
+ }
325
+ released = true;
326
+ externalActivities -= 1;
327
+ reconcile();
328
+ };
329
+ },
330
+ dispose,
331
+ };
332
+ }
package/dist/index.d.ts CHANGED
@@ -1,11 +1,20 @@
1
- import { type BridgeRequest, type BridgeResponse } from '@mpgd/bridge';
2
- import type { PlatformGateway, PlatformTarget } from '@mpgd/platform';
3
- export interface NativeBridge {
4
- request(input: BridgeRequest): Promise<BridgeResponse>;
5
- }
1
+ import { type PlatformGateway, type PlatformTarget, type ViewportAdapter } from '@mpgd/platform';
2
+ import { type CapacitorAppEventsApi, type CapacitorIncomingUrlKind, type CapacitorVisibilitySource } from './app-events.js';
3
+ import { type CapacitorServiceProvider, type NativeBridge } from './providers.js';
4
+ export { createCapacitorProviderRegistry, type CapacitorServiceProvider, type NativeBridge, } from './providers.js';
5
+ export { createCapacitorAppEvents, type CapacitorAppEventsApi, type CapacitorIncomingUrlKind, type CapacitorVisibilitySource, } from './app-events.js';
6
+ export { CapacitorNativeHttpError, createCapacitorNativeJsonTransport, type CapacitorJsonRequest, type CapacitorJsonResponse, type CapacitorNativeHttpErrorCode, type CapacitorNativeJsonTransport, type CreateCapacitorNativeJsonTransportInput, } from './native-http.js';
7
+ export { createCapacitorViewport, type CapacitorViewportBaseState, type CapacitorOccupiedSurfaceInput, type CapacitorViewportController, type CapacitorViewportHost, } from './viewport.js';
6
8
  export declare function createCapacitorPlatformGateway(input: {
7
9
  readonly target: Extract<PlatformTarget, 'android' | 'ios'>;
8
10
  readonly appVersion: string;
9
11
  readonly buildId: string;
10
12
  readonly bridge?: NativeBridge;
13
+ readonly providers?: readonly CapacitorServiceProvider[];
14
+ readonly app?: CapacitorAppEventsApi;
15
+ readonly visibility?: CapacitorVisibilitySource | null;
16
+ readonly classifyIncomingUrl?: (url: string) => CapacitorIncomingUrlKind | null;
17
+ readonly historyBack?: () => void;
18
+ readonly onAppEventError?: (error: unknown) => void;
19
+ readonly viewport?: ViewportAdapter;
11
20
  }): PlatformGateway;