@vkma.dev/bridge 0.0.0-stage → 0.2.1

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 pajecawav
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 CHANGED
@@ -1,3 +1,89 @@
1
- # Temporary Holding Version
1
+ # @vkma.dev/bridge
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/@vkma.dev/bridge.svg)](https://www.npmjs.com/package/@vkma.dev/bridge)
4
+ [![minzip](https://img.shields.io/bundlephobia/minzip/@vkma.dev/bridge.svg)](https://bundlephobia.com/package/@vkma.dev/bridge)
5
+
6
+ Independent VK Bridge for VK Mini Apps. Drop-in classic API (`send` / `subscribe` / `supportsAsync` / …) plus a built-in observability tap, per-send timeout/abort and a configurable `request_id` policy.
7
+
8
+ Types come from [`@vkma.dev/bridge-schema`](https://github.com/pajecawav/vkma/tree/master/packages/bridge-schema#readme) — the catalogue of every platform event as data, with an open interface for your own events.
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ npm install @vkma.dev/bridge
14
+ # or: pnpm add / yarn add / bun add
15
+ ```
16
+
17
+ ## Use
18
+
19
+ ```ts
20
+ import bridge from "@vkma.dev/bridge";
21
+
22
+ await bridge.send("VKWebAppInit");
23
+ const user = await bridge.send("VKWebAppGetUserInfo");
24
+
25
+ bridge.subscribe(event => {
26
+ // event.detail.type / event.detail.data — every event from the host
27
+ });
28
+ ```
29
+
30
+ Or build isolated instances (own tap, own pairing state):
31
+
32
+ ```ts
33
+ import { createVKBridge } from "@vkma.dev/bridge";
34
+
35
+ const bridge = createVKBridge("1.0.0", {
36
+ // the version string is reported to the web host
37
+ requestId: { generator: sequence => `req-${sequence}` },
38
+ });
39
+ ```
40
+
41
+ ## Beyond the classic API
42
+
43
+ Everything lives on the instance — no extra imports:
44
+
45
+ ```ts
46
+ // Every outgoing send / incoming event, always on, replayed to late subscribers
47
+ const off = bridge.onSend(event => log(event.name, event.payload));
48
+ const off2 = bridge.onEvent(event => log(event.name, event.ok));
49
+
50
+ // Per-send controls
51
+ await bridge.send("VKWebAppGetUserInfo", undefined, { timeout: 5000 });
52
+ await bridge.send("VKWebAppShowWallPostBox", props, { signal: controller.signal });
53
+
54
+ // request_id policy (default: auto-inject __vkma_<n>, that's what pairing uses)
55
+ createVKBridge("1.0.0", { requestId: { inject: false } });
56
+ ```
57
+
58
+ The page-wide default instance also publishes the devtools handle on `globalThis.__vkmaDevtoolsBridge` (`{ tap, send }`) — that's the contract the [VKMA devtools](https://github.com/pajecawav/vkma/tree/master/packages/devtools#readme) collector observes. Importing the package is enough; no opt-in.
59
+
60
+ ## Types & custom events
61
+
62
+ All payload types and the three catalogue maps are re-exported here. To teach the bridge your own (or your proxy backend's) events, extend the schema package — `send`, `subscribe` and every consumer of the types pick it up:
63
+
64
+ ```ts
65
+ declare module "@vkma.dev/bridge-schema" {
66
+ interface RequestPropsMap {
67
+ VKWebAppMyCustomMethod: { foo: string };
68
+ }
69
+ interface ReceiveDataMap {
70
+ VKWebAppMyCustomMethodResult: { bar: number };
71
+ }
72
+ interface ReceiveEventMap {
73
+ VKWebAppMyCustomMethod: {
74
+ result: "VKWebAppMyCustomMethodResult";
75
+ failed: "VKWebAppMyCustomMethodFailed";
76
+ };
77
+ }
78
+ }
79
+ ```
80
+
81
+ The rules live in [`@vkma.dev/bridge-schema`](https://github.com/pajecawav/vkma/tree/master/packages/bridge-schema#readme) — augment that package's root, extend only.
82
+
83
+ ## Transports
84
+
85
+ Web (iframe `postMessage` with the `vk-connect` envelope), Android WebView, iOS WebView and React Native WebView — with receive-side `message` / `VKWebAppEvent` listeners to match. The web transport is covered by the automated suite; the mobile WebView transports follow the platform host contracts.
86
+
87
+ ## License
88
+
89
+ MIT.
@@ -0,0 +1,38 @@
1
+ import { BridgeTap } from "./tap.js";
2
+ import { VkmaBridge } from "./types.js";
3
+ //#region src/bridge.d.ts
4
+ /**
5
+ * request_id policy of a bridge instance.
6
+ */
7
+ export interface RequestIdPolicy {
8
+ /**
9
+ * Own generator of outgoing request ids; receives an incrementing
10
+ * sequence. The default mints `__vkma_<n>` — the ids the devtools tap
11
+ * pairs responses by.
12
+ */
13
+ generator?: (sequence: number) => string | number;
14
+ /**
15
+ * Turn the auto-injection off: sends pass caller props untouched, so
16
+ * promise pairing only works for calls that provide their own
17
+ * `request_id`. Default: on.
18
+ */
19
+ inject?: boolean;
20
+ }
21
+ export interface VkmaBridgeOptions {
22
+ /** Outgoing request_id policy. */
23
+ requestId?: RequestIdPolicy;
24
+ /** Inject a shared tap (the default instance passes the page-wide one). */
25
+ tap?: BridgeTap;
26
+ /** Version string reported to the web host in the vk-connect envelope. */
27
+ version?: string;
28
+ }
29
+ /**
30
+ * Creates a VK Bridge instance: transport dispatch, subscription fan-out,
31
+ * promise pairing and the always-on tap.
32
+ *
33
+ * @param version Version of the package (reported to the web host).
34
+ * @param [options] Tap sharing and the request_id policy.
35
+ */
36
+ export declare function createVKBridge(version: string, options?: VkmaBridgeOptions): VkmaBridge;
37
+ //#endregion
38
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","names":[],"sources":["../src/bridge.ts"],"mappings":";;;;;;iBA6JiB;;;;;;EAMhB,aAAa;;;;;;EAMb;;iBAGgB;;EAEhB,YAAY;;EAEZ,MAAM;;EAEN;;;;;;;;;wBAkBe,eAAe,iBAAiB,UAAS,oBAAyB"}
package/dist/bridge.js ADDED
@@ -0,0 +1,384 @@
1
+ import { createTap } from "./tap.js";
2
+ //#region src/bridge.ts
3
+ /**
4
+ * The bridge core: transport dispatch, subscription fan-out, promise pairing
5
+ * and the always-on tap.
6
+ *
7
+ * Transports (checked lazily on every call, unlike the load-time caches of
8
+ * the reference implementation — same in practice, testable in unit tests):
9
+ *
10
+ * - Android WebView: `window.AndroidBridge[method](JSON.stringify(props))`
11
+ * - iOS WebView: `window.webkit.messageHandlers[method].postMessage(props)`
12
+ * - React Native WebView: `window.ReactNativeWebView.postMessage(JSON)`
13
+ * - Web (iframe on vk.ru / m.vk.ru): `parent.postMessage` with the
14
+ * `type: "vk-connect"` envelope; receive side listens to `message`
15
+ * - WebViews receive via `CustomEvent("VKWebAppEvent")`
16
+ *
17
+ * The wire format is the platform interface — the host clients on the other
18
+ * side of these bridges define it, which is why the envelopes above are
19
+ * reproduced exactly.
20
+ */
21
+ /** Methods supported on the desktop web hosts. */
22
+ const DESKTOP_METHODS = /* @__PURE__ */ new Set([
23
+ "VKWebAppInit",
24
+ "VKWebAppGetCommunityAuthToken",
25
+ "VKWebAppAddToCommunity",
26
+ "VKWebAppAddToHomeScreenInfo",
27
+ "VKWebAppClose",
28
+ "VKWebAppCopyText",
29
+ "VKWebAppCreateHash",
30
+ "VKWebAppGetUserInfo",
31
+ "VKWebAppSetLocation",
32
+ "VKWebAppSendToClient",
33
+ "VKWebAppGetClientVersion",
34
+ "VKWebAppGetPhoneNumber",
35
+ "VKWebAppGetEmail",
36
+ "VKWebAppGetGroupInfo",
37
+ "VKWebAppGetGeodata",
38
+ "VKWebAppGetCommunityToken",
39
+ "VKWebAppGetConfig",
40
+ "VKWebAppGetLaunchParams",
41
+ "VKWebAppSetTitle",
42
+ "VKWebAppGetAuthToken",
43
+ "VKWebAppCallAPIMethod",
44
+ "VKWebAppJoinGroup",
45
+ "VKWebAppLeaveGroup",
46
+ "VKWebAppAllowMessagesFromGroup",
47
+ "VKWebAppDenyNotifications",
48
+ "VKWebAppAllowNotifications",
49
+ "VKWebAppOpenPayForm",
50
+ "VKWebAppOpenApp",
51
+ "VKWebAppShare",
52
+ "VKWebAppShowWallPostBox",
53
+ "VKWebAppScroll",
54
+ "VKWebAppShowOrderBox",
55
+ "VKWebAppShowLeaderBoardBox",
56
+ "VKWebAppShowInviteBox",
57
+ "VKWebAppShowRequestBox",
58
+ "VKWebAppAddToFavorites",
59
+ "VKWebAppShowStoryBox",
60
+ "VKWebAppStorageGet",
61
+ "VKWebAppStorageGetKeys",
62
+ "VKWebAppStorageSet",
63
+ "VKWebAppFlashGetInfo",
64
+ "VKWebAppSubscribeStoryApp",
65
+ "VKWebAppOpenWallPost",
66
+ "VKWebAppCheckAllowedScopes",
67
+ "VKWebAppCheckBannerAd",
68
+ "VKWebAppHideBannerAd",
69
+ "VKWebAppShowBannerAd",
70
+ "VKWebAppCheckNativeAds",
71
+ "VKWebAppShowNativeAds",
72
+ "VKWebAppRetargetingPixel",
73
+ "VKWebAppConversionHit",
74
+ "VKWebAppShowSubscriptionBox",
75
+ "VKWebAppCheckSurvey",
76
+ "VKWebAppShowSurvey",
77
+ "VKWebAppScrollTop",
78
+ "VKWebAppScrollTopStart",
79
+ "VKWebAppScrollTopStop",
80
+ "VKWebAppShowSlidesSheet",
81
+ "VKWebAppTranslate",
82
+ "VKWebAppRecommend",
83
+ "VKWebAppAddToProfile",
84
+ "VKWebAppGetFriends",
85
+ ...isDesktopVk() ? [
86
+ "VKWebAppResizeWindow",
87
+ "VKWebAppAddToMenu",
88
+ "VKWebAppShowInstallPushBox",
89
+ "VKWebAppShowCommunityWidgetPreviewBox",
90
+ "VKWebAppCallStart",
91
+ "VKWebAppCallJoin",
92
+ "VKWebAppCallGetStatus"
93
+ ] : ["VKWebAppShowImages"]
94
+ ]);
95
+ function clientWindow() {
96
+ return typeof window === "undefined" ? void 0 : window;
97
+ }
98
+ /**
99
+ * The native side of the host (AndroidBridge / webkit / ReactNativeWebView)
100
+ * lives on the window but is not part of any DOM typing — tests install
101
+ * fakes under the same keys.
102
+ */
103
+ function hostGlobal() {
104
+ const win = clientWindow();
105
+ return win === void 0 ? void 0 : win;
106
+ }
107
+ function isClientSide() {
108
+ return typeof window !== "undefined";
109
+ }
110
+ function isAndroidWebview() {
111
+ return Boolean(hostGlobal()?.["AndroidBridge"]);
112
+ }
113
+ function isIosWebview() {
114
+ const webkit = hostGlobal()?.["webkit"];
115
+ return Boolean(webkit?.messageHandlers?.["VKWebAppClose"]);
116
+ }
117
+ function isReactNativeWebview() {
118
+ const reactNative = hostGlobal()?.["ReactNativeWebView"];
119
+ return Boolean(reactNative && typeof reactNative.postMessage === "function");
120
+ }
121
+ function isWeb() {
122
+ return isClientSide() && !isAndroidWebview() && !isIosWebview();
123
+ }
124
+ function isMvk() {
125
+ return isWeb() && /(^\?|&)vk_platform=mobile_web(&|$)/.test(clientWindow()?.location.search ?? "");
126
+ }
127
+ function isDesktopVk() {
128
+ return isWeb() && !isMvk();
129
+ }
130
+ /**
131
+ * Creates a VK Bridge instance: transport dispatch, subscription fan-out,
132
+ * promise pairing and the always-on tap.
133
+ *
134
+ * @param version Version of the package (reported to the web host).
135
+ * @param [options] Tap sharing and the request_id policy.
136
+ */
137
+ function createVKBridge(version, options = {}) {
138
+ const tap = options.tap ?? createTap();
139
+ const requestIdPolicy = options.requestId ?? {};
140
+ /** Current frame id, assigned by the host via VKWebAppSettings. */
141
+ let webFrameId;
142
+ /** Functions subscribed to bridge events. */
143
+ const subscribers = [];
144
+ /** Pending promise controllers by outgoing request_id. */
145
+ const pendingRequests = /* @__PURE__ */ new Map();
146
+ let requestCounter = 0;
147
+ let supportedHandlers;
148
+ const isPlainObject = (value) => typeof value === "object" && value !== null;
149
+ function sendThroughTransport(method, props) {
150
+ const host = hostGlobal();
151
+ const android = host?.["AndroidBridge"];
152
+ if (typeof android?.[method] === "function") {
153
+ android[method](JSON.stringify(props));
154
+ return;
155
+ }
156
+ const iosHandlers = (host?.["webkit"])?.messageHandlers;
157
+ if (typeof iosHandlers?.[method]?.postMessage === "function") {
158
+ iosHandlers[method].postMessage(props);
159
+ return;
160
+ }
161
+ if (isReactNativeWebview()) {
162
+ (host?.["ReactNativeWebView"]).postMessage(JSON.stringify({
163
+ handler: method,
164
+ params: props
165
+ }));
166
+ return;
167
+ }
168
+ const win = clientWindow();
169
+ const parent = win?.parent;
170
+ if (win && parent && typeof parent.postMessage === "function") parent.postMessage({
171
+ handler: method,
172
+ params: props,
173
+ type: "vk-connect",
174
+ webFrameId,
175
+ connectVersion: version
176
+ }, "*");
177
+ }
178
+ function handleEvent(event) {
179
+ if (isIosWebview() || isAndroidWebview()) {
180
+ deliver(event);
181
+ return;
182
+ }
183
+ let bridgeEventData = event?.data;
184
+ if (!isWeb() || !bridgeEventData) return;
185
+ if (isReactNativeWebview() && typeof bridgeEventData === "string") try {
186
+ bridgeEventData = JSON.parse(bridgeEventData);
187
+ } catch {}
188
+ if (!isPlainObject(bridgeEventData)) return;
189
+ const { type, data, frameId } = bridgeEventData;
190
+ if (!type) return;
191
+ if (type === "VKWebAppSettings") {
192
+ webFrameId = typeof frameId === "string" ? frameId : webFrameId;
193
+ return;
194
+ }
195
+ deliver({ detail: {
196
+ type,
197
+ data
198
+ } });
199
+ }
200
+ /**
201
+ * Upstream parity: every incoming event reaches `[...subscribers]` —
202
+ * paired responses included, with the raw wire data (upstream's
203
+ * promisifySend is itself a subscriber and strips request_id only for
204
+ * the promise). Snapshot iteration: a handler may unsubscribe mid-fan.
205
+ */
206
+ function fanOutToSubscribers(type, data) {
207
+ const out = { detail: {
208
+ type,
209
+ data
210
+ } };
211
+ for (const listener of subscribers.slice()) listener(out);
212
+ }
213
+ /** Fan an incoming event out to the tap and to the subscribers. */
214
+ function deliver(event) {
215
+ const { type, data } = event.detail ?? {};
216
+ if (typeof type !== "string") return;
217
+ if (isPlainObject(data)) {
218
+ const wireRequestId = data["request_id"];
219
+ if (typeof wireRequestId !== "string" && typeof wireRequestId !== "number") {
220
+ tap.emit({
221
+ direction: "in",
222
+ kind: "event",
223
+ name: type,
224
+ payload: data
225
+ });
226
+ fanOutToSubscribers(type, data);
227
+ return;
228
+ }
229
+ const requestId = String(wireRequestId);
230
+ const pending = pendingRequests.get(requestId);
231
+ if (pending) {
232
+ pendingRequests.delete(requestId);
233
+ const { request_id: _stripped, ...payload } = data;
234
+ const ok = !("error_type" in data);
235
+ pending.cleanup();
236
+ tap.emit({
237
+ direction: "in",
238
+ kind: "response",
239
+ name: type,
240
+ payload,
241
+ reqId: pending.tapId,
242
+ ok
243
+ });
244
+ if (ok) pending.resolve(payload);
245
+ else pending.reject(payload);
246
+ fanOutToSubscribers(type, data);
247
+ return;
248
+ }
249
+ }
250
+ tap.emit({
251
+ direction: "in",
252
+ kind: "event",
253
+ name: type,
254
+ payload: data
255
+ });
256
+ fanOutToSubscribers(type, data);
257
+ }
258
+ function subscribe(listener) {
259
+ subscribers.push(listener);
260
+ }
261
+ function unsubscribe(listener) {
262
+ const index = subscribers.indexOf(listener);
263
+ if (index > -1) subscribers.splice(index, 1);
264
+ }
265
+ if (isClientSide()) {
266
+ const win = clientWindow();
267
+ if (isReactNativeWebview() && /(android)/i.test(win?.navigator.userAgent ?? "")) document.addEventListener("message", handleEvent);
268
+ else if (win && "addEventListener" in win) win.addEventListener(isWeb() ? "message" : "VKWebAppEvent", handleEvent);
269
+ }
270
+ const send = ((method, props, sendOptions) => {
271
+ return new Promise((resolve, reject) => {
272
+ const signal = sendOptions?.signal;
273
+ if (signal?.aborted) {
274
+ const error = /* @__PURE__ */ new Error(`VK Bridge send "${method}" aborted`);
275
+ error.name = "AbortError";
276
+ reject(error);
277
+ return;
278
+ }
279
+ const sourceProps = { ...props };
280
+ const explicitId = typeof sourceProps["request_id"] === "string" || typeof sourceProps["request_id"] === "number" ? sourceProps["request_id"] : void 0;
281
+ let requestId;
282
+ if (explicitId !== void 0) requestId = explicitId;
283
+ else if (requestIdPolicy.inject !== false) {
284
+ const sequence = ++requestCounter;
285
+ requestId = requestIdPolicy.generator ? requestIdPolicy.generator(sequence) : `__vkma_${sequence}`;
286
+ }
287
+ const wireProps = requestId === void 0 ? sourceProps : {
288
+ ...sourceProps,
289
+ request_id: requestId
290
+ };
291
+ const tapId = tap.emit({
292
+ direction: "out",
293
+ kind: "send",
294
+ name: method,
295
+ payload: wireProps
296
+ });
297
+ let timer;
298
+ let signalHandler;
299
+ const cleanup = () => {
300
+ if (timer !== void 0) clearTimeout(timer);
301
+ if (signal && signalHandler) signal.removeEventListener("abort", signalHandler);
302
+ };
303
+ const timeoutReject = () => {
304
+ if (requestId !== void 0 && !pendingRequests.delete(String(requestId))) return;
305
+ cleanup();
306
+ const error = /* @__PURE__ */ new Error(`VK Bridge send "${method}" timed out after ${String(sendOptions?.timeout)}ms`);
307
+ error.name = "TimeoutError";
308
+ reject(error);
309
+ };
310
+ const abortReject = () => {
311
+ if (requestId !== void 0 && !pendingRequests.delete(String(requestId))) return;
312
+ cleanup();
313
+ const error = /* @__PURE__ */ new Error(`VK Bridge send "${method}" aborted`);
314
+ error.name = "AbortError";
315
+ reject(error);
316
+ };
317
+ if (requestId !== void 0) pendingRequests.set(String(requestId), {
318
+ resolve,
319
+ reject,
320
+ tapId,
321
+ options: sendOptions ?? {},
322
+ cleanup
323
+ });
324
+ if (sendOptions?.timeout !== void 0) timer = setTimeout(timeoutReject, sendOptions.timeout);
325
+ if (signal) {
326
+ signalHandler = abortReject;
327
+ signal.addEventListener("abort", signalHandler);
328
+ }
329
+ sendThroughTransport(method, wireProps);
330
+ });
331
+ });
332
+ subscribe((event) => {
333
+ if (event.detail?.type === "SetSupportedHandlers" && isPlainObject(event.detail.data)) {
334
+ const handlers = event.detail.data.supportedHandlers;
335
+ if (Array.isArray(handlers)) supportedHandlers = new Set(handlers.filter((name) => typeof name === "string"));
336
+ }
337
+ });
338
+ function supportsInner(method) {
339
+ if (isAndroidWebview()) return typeof (hostGlobal()?.["AndroidBridge"])?.[method] === "function";
340
+ if (isIosWebview()) return typeof ((hostGlobal()?.["webkit"])?.messageHandlers)?.[method]?.postMessage === "function";
341
+ if (isWeb()) return DESKTOP_METHODS.has(method);
342
+ return false;
343
+ }
344
+ const bridge = {
345
+ send,
346
+ sendPromise: send,
347
+ subscribe,
348
+ unsubscribe,
349
+ supports: (method) => {
350
+ console.warn("bridge.supports method is deprecated. Use bridge.supportsAsync instead.");
351
+ return supportsInner(method);
352
+ },
353
+ supportsAsync: async (method) => {
354
+ if (isAndroidWebview() || isIosWebview()) return supportsInner(method);
355
+ if (supportedHandlers) return supportedHandlers.has(method);
356
+ try {
357
+ const response = await send("SetSupportedHandlers");
358
+ supportedHandlers = new Set(response.supportedHandlers ?? ["VKWebAppInit"]);
359
+ } catch {
360
+ supportedHandlers = /* @__PURE__ */ new Set(["VKWebAppInit"]);
361
+ }
362
+ return supportedHandlers.has(method);
363
+ },
364
+ isWebView: () => isIosWebview() || isAndroidWebview(),
365
+ isIframe: () => {
366
+ const win = clientWindow();
367
+ return Boolean(win && win.parent !== win);
368
+ },
369
+ isEmbedded: () => bridge.isWebView() || bridge.isIframe(),
370
+ isStandalone: () => !bridge.isEmbedded(),
371
+ tap,
372
+ onSend: (listener) => tap.subscribe((event) => {
373
+ if (event.direction === "out") listener(event);
374
+ }),
375
+ onEvent: (listener) => tap.subscribe((event) => {
376
+ if (event.direction === "in") listener(event);
377
+ })
378
+ };
379
+ return bridge;
380
+ }
381
+ //#endregion
382
+ export { createVKBridge, isAndroidWebview, isClientSide, isDesktopVk, isIosWebview, isMvk, isReactNativeWebview, isWeb };
383
+
384
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","names":[],"sources":["../src/bridge.ts"],"sourcesContent":["/**\n * The bridge core: transport dispatch, subscription fan-out, promise pairing\n * and the always-on tap.\n *\n * Transports (checked lazily on every call, unlike the load-time caches of\n * the reference implementation — same in practice, testable in unit tests):\n *\n * - Android WebView: `window.AndroidBridge[method](JSON.stringify(props))`\n * - iOS WebView: `window.webkit.messageHandlers[method].postMessage(props)`\n * - React Native WebView: `window.ReactNativeWebView.postMessage(JSON)`\n * - Web (iframe on vk.ru / m.vk.ru): `parent.postMessage` with the\n * `type: \"vk-connect\"` envelope; receive side listens to `message`\n * - WebViews receive via `CustomEvent(\"VKWebAppEvent\")`\n *\n * The wire format is the platform interface — the host clients on the other\n * side of these bridges define it, which is why the envelopes above are\n * reproduced exactly.\n */\n\nimport { createTap, type BridgeTap, type BridgeTapEvent } from \"./tap.ts\";\nimport type { SendOptions, VKBridgeSend, VKBridgeSubscribeHandler, VkmaBridge } from \"./types.ts\";\n\n/** Methods supported on the desktop web hosts. */\nconst DESKTOP_METHODS = new Set([\n\t\"VKWebAppInit\",\n\t\"VKWebAppGetCommunityAuthToken\",\n\t\"VKWebAppAddToCommunity\",\n\t\"VKWebAppAddToHomeScreenInfo\",\n\t\"VKWebAppClose\",\n\t\"VKWebAppCopyText\",\n\t\"VKWebAppCreateHash\",\n\t\"VKWebAppGetUserInfo\",\n\t\"VKWebAppSetLocation\",\n\t\"VKWebAppSendToClient\",\n\t\"VKWebAppGetClientVersion\",\n\t\"VKWebAppGetPhoneNumber\",\n\t\"VKWebAppGetEmail\",\n\t\"VKWebAppGetGroupInfo\",\n\t\"VKWebAppGetGeodata\",\n\t\"VKWebAppGetCommunityToken\",\n\t\"VKWebAppGetConfig\",\n\t\"VKWebAppGetLaunchParams\",\n\t\"VKWebAppSetTitle\",\n\t\"VKWebAppGetAuthToken\",\n\t\"VKWebAppCallAPIMethod\",\n\t\"VKWebAppJoinGroup\",\n\t\"VKWebAppLeaveGroup\",\n\t\"VKWebAppAllowMessagesFromGroup\",\n\t\"VKWebAppDenyNotifications\",\n\t\"VKWebAppAllowNotifications\",\n\t\"VKWebAppOpenPayForm\",\n\t\"VKWebAppOpenApp\",\n\t\"VKWebAppShare\",\n\t\"VKWebAppShowWallPostBox\",\n\t\"VKWebAppScroll\",\n\t\"VKWebAppShowOrderBox\",\n\t\"VKWebAppShowLeaderBoardBox\",\n\t\"VKWebAppShowInviteBox\",\n\t\"VKWebAppShowRequestBox\",\n\t\"VKWebAppAddToFavorites\",\n\t\"VKWebAppShowStoryBox\",\n\t\"VKWebAppStorageGet\",\n\t\"VKWebAppStorageGetKeys\",\n\t\"VKWebAppStorageSet\",\n\t\"VKWebAppFlashGetInfo\",\n\t\"VKWebAppSubscribeStoryApp\",\n\t\"VKWebAppOpenWallPost\",\n\t\"VKWebAppCheckAllowedScopes\",\n\t\"VKWebAppCheckBannerAd\",\n\t\"VKWebAppHideBannerAd\",\n\t\"VKWebAppShowBannerAd\",\n\t\"VKWebAppCheckNativeAds\",\n\t\"VKWebAppShowNativeAds\",\n\t\"VKWebAppRetargetingPixel\",\n\t\"VKWebAppConversionHit\",\n\t\"VKWebAppShowSubscriptionBox\",\n\t\"VKWebAppCheckSurvey\",\n\t\"VKWebAppShowSurvey\",\n\t\"VKWebAppScrollTop\",\n\t\"VKWebAppScrollTopStart\",\n\t\"VKWebAppScrollTopStop\",\n\t\"VKWebAppShowSlidesSheet\",\n\t\"VKWebAppTranslate\",\n\t\"VKWebAppRecommend\",\n\t\"VKWebAppAddToProfile\",\n\t\"VKWebAppGetFriends\",\n\t// Desktop web specific events\n\t...(isDesktopVk()\n\t\t? [\n\t\t\t\t\"VKWebAppResizeWindow\",\n\t\t\t\t\"VKWebAppAddToMenu\",\n\t\t\t\t\"VKWebAppShowInstallPushBox\",\n\t\t\t\t\"VKWebAppShowCommunityWidgetPreviewBox\",\n\t\t\t\t\"VKWebAppCallStart\",\n\t\t\t\t\"VKWebAppCallJoin\",\n\t\t\t\t\"VKWebAppCallGetStatus\",\n\t\t\t]\n\t\t: [\"VKWebAppShowImages\"]),\n]);\n\n// ------------------------------------------------------------- environment\n\nfunction clientWindow(): (Window & typeof globalThis) | undefined {\n\treturn typeof window === \"undefined\" ? undefined : window;\n}\n\n/**\n * The native side of the host (AndroidBridge / webkit / ReactNativeWebView)\n * lives on the window but is not part of any DOM typing — tests install\n * fakes under the same keys.\n */\nfunction hostGlobal(): Record<string, unknown> | undefined {\n\tconst win = clientWindow();\n\treturn win === undefined ? undefined : (win as unknown as Record<string, unknown>);\n}\n\nexport function isClientSide(): boolean {\n\treturn typeof window !== \"undefined\";\n}\n\nexport function isAndroidWebview(): boolean {\n\treturn Boolean(hostGlobal()?.[\"AndroidBridge\"]);\n}\n\nexport function isIosWebview(): boolean {\n\tconst webkit = hostGlobal()?.[\"webkit\"] as\n\t\t| { messageHandlers?: Record<string, { postMessage?: unknown }> }\n\t\t| undefined;\n\treturn Boolean(webkit?.messageHandlers?.[\"VKWebAppClose\"]);\n}\n\nexport function isReactNativeWebview(): boolean {\n\tconst reactNative = hostGlobal()?.[\"ReactNativeWebView\"] as\n\t\t| { postMessage?: unknown }\n\t\t| undefined;\n\treturn Boolean(reactNative && typeof reactNative.postMessage === \"function\");\n}\n\nexport function isWeb(): boolean {\n\treturn isClientSide() && !isAndroidWebview() && !isIosWebview();\n}\n\nexport function isMvk(): boolean {\n\treturn (\n\t\tisWeb() && /(^\\?|&)vk_platform=mobile_web(&|$)/.test(clientWindow()?.location.search ?? \"\")\n\t);\n}\n\nexport function isDesktopVk(): boolean {\n\treturn isWeb() && !isMvk();\n}\n\n// -------------------------------------------------------------- the bridge\n\n/**\n * request_id policy of a bridge instance.\n */\nexport interface RequestIdPolicy {\n\t/**\n\t * Own generator of outgoing request ids; receives an incrementing\n\t * sequence. The default mints `__vkma_<n>` — the ids the devtools tap\n\t * pairs responses by.\n\t */\n\tgenerator?: (sequence: number) => string | number;\n\t/**\n\t * Turn the auto-injection off: sends pass caller props untouched, so\n\t * promise pairing only works for calls that provide their own\n\t * `request_id`. Default: on.\n\t */\n\tinject?: boolean;\n}\n\nexport interface VkmaBridgeOptions {\n\t/** Outgoing request_id policy. */\n\trequestId?: RequestIdPolicy;\n\t/** Inject a shared tap (the default instance passes the page-wide one). */\n\ttap?: BridgeTap;\n\t/** Version string reported to the web host in the vk-connect envelope. */\n\tversion?: string;\n}\n\ninterface PendingRequest {\n\tresolve: (value: unknown) => void;\n\treject: (reason: unknown) => void;\n\ttapId: number;\n\toptions: SendOptions;\n\tcleanup: () => void;\n}\n\n/**\n * Creates a VK Bridge instance: transport dispatch, subscription fan-out,\n * promise pairing and the always-on tap.\n *\n * @param version Version of the package (reported to the web host).\n * @param [options] Tap sharing and the request_id policy.\n */\nexport function createVKBridge(version: string, options: VkmaBridgeOptions = {}): VkmaBridge {\n\tconst tap = options.tap ?? createTap();\n\tconst requestIdPolicy = options.requestId ?? {};\n\n\t/** Current frame id, assigned by the host via VKWebAppSettings. */\n\tlet webFrameId: string | undefined;\n\n\t/** Functions subscribed to bridge events. */\n\tconst subscribers: VKBridgeSubscribeHandler[] = [];\n\n\t/** Pending promise controllers by outgoing request_id. */\n\tconst pendingRequests = new Map<string, PendingRequest>();\n\n\tlet requestCounter = 0;\n\tlet supportedHandlers: Set<string> | undefined;\n\n\tconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n\t\ttypeof value === \"object\" && value !== null;\n\n\t// ------------------------------------------------------------- transports\n\n\tfunction sendThroughTransport(method: string, props: Record<string, unknown>): void {\n\t\tconst host = hostGlobal();\n\t\tconst android = host?.[\"AndroidBridge\"] as\n\t\t\t| Record<string, ((serializedData: string) => void) | undefined>\n\t\t\t| undefined;\n\t\t// Sending data through the Android bridge\n\t\tif (typeof android?.[method] === \"function\") {\n\t\t\tandroid[method](JSON.stringify(props));\n\t\t\treturn;\n\t\t}\n\n\t\tconst iosHandlers = (\n\t\t\thost?.[\"webkit\"] as\n\t\t\t\t| { messageHandlers?: Record<string, { postMessage?: unknown }> }\n\t\t\t\t| undefined\n\t\t)?.messageHandlers;\n\t\t// Sending data through the iOS bridge\n\t\tif (typeof iosHandlers?.[method]?.postMessage === \"function\") {\n\t\t\t// Not a window.postMessage — the native handler takes no targetOrigin.\n\t\t\t// oxlint-disable-next-line unicorn/require-post-message-target-origin\n\t\t\t(iosHandlers[method].postMessage as (data: unknown) => void)(props);\n\t\t\treturn;\n\t\t}\n\n\t\t// Sending data through the React Native bridge\n\t\tif (isReactNativeWebview()) {\n\t\t\tconst reactNative = host?.[\"ReactNativeWebView\"] as {\n\t\t\t\tpostMessage: (data: string) => void;\n\t\t\t};\n\t\t\t// Not a window.postMessage — the RN handler takes no targetOrigin.\n\t\t\t// oxlint-disable-next-line unicorn/require-post-message-target-origin\n\t\t\treactNative.postMessage(JSON.stringify({ handler: method, params: props }));\n\t\t\treturn;\n\t\t}\n\n\t\t// Sending data through the web bridge (the parent frame)\n\t\tconst win = clientWindow();\n\t\tconst parent = win?.parent;\n\t\tif (win && parent && typeof parent.postMessage === \"function\") {\n\t\t\t// A hoisted handle, not a literal `parent.postMessage(...)` call:\n\t\t\t// Facebook's Instant Games static analysis rejects builds that\n\t\t\t// contain the literal substring (VKCOM/vk-bridge#290) — the same\n\t\t\t// pattern as the `iosBridge`/`reactNative` handles above.\n\t\t\tconst webBridge = parent;\n\t\t\t// The mini app does not know the host origin ahead of time (vk.ru,\n\t\t\t// m.vk.ru, mobile clients) — \"*\" is the wire contract here.\n\t\t\t// oxlint-disable-next-line unicorn/require-post-message-target-origin\n\t\t\twebBridge.postMessage(\n\t\t\t\t{\n\t\t\t\t\thandler: method,\n\t\t\t\t\tparams: props,\n\t\t\t\t\ttype: \"vk-connect\",\n\t\t\t\t\twebFrameId,\n\t\t\t\t\tconnectVersion: version,\n\t\t\t\t},\n\t\t\t\t\"*\",\n\t\t\t);\n\t\t}\n\t}\n\n\t// ------------------------------------------------------------- event intake\n\n\tfunction handleEvent(event: unknown): void {\n\t\tif (isIosWebview() || isAndroidWebview()) {\n\t\t\t// WebViews: the native layer dispatches ready-made events\n\t\t\tdeliver(event as { detail?: { type?: unknown; data?: unknown } });\n\t\t\treturn;\n\t\t}\n\n\t\tlet bridgeEventData = (event as { data?: unknown } | undefined)?.data;\n\t\tif (!isWeb() || !bridgeEventData) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (isReactNativeWebview() && typeof bridgeEventData === \"string\") {\n\t\t\ttry {\n\t\t\t\tbridgeEventData = JSON.parse(bridgeEventData) as unknown;\n\t\t\t} catch {}\n\t\t}\n\n\t\tif (!isPlainObject(bridgeEventData)) {\n\t\t\treturn;\n\t\t}\n\t\tconst { type, data, frameId } = bridgeEventData as {\n\t\t\ttype?: unknown;\n\t\t\tdata?: unknown;\n\t\t\tframeId?: unknown;\n\t\t};\n\t\tif (!type) {\n\t\t\treturn;\n\t\t}\n\n\t\t// The host assigns this iframe its frame id — internal protocol,\n\t\t// not delivered to subscribers.\n\t\tif (type === \"VKWebAppSettings\") {\n\t\t\twebFrameId = typeof frameId === \"string\" ? frameId : webFrameId;\n\t\t\treturn;\n\t\t}\n\n\t\tdeliver({ detail: { type, data } });\n\t}\n\n\t/**\n\t * Upstream parity: every incoming event reaches `[...subscribers]` —\n\t * paired responses included, with the raw wire data (upstream's\n\t * promisifySend is itself a subscriber and strips request_id only for\n\t * the promise). Snapshot iteration: a handler may unsubscribe mid-fan.\n\t */\n\tfunction fanOutToSubscribers(type: string, data: unknown): void {\n\t\t// The wire type is stringly here by design — handleEvent dispatches\n\t\t// arbitrary host envelopes; the public subscribe surface narrows it.\n\t\t// The slice is a snapshot: a handler may unsubscribe mid-fan-out,\n\t\t// and shifting live iteration would skip its neighbours (upstream\n\t\t// copies the array for the same reason).\n\t\tconst out = { detail: { type, data } } as Parameters<VKBridgeSubscribeHandler>[0];\n\t\tfor (const listener of subscribers.slice()) {\n\t\t\tlistener(out);\n\t\t}\n\t}\n\n\t/** Fan an incoming event out to the tap and to the subscribers. */\n\tfunction deliver(event: { detail?: { type?: unknown; data?: unknown } }): void {\n\t\tconst { type, data } = event.detail ?? {};\n\t\tif (typeof type !== \"string\") {\n\t\t\treturn;\n\t\t}\n\n\t\t// A payload carrying one of our pending request ids is the paired\n\t\t// response to a send; everything else is an unsolicited event.\n\t\t// The wire contract types request_id as a string or a number.\n\t\tif (isPlainObject(data)) {\n\t\t\tconst wireRequestId = data[\"request_id\"];\n\t\t\tif (typeof wireRequestId !== \"string\" && typeof wireRequestId !== \"number\") {\n\t\t\t\ttap.emit({ direction: \"in\", kind: \"event\", name: type, payload: data });\n\t\t\t\tfanOutToSubscribers(type, data);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst requestId = String(wireRequestId);\n\t\t\tconst pending = pendingRequests.get(requestId);\n\t\t\tif (pending) {\n\t\t\t\tpendingRequests.delete(requestId);\n\t\t\t\tconst { request_id: _stripped, ...payload } = data;\n\t\t\t\tconst ok = !(\"error_type\" in data);\n\t\t\t\tpending.cleanup();\n\t\t\t\ttap.emit({\n\t\t\t\t\tdirection: \"in\",\n\t\t\t\t\tkind: \"response\",\n\t\t\t\t\tname: type,\n\t\t\t\t\tpayload,\n\t\t\t\t\treqId: pending.tapId,\n\t\t\t\t\tok,\n\t\t\t\t});\n\t\t\t\tif (ok) {\n\t\t\t\t\tpending.resolve(payload);\n\t\t\t\t} else {\n\t\t\t\t\tpending.reject(payload);\n\t\t\t\t}\n\t\t\t\t// After the pairing listener's job (the promise is settled\n\t\t\t\t// already, like upstream's first-registered pairing handler).\n\t\t\t\tfanOutToSubscribers(type, data);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\ttap.emit({ direction: \"in\", kind: \"event\", name: type, payload: data });\n\t\tfanOutToSubscribers(type, data);\n\t}\n\n\tfunction subscribe(listener: VKBridgeSubscribeHandler): void {\n\t\tsubscribers.push(listener);\n\t}\n\n\tfunction unsubscribe(listener: VKBridgeSubscribeHandler): void {\n\t\tconst index = subscribers.indexOf(listener);\n\t\tif (index > -1) {\n\t\t\tsubscribers.splice(index, 1);\n\t\t}\n\t}\n\n\t// Listening for messages from the runtime env. React Native on Android\n\t// dispatches on the document, everything else on the window.\n\tif (isClientSide()) {\n\t\tconst win = clientWindow();\n\t\tif (isReactNativeWebview() && /(android)/i.test(win?.navigator.userAgent ?? \"\")) {\n\t\t\tdocument.addEventListener(\"message\", handleEvent);\n\t\t} else if (win && \"addEventListener\" in win) {\n\t\t\twin.addEventListener(isWeb() ? \"message\" : \"VKWebAppEvent\", handleEvent);\n\t\t}\n\t}\n\n\t// ------------------------------------------------------------ send + tap\n\n\tconst send: VKBridgeSend = ((\n\t\tmethod: string,\n\t\tprops?: Record<string, unknown>,\n\t\tsendOptions?: SendOptions,\n\t) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst signal = sendOptions?.signal;\n\t\t\t// A pre-aborted signal rejects before anything happens: no wire\n\t\t\t// send, no tap entry, no pending slot (the tap reflects reality).\n\t\t\tif (signal?.aborted) {\n\t\t\t\tconst error = new Error(`VK Bridge send \"${method}\" aborted`);\n\t\t\t\terror.name = \"AbortError\";\n\t\t\t\treject(error);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst sourceProps: Record<string, unknown> = { ...props };\n\t\t\tconst explicitId =\n\t\t\t\ttypeof sourceProps[\"request_id\"] === \"string\" ||\n\t\t\t\ttypeof sourceProps[\"request_id\"] === \"number\"\n\t\t\t\t\t? sourceProps[\"request_id\"]\n\t\t\t\t\t: undefined;\n\t\t\tlet requestId: string | number | undefined;\n\t\t\tif (explicitId !== undefined) {\n\t\t\t\trequestId = explicitId;\n\t\t\t} else if (requestIdPolicy.inject !== false) {\n\t\t\t\tconst sequence = ++requestCounter;\n\t\t\t\trequestId = requestIdPolicy.generator\n\t\t\t\t\t? requestIdPolicy.generator(sequence)\n\t\t\t\t\t: `__vkma_${sequence}`;\n\t\t\t}\n\n\t\t\t// The wire props are a separate object: the tap payload must not\n\t\t\t// mutate under a listener that observed the emit.\n\t\t\tconst wireProps =\n\t\t\t\trequestId === undefined ? sourceProps : { ...sourceProps, request_id: requestId };\n\n\t\t\tconst tapId = tap.emit({\n\t\t\t\tdirection: \"out\",\n\t\t\t\tkind: \"send\",\n\t\t\t\tname: method,\n\t\t\t\tpayload: wireProps,\n\t\t\t});\n\n\t\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\t\tlet signalHandler: (() => void) | undefined;\n\n\t\t\tconst cleanup = (): void => {\n\t\t\t\tif (timer !== undefined) {\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t}\n\t\t\t\tif (signal && signalHandler) {\n\t\t\t\t\tsignal.removeEventListener(\"abort\", signalHandler);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Timeout and abort must fire even for unpaired sends (no\n\t\t\t// pending slot to consult — e.g. inject: false without a caller\n\t\t\t// request_id): the caller asked for a bounded call. For paired\n\t\t\t// sends the pending entry doubles as the \"already answered\"\n\t\t\t// guard against a double rejection.\n\t\t\tconst timeoutReject = (): void => {\n\t\t\t\tif (requestId !== undefined && !pendingRequests.delete(String(requestId))) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcleanup();\n\t\t\t\tconst error = new Error(\n\t\t\t\t\t`VK Bridge send \"${method}\" timed out after ${String(sendOptions?.timeout)}ms`,\n\t\t\t\t);\n\t\t\t\terror.name = \"TimeoutError\";\n\t\t\t\treject(error);\n\t\t\t};\n\n\t\t\tconst abortReject = (): void => {\n\t\t\t\tif (requestId !== undefined && !pendingRequests.delete(String(requestId))) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcleanup();\n\t\t\t\tconst error = new Error(`VK Bridge send \"${method}\" aborted`);\n\t\t\t\terror.name = \"AbortError\";\n\t\t\t\treject(error);\n\t\t\t};\n\n\t\t\tif (requestId !== undefined) {\n\t\t\t\tpendingRequests.set(String(requestId), {\n\t\t\t\t\tresolve: resolve as (value: unknown) => void,\n\t\t\t\t\treject,\n\t\t\t\t\ttapId,\n\t\t\t\t\toptions: sendOptions ?? {},\n\t\t\t\t\tcleanup,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (sendOptions?.timeout !== undefined) {\n\t\t\t\ttimer = setTimeout(timeoutReject, sendOptions.timeout);\n\t\t\t}\n\t\t\tif (signal) {\n\t\t\t\tsignalHandler = abortReject;\n\t\t\t\tsignal.addEventListener(\"abort\", signalHandler);\n\t\t\t}\n\n\t\t\tsendThroughTransport(method, wireProps);\n\t\t});\n\t}) as VKBridgeSend;\n\n\t// The cache behind supportsAsync, fed by both the SetSupportedHandlers\n\t// response and the unsolicited pushes of the same name.\n\tsubscribe((event: { detail?: { type?: unknown; data?: unknown } }) => {\n\t\tif (event.detail?.type === \"SetSupportedHandlers\" && isPlainObject(event.detail.data)) {\n\t\t\tconst handlers = (event.detail.data as { supportedHandlers?: unknown })\n\t\t\t\t.supportedHandlers;\n\t\t\tif (Array.isArray(handlers)) {\n\t\t\t\tsupportedHandlers = new Set(\n\t\t\t\t\thandlers.filter((name): name is string => typeof name === \"string\"),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t});\n\n\tfunction supportsInner(method: string): boolean {\n\t\tif (isAndroidWebview()) {\n\t\t\tconst android = hostGlobal()?.[\"AndroidBridge\"] as\n\t\t\t\t| Record<string, ((serializedData: string) => void) | undefined>\n\t\t\t\t| undefined;\n\t\t\treturn typeof android?.[method] === \"function\";\n\t\t}\n\t\tif (isIosWebview()) {\n\t\t\tconst handlers = (\n\t\t\t\thostGlobal()?.[\"webkit\"] as\n\t\t\t\t\t| { messageHandlers?: Record<string, { postMessage?: unknown }> }\n\t\t\t\t\t| undefined\n\t\t\t)?.messageHandlers;\n\t\t\treturn typeof handlers?.[method]?.postMessage === \"function\";\n\t\t}\n\t\tif (isWeb()) {\n\t\t\treturn DESKTOP_METHODS.has(method);\n\t\t}\n\t\treturn false;\n\t}\n\n\tconst bridge: VkmaBridge = {\n\t\tsend,\n\t\tsendPromise: send,\n\t\tsubscribe,\n\t\tunsubscribe,\n\t\tsupports: (method: string) => {\n\t\t\tconsole.warn(\"bridge.supports method is deprecated. Use bridge.supportsAsync instead.\");\n\t\t\treturn supportsInner(method);\n\t\t},\n\t\tsupportsAsync: async (method: string) => {\n\t\t\tif (isAndroidWebview() || isIosWebview()) {\n\t\t\t\treturn supportsInner(method);\n\t\t\t}\n\t\t\tif (supportedHandlers) {\n\t\t\t\treturn supportedHandlers.has(method);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = (await send(\"SetSupportedHandlers\")) as {\n\t\t\t\t\tsupportedHandlers?: string[];\n\t\t\t\t};\n\t\t\t\tsupportedHandlers = new Set(response.supportedHandlers ?? [\"VKWebAppInit\"]);\n\t\t\t} catch {\n\t\t\t\tsupportedHandlers = new Set([\"VKWebAppInit\"]);\n\t\t\t}\n\t\t\treturn supportedHandlers.has(method);\n\t\t},\n\t\tisWebView: () => isIosWebview() || isAndroidWebview(),\n\t\tisIframe: () => {\n\t\t\tconst win = clientWindow();\n\t\t\treturn Boolean(win && win.parent !== win);\n\t\t},\n\t\tisEmbedded: () => bridge.isWebView() || bridge.isIframe(),\n\t\tisStandalone: () => !bridge.isEmbedded(),\n\t\ttap,\n\t\tonSend: listener =>\n\t\t\ttap.subscribe(event => {\n\t\t\t\tif (event.direction === \"out\") {\n\t\t\t\t\tlistener(event as BridgeTapEvent & { direction: \"out\" });\n\t\t\t\t}\n\t\t\t}),\n\t\tonEvent: listener =>\n\t\t\ttap.subscribe(event => {\n\t\t\t\tif (event.direction === \"in\") {\n\t\t\t\t\tlistener(event as BridgeTapEvent & { direction: \"in\" });\n\t\t\t\t}\n\t\t\t}),\n\t};\n\n\treturn bridge;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAuBA,MAAM,kCAAkB,IAAI,IAAI;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,GAAI,YAAY,IACb;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACD,IACC,CAAC,oBAAoB;AACzB,CAAC;AAID,SAAS,eAAyD;CACjE,OAAO,OAAO,WAAW,cAAc,KAAA,IAAY;AACpD;;;;;;AAOA,SAAS,aAAkD;CAC1D,MAAM,MAAM,aAAa;CACzB,OAAO,QAAQ,KAAA,IAAY,KAAA,IAAa;AACzC;AAEA,SAAgB,eAAwB;CACvC,OAAO,OAAO,WAAW;AAC1B;AAEA,SAAgB,mBAA4B;CAC3C,OAAO,QAAQ,WAAW,CAAC,GAAG,gBAAgB;AAC/C;AAEA,SAAgB,eAAwB;CACvC,MAAM,SAAS,WAAW,CAAC,GAAG;CAG9B,OAAO,QAAQ,QAAQ,kBAAkB,gBAAgB;AAC1D;AAEA,SAAgB,uBAAgC;CAC/C,MAAM,cAAc,WAAW,CAAC,GAAG;CAGnC,OAAO,QAAQ,eAAe,OAAO,YAAY,gBAAgB,UAAU;AAC5E;AAEA,SAAgB,QAAiB;CAChC,OAAO,aAAa,KAAK,CAAC,iBAAiB,KAAK,CAAC,aAAa;AAC/D;AAEA,SAAgB,QAAiB;CAChC,OACC,MAAM,KAAK,qCAAqC,KAAK,aAAa,CAAC,EAAE,SAAS,UAAU,EAAE;AAE5F;AAEA,SAAgB,cAAuB;CACtC,OAAO,MAAM,KAAK,CAAC,MAAM;AAC1B;;;;;;;;AA8CA,SAAgB,eAAe,SAAiB,UAA6B,CAAC,GAAe;CAC5F,MAAM,MAAM,QAAQ,OAAO,UAAU;CACrC,MAAM,kBAAkB,QAAQ,aAAa,CAAC;;CAG9C,IAAI;;CAGJ,MAAM,cAA0C,CAAC;;CAGjD,MAAM,kCAAkB,IAAI,IAA4B;CAExD,IAAI,iBAAiB;CACrB,IAAI;CAEJ,MAAM,iBAAiB,UACtB,OAAO,UAAU,YAAY,UAAU;CAIxC,SAAS,qBAAqB,QAAgB,OAAsC;EACnF,MAAM,OAAO,WAAW;EACxB,MAAM,UAAU,OAAO;EAIvB,IAAI,OAAO,UAAU,YAAY,YAAY;GAC5C,QAAQ,OAAO,CAAC,KAAK,UAAU,KAAK,CAAC;GACrC;EACD;EAEA,MAAM,eACL,OAAO,UAAA,EAGL;EAEH,IAAI,OAAO,cAAc,OAAO,EAAE,gBAAgB,YAAY;GAG7D,YAAa,OAAO,CAAC,YAAwC,KAAK;GAClE;EACD;EAGA,IAAI,qBAAqB,GAAG;GAM3B,CALoB,OAAO,sBAAA,CAKf,YAAY,KAAK,UAAU;IAAE,SAAS;IAAQ,QAAQ;GAAM,CAAC,CAAC;GAC1E;EACD;EAGA,MAAM,MAAM,aAAa;EACzB,MAAM,SAAS,KAAK;EACpB,IAAI,OAAO,UAAU,OAAO,OAAO,gBAAgB,YASlD,OAAU,YACT;GACC,SAAS;GACT,QAAQ;GACR,MAAM;GACN;GACA,gBAAgB;EACjB,GACA,GACD;CAEF;CAIA,SAAS,YAAY,OAAsB;EAC1C,IAAI,aAAa,KAAK,iBAAiB,GAAG;GAEzC,QAAQ,KAAwD;GAChE;EACD;EAEA,IAAI,kBAAmB,OAA0C;EACjE,IAAI,CAAC,MAAM,KAAK,CAAC,iBAChB;EAGD,IAAI,qBAAqB,KAAK,OAAO,oBAAoB,UACxD,IAAI;GACH,kBAAkB,KAAK,MAAM,eAAe;EAC7C,QAAQ,CAAC;EAGV,IAAI,CAAC,cAAc,eAAe,GACjC;EAED,MAAM,EAAE,MAAM,MAAM,YAAY;EAKhC,IAAI,CAAC,MACJ;EAKD,IAAI,SAAS,oBAAoB;GAChC,aAAa,OAAO,YAAY,WAAW,UAAU;GACrD;EACD;EAEA,QAAQ,EAAE,QAAQ;GAAE;GAAM;EAAK,EAAE,CAAC;CACnC;;;;;;;CAQA,SAAS,oBAAoB,MAAc,MAAqB;EAM/D,MAAM,MAAM,EAAE,QAAQ;GAAE;GAAM;EAAK,EAAE;EACrC,KAAK,MAAM,YAAY,YAAY,MAAM,GACxC,SAAS,GAAG;CAEd;;CAGA,SAAS,QAAQ,OAA8D;EAC9E,MAAM,EAAE,MAAM,SAAS,MAAM,UAAU,CAAC;EACxC,IAAI,OAAO,SAAS,UACnB;EAMD,IAAI,cAAc,IAAI,GAAG;GACxB,MAAM,gBAAgB,KAAK;GAC3B,IAAI,OAAO,kBAAkB,YAAY,OAAO,kBAAkB,UAAU;IAC3E,IAAI,KAAK;KAAE,WAAW;KAAM,MAAM;KAAS,MAAM;KAAM,SAAS;IAAK,CAAC;IACtE,oBAAoB,MAAM,IAAI;IAC9B;GACD;GACA,MAAM,YAAY,OAAO,aAAa;GACtC,MAAM,UAAU,gBAAgB,IAAI,SAAS;GAC7C,IAAI,SAAS;IACZ,gBAAgB,OAAO,SAAS;IAChC,MAAM,EAAE,YAAY,WAAW,GAAG,YAAY;IAC9C,MAAM,KAAK,EAAE,gBAAgB;IAC7B,QAAQ,QAAQ;IAChB,IAAI,KAAK;KACR,WAAW;KACX,MAAM;KACN,MAAM;KACN;KACA,OAAO,QAAQ;KACf;IACD,CAAC;IACD,IAAI,IACH,QAAQ,QAAQ,OAAO;SAEvB,QAAQ,OAAO,OAAO;IAIvB,oBAAoB,MAAM,IAAI;IAC9B;GACD;EACD;EAEA,IAAI,KAAK;GAAE,WAAW;GAAM,MAAM;GAAS,MAAM;GAAM,SAAS;EAAK,CAAC;EACtE,oBAAoB,MAAM,IAAI;CAC/B;CAEA,SAAS,UAAU,UAA0C;EAC5D,YAAY,KAAK,QAAQ;CAC1B;CAEA,SAAS,YAAY,UAA0C;EAC9D,MAAM,QAAQ,YAAY,QAAQ,QAAQ;EAC1C,IAAI,QAAQ,IACX,YAAY,OAAO,OAAO,CAAC;CAE7B;CAIA,IAAI,aAAa,GAAG;EACnB,MAAM,MAAM,aAAa;EACzB,IAAI,qBAAqB,KAAK,aAAa,KAAK,KAAK,UAAU,aAAa,EAAE,GAC7E,SAAS,iBAAiB,WAAW,WAAW;OAC1C,IAAI,OAAO,sBAAsB,KACvC,IAAI,iBAAiB,MAAM,IAAI,YAAY,iBAAiB,WAAW;CAEzE;CAIA,MAAM,SACL,QACA,OACA,gBACI;EACJ,OAAO,IAAI,SAAS,SAAS,WAAW;GACvC,MAAM,SAAS,aAAa;GAG5B,IAAI,QAAQ,SAAS;IACpB,MAAM,wBAAQ,IAAI,MAAM,mBAAmB,OAAO,UAAU;IAC5D,MAAM,OAAO;IACb,OAAO,KAAK;IACZ;GACD;GAEA,MAAM,cAAuC,EAAE,GAAG,MAAM;GACxD,MAAM,aACL,OAAO,YAAY,kBAAkB,YACrC,OAAO,YAAY,kBAAkB,WAClC,YAAY,gBACZ,KAAA;GACJ,IAAI;GACJ,IAAI,eAAe,KAAA,GAClB,YAAY;QACN,IAAI,gBAAgB,WAAW,OAAO;IAC5C,MAAM,WAAW,EAAE;IACnB,YAAY,gBAAgB,YACzB,gBAAgB,UAAU,QAAQ,IAClC,UAAU;GACd;GAIA,MAAM,YACL,cAAc,KAAA,IAAY,cAAc;IAAE,GAAG;IAAa,YAAY;GAAU;GAEjF,MAAM,QAAQ,IAAI,KAAK;IACtB,WAAW;IACX,MAAM;IACN,MAAM;IACN,SAAS;GACV,CAAC;GAED,IAAI;GACJ,IAAI;GAEJ,MAAM,gBAAsB;IAC3B,IAAI,UAAU,KAAA,GACb,aAAa,KAAK;IAEnB,IAAI,UAAU,eACb,OAAO,oBAAoB,SAAS,aAAa;GAEnD;GAOA,MAAM,sBAA4B;IACjC,IAAI,cAAc,KAAA,KAAa,CAAC,gBAAgB,OAAO,OAAO,SAAS,CAAC,GACvE;IAED,QAAQ;IACR,MAAM,wBAAQ,IAAI,MACjB,mBAAmB,OAAO,oBAAoB,OAAO,aAAa,OAAO,EAAE,GAC5E;IACA,MAAM,OAAO;IACb,OAAO,KAAK;GACb;GAEA,MAAM,oBAA0B;IAC/B,IAAI,cAAc,KAAA,KAAa,CAAC,gBAAgB,OAAO,OAAO,SAAS,CAAC,GACvE;IAED,QAAQ;IACR,MAAM,wBAAQ,IAAI,MAAM,mBAAmB,OAAO,UAAU;IAC5D,MAAM,OAAO;IACb,OAAO,KAAK;GACb;GAEA,IAAI,cAAc,KAAA,GACjB,gBAAgB,IAAI,OAAO,SAAS,GAAG;IAC7B;IACT;IACA;IACA,SAAS,eAAe,CAAC;IACzB;GACD,CAAC;GAGF,IAAI,aAAa,YAAY,KAAA,GAC5B,QAAQ,WAAW,eAAe,YAAY,OAAO;GAEtD,IAAI,QAAQ;IACX,gBAAgB;IAChB,OAAO,iBAAiB,SAAS,aAAa;GAC/C;GAEA,qBAAqB,QAAQ,SAAS;EACvC,CAAC;CACF;CAIA,WAAW,UAA2D;EACrE,IAAI,MAAM,QAAQ,SAAS,0BAA0B,cAAc,MAAM,OAAO,IAAI,GAAG;GACtF,MAAM,WAAY,MAAM,OAAO,KAC7B;GACF,IAAI,MAAM,QAAQ,QAAQ,GACzB,oBAAoB,IAAI,IACvB,SAAS,QAAQ,SAAyB,OAAO,SAAS,QAAQ,CACnE;EAEF;CACD,CAAC;CAED,SAAS,cAAc,QAAyB;EAC/C,IAAI,iBAAiB,GAIpB,OAAO,QAHS,WAAW,CAAC,GAAG,iBAAA,GAGP,YAAY;EAErC,IAAI,aAAa,GAMhB,OAAO,SAJN,WAAW,CAAC,GAAG,UAAA,EAGb,gBAAA,GACsB,OAAO,EAAE,gBAAgB;EAEnD,IAAI,MAAM,GACT,OAAO,gBAAgB,IAAI,MAAM;EAElC,OAAO;CACR;CAEA,MAAM,SAAqB;EAC1B;EACA,aAAa;EACb;EACA;EACA,WAAW,WAAmB;GAC7B,QAAQ,KAAK,yEAAyE;GACtF,OAAO,cAAc,MAAM;EAC5B;EACA,eAAe,OAAO,WAAmB;GACxC,IAAI,iBAAiB,KAAK,aAAa,GACtC,OAAO,cAAc,MAAM;GAE5B,IAAI,mBACH,OAAO,kBAAkB,IAAI,MAAM;GAEpC,IAAI;IACH,MAAM,WAAY,MAAM,KAAK,sBAAsB;IAGnD,oBAAoB,IAAI,IAAI,SAAS,qBAAqB,CAAC,cAAc,CAAC;GAC3E,QAAQ;IACP,oCAAoB,IAAI,IAAI,CAAC,cAAc,CAAC;GAC7C;GACA,OAAO,kBAAkB,IAAI,MAAM;EACpC;EACA,iBAAiB,aAAa,KAAK,iBAAiB;EACpD,gBAAgB;GACf,MAAM,MAAM,aAAa;GACzB,OAAO,QAAQ,OAAO,IAAI,WAAW,GAAG;EACzC;EACA,kBAAkB,OAAO,UAAU,KAAK,OAAO,SAAS;EACxD,oBAAoB,CAAC,OAAO,WAAW;EACvC;EACA,SAAQ,aACP,IAAI,WAAU,UAAS;GACtB,IAAI,MAAM,cAAc,OACvB,SAAS,KAA8C;EAEzD,CAAC;EACF,UAAS,aACR,IAAI,WAAU,UAAS;GACtB,IAAI,MAAM,cAAc,MACvB,SAAS,KAA6C;EAExD,CAAC;CACH;CAEA,OAAO;AACR"}
@@ -0,0 +1,47 @@
1
+ import { AnyIOMethodName, AnyReceiveMethodName, AnyReceiveOnlyMethodName, AnyRequestMethodName, AnyRequestOnlyMethodName } from "./types.js";
2
+ import { CommunityWidgetType } from "@vkma.dev/bridge-schema";
3
+ //#region src/deprecated.d.ts
4
+ /**
5
+ * Name of a method that can be sent.
6
+ * @alias AnyRequestMethodName
7
+ * @deprecated
8
+ */
9
+ export type RequestMethodName = AnyRequestMethodName;
10
+ /**
11
+ * Name of a method that can be received.
12
+ * @alias AnyReceiveMethodName
13
+ * @deprecated
14
+ */
15
+ export type ReceiveMethodName = AnyReceiveMethodName;
16
+ /**
17
+ * Name of a method that can be only sent.
18
+ * @alias AnyRequestOnlyMethodName
19
+ * @deprecated
20
+ */
21
+ export type RequestOnlyMethodName = AnyRequestOnlyMethodName;
22
+ /**
23
+ * Name of a method that can be only received.
24
+ * @alias AnyReceiveOnlyMethodName
25
+ * @deprecated
26
+ */
27
+ export type ReceiveOnlyMethodName = AnyReceiveOnlyMethodName;
28
+ /**
29
+ * Type of any method name.
30
+ * @alias AnyMethodName
31
+ * @deprecated
32
+ */
33
+ export type MethodName = AnyRequestMethodName | AnyReceiveMethodName;
34
+ /**
35
+ * The name of the method that can be both sent and received.
36
+ * @alias AnyIOMethodName
37
+ * @deprecated
38
+ */
39
+ export type IOMethodName = AnyIOMethodName;
40
+ /**
41
+ * Community widget type
42
+ * @alias CommunityWidgetType
43
+ * @deprecated
44
+ */
45
+ export type WidgetType = CommunityWidgetType;
46
+ //#endregion
47
+ //# sourceMappingURL=deprecated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deprecated.d.ts","names":[],"sources":["../src/deprecated.ts"],"mappings":";;;;;;;;YAkBY,oBAAoB;;;;;;YAOpB,oBAAoB;;;;;;YAOpB,wBAAwB;;;;;;YAOxB,wBAAwB;;;;;;YAOxB,aAAa,uBAAuB;;;;;;YAOpC,eAAe;;;;;;YAOf,aAAa"}