vk-helpers
Version:
Helpers and utilities for creating VK mini apps
656 lines (637 loc) • 25.1 kB
JavaScript
import crypto from 'node:crypto';
const sortParams$1 = (params) => {
return Buffer.from(Object.entries(params)
.sort(([key1], [key2]) => key1.localeCompare(key2))
.map(([key, value]) => `${key}_${encodeURIComponent(String(value))}`)
.join(";")).toString('base64')
.replace(/=/g, "");
};
const generateSignature = ({ secretKey, app_id, params, user_id, ts, }) => {
const newParams = { ...params };
const request_id = sortParams$1(newParams);
const innerParams = `app_id=${app_id}&request_id=${request_id}&ts=${ts}&user_id=${user_id}`;
const hmac = crypto.createHmac('sha256', secretKey);
hmac.update(innerParams);
const signature = hmac.digest('base64');
return signature.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
};
const checkRequestSignature = ({ signature, secretKey, app_id, params, user_id, ts, }) => {
const generatedSignature = generateSignature({
secretKey,
app_id,
params,
user_id,
ts,
});
return signature === generatedSignature;
};
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
}
/**
* Creates counter interface.
*/
function createCounter() {
return {
current: 0,
next: function () {
return ++this.current;
},
};
}
/**
* Creates interface for resolving request promises by request id's (or not).
*
* @param instanceId Uniq bridge instance ID.
*/
function createRequestResolver(instanceId) {
var counter = createCounter();
var promiseControllers = {};
return {
/**
* Adds new controller with resolve/reject methods.
*
* @param controller Object with `resolve` and `reject` functions
* @param customId Custom `request_id`
* @returns New request id of the added controller.
*/
add: function (controller, customId) {
var id = customId != null ? customId : "".concat(counter.next(), "_").concat(instanceId);
promiseControllers[id] = controller;
return id;
},
/**
* Resolves/rejects an added promise by request id and the `isSuccess`
* predicate.
*
* @param requestId Request ID.
* @param data Data to pass to the resolve- or reject-function.
* @param isSuccess Predicate to select the desired function.
*/
resolve: function (requestId, data, isSuccess) {
var requestPromise = promiseControllers[requestId];
if (requestPromise) {
if (isSuccess(data)) {
requestPromise.resolve(data);
}
else {
requestPromise.reject(data);
}
promiseControllers[requestId] = null;
}
},
};
}
/**
* Returns send function that returns promises.
*
* @param sendEvent Send event function.
* @param subscribe Subscribe event function.
* @param instanceId Uniq bridge instance ID.
* @returns Send function which returns the Promise object.
*/
function promisifySend(sendEvent, subscribe, instanceId) {
var requestResolver = createRequestResolver(instanceId);
// Subscribe to receive a data
subscribe(function (event) {
if (!event.detail || !event.detail.data || typeof event.detail.data !== 'object') {
return;
}
// There is no request_id in receive-only events, so we check its existence.
if ('request_id' in event.detail.data) {
var _a = event.detail.data, requestId = _a.request_id, data = __rest(_a, ["request_id"]);
if (requestId) {
requestResolver.resolve(requestId, data, function (data) { return !('error_type' in data); });
}
}
});
return function promisifiedSend(method, props) {
if (props === void 0) { props = {}; }
return new Promise(function (resolve, reject) {
var requestId = requestResolver.add({ resolve: resolve, reject: reject }, props.request_id);
sendEvent(method, __assign(__assign({}, props), { request_id: requestId }));
});
};
}
function createInstanceId() {
var allNumbersAndLetters = 36;
var positionAfterZeroAnDot = 2;
var keyLength = 3;
return Math.random()
.toString(allNumbersAndLetters)
.substring(positionAfterZeroAnDot, positionAfterZeroAnDot + keyLength);
}
/** Is the client side runtime environment */
var IS_CLIENT_SIDE = typeof window !== 'undefined';
/** Is the runtime environment an Android app */
var IS_ANDROID_WEBVIEW = Boolean(IS_CLIENT_SIDE && window.AndroidBridge);
/** Is the runtime environment an iOS app */
var IS_IOS_WEBVIEW = Boolean(IS_CLIENT_SIDE &&
window.webkit &&
window.webkit.messageHandlers &&
window.webkit.messageHandlers.VKWebAppClose);
var IS_REACT_NATIVE_WEBVIEW = Boolean(IS_CLIENT_SIDE &&
window.ReactNativeWebView &&
typeof window.ReactNativeWebView.postMessage === 'function');
/** Is the runtime environment a browser */
var IS_WEB = IS_CLIENT_SIDE && !IS_ANDROID_WEBVIEW && !IS_IOS_WEBVIEW;
/** Is the runtime environment m.vk.com */
var IS_MVK = IS_WEB && /(^\?|&)vk_platform=mobile_web(&|$)/.test(location.search);
/** Is the runtime environment vk.com */
var IS_DESKTOP_VK = IS_WEB && !IS_MVK;
/** Type of subscribe event */
var EVENT_TYPE = IS_WEB ? 'message' : 'VKWebAppEvent';
/** Methods supported on the desktop */
var DESKTOP_METHODS = __spreadArray([
'VKWebAppInit',
'VKWebAppGetCommunityAuthToken',
'VKWebAppAddToCommunity',
'VKWebAppAddToHomeScreenInfo',
'VKWebAppClose',
'VKWebAppCopyText',
'VKWebAppCreateHash',
'VKWebAppGetUserInfo',
'VKWebAppSetLocation',
'VKWebAppSendToClient',
'VKWebAppGetClientVersion',
'VKWebAppGetPhoneNumber',
'VKWebAppGetEmail',
'VKWebAppGetGroupInfo',
'VKWebAppGetGeodata',
'VKWebAppGetCommunityToken',
'VKWebAppGetConfig',
'VKWebAppGetLaunchParams',
'VKWebAppSetTitle',
'VKWebAppGetAuthToken',
'VKWebAppCallAPIMethod',
'VKWebAppJoinGroup',
'VKWebAppLeaveGroup',
'VKWebAppAllowMessagesFromGroup',
'VKWebAppDenyNotifications',
'VKWebAppAllowNotifications',
'VKWebAppOpenPayForm',
'VKWebAppOpenApp',
'VKWebAppShare',
'VKWebAppShowWallPostBox',
'VKWebAppScroll',
'VKWebAppShowOrderBox',
'VKWebAppShowLeaderBoardBox',
'VKWebAppShowInviteBox',
'VKWebAppShowRequestBox',
'VKWebAppAddToFavorites',
'VKWebAppShowStoryBox',
'VKWebAppStorageGet',
'VKWebAppStorageGetKeys',
'VKWebAppStorageSet',
'VKWebAppFlashGetInfo',
'VKWebAppSubscribeStoryApp',
'VKWebAppOpenWallPost',
'VKWebAppCheckAllowedScopes',
'VKWebAppCheckBannerAd',
'VKWebAppHideBannerAd',
'VKWebAppShowBannerAd',
'VKWebAppCheckNativeAds',
'VKWebAppShowNativeAds',
'VKWebAppRetargetingPixel',
'VKWebAppConversionHit',
'VKWebAppShowSubscriptionBox',
'VKWebAppCheckSurvey',
'VKWebAppShowSurvey',
'VKWebAppScrollTop',
'VKWebAppScrollTopStart',
'VKWebAppScrollTopStop',
'VKWebAppShowSlidesSheet',
'VKWebAppTranslate',
'VKWebAppRecommend',
'VKWebAppAddToProfile',
'VKWebAppGetFriends'
], (IS_DESKTOP_VK
? [
'VKWebAppResizeWindow',
'VKWebAppAddToMenu',
'VKWebAppShowInstallPushBox',
'VKWebAppShowCommunityWidgetPreviewBox',
'VKWebAppCallStart',
'VKWebAppCallJoin',
'VKWebAppCallGetStatus',
]
: ['VKWebAppShowImages']), true);
/** Cache for supported methods */
var supportedHandlers;
/** Android VK Bridge interface. */
var androidBridge = IS_CLIENT_SIDE ? window.AndroidBridge : undefined;
/** iOS VK Bridge interface. */
var iosBridge = IS_IOS_WEBVIEW ? window.webkit.messageHandlers : undefined;
/** Web VK Bridge interface. */
var webBridge = IS_WEB
? parent
: undefined;
// [Примечание 1] Отключили использование в этом PR https://github.com/VKCOM/vk-bridge/pull/262
// let webSdkHandlers: string[] | undefined;
/**
* Creates a VK Bridge API that holds functions for interact with runtime
* environment.
*
* @param version Version of the package
*/
function createVKBridge(version) {
/** Current frame id. */
var webFrameId = undefined;
/** List of functions that subscribed on events. */
var subscribers = [];
/** Uniq instance ID */
var instanceId = createInstanceId();
/**
* Sends an event to the runtime env. In the case of Android/iOS application
* env is the application itself. In the case of the browser, the parent
* frame in which the event handlers is located.
*
* @param method The method (event) name to send
* @param [props] Method properties
*/
function send(method, props) {
// Sending data through Android bridge
if (androidBridge && androidBridge[method]) {
androidBridge[method](JSON.stringify(props));
}
// Sending data through iOS bridge
else if (iosBridge &&
iosBridge[method] &&
typeof iosBridge[method].postMessage === 'function') {
iosBridge[method].postMessage(props);
}
// Sending data through React Native bridge
else if (IS_REACT_NATIVE_WEBVIEW) {
window.ReactNativeWebView.postMessage(JSON.stringify({
handler: method,
params: props,
}));
}
// Sending data through web bridge
else if (webBridge && typeof webBridge.postMessage === 'function') {
webBridge.postMessage({
handler: method,
params: props,
type: 'vk-connect',
webFrameId: webFrameId,
connectVersion: version,
}, '*');
}
}
/**
* Adds an event listener. It will be called any time a data is received.
*
* @param listener A callback to be invoked on every event receive.
*/
function subscribe(listener) {
subscribers.push(listener);
}
/**
* Removes an event listener which has been subscribed for event listening.
*
* @param listener A callback to unsubscribe.
*/
function unsubscribe(listener) {
var index = subscribers.indexOf(listener);
if (index > -1) {
subscribers.splice(index, 1);
}
}
function supportsInner(method) {
if (IS_ANDROID_WEBVIEW) {
// Android support check
return !!(androidBridge && typeof androidBridge[method] === 'function');
}
else if (IS_IOS_WEBVIEW) {
// iOS support check
return !!(iosBridge &&
iosBridge[method] &&
typeof iosBridge[method].postMessage === 'function');
}
else if (IS_WEB) {
// Web support check
return DESKTOP_METHODS.includes(method);
// см. Примечание 1
// if (!webSdkHandlers) {
// console.error('You should call bridge.send("VKWebAppInit") first');
// return false;
// }
// return webSdkHandlers.includes(method);
}
return false;
}
/**
* Checks if a method is supported on runtime platform.
*
* @param method Method (event) name to check.
* @returns Result of checking.
* @deprecated This method is deprecated. Use supportsAsync instead.
*/
function supports(method) {
console.warn('bridge.supports method is deprecated. Use bridge.supportsAsync instead.');
return supportsInner(method);
}
/**
* Checks whether the runtime is a WebView.
*
* @returns Result of checking.
*/
function isWebView() {
return IS_IOS_WEBVIEW || IS_ANDROID_WEBVIEW;
}
/**
* Checks whether the runtime is an iframe.
*
* @returns Result of checking.
*/
function isIframe() {
return IS_WEB && window.parent !== window;
}
/**
* Checks whether the runtime is embedded.
*
* @returns Result of checking.
*/
function isEmbedded() {
return isWebView() || isIframe();
}
/**
* Checks whether the runtime is standalone.
*
* @returns Result of checking.
*/
function isStandalone() {
return !isEmbedded();
}
function handleEvent(event) {
if (IS_IOS_WEBVIEW || IS_ANDROID_WEBVIEW) {
// If it's webview
return __spreadArray([], subscribers, true).map(function (fn) { return fn.call(null, event); });
}
var bridgeEventData = event === null || event === void 0 ? void 0 : event.data;
if (!IS_WEB || !bridgeEventData) {
return;
}
if (IS_REACT_NATIVE_WEBVIEW && typeof bridgeEventData === 'string') {
try {
bridgeEventData = JSON.parse(bridgeEventData);
}
catch (_a) { }
}
var type = bridgeEventData.type, data = bridgeEventData.data, frameId = bridgeEventData.frameId;
if (!type) {
return;
}
// см. Примечание 1
// if (type === 'SetSupportedHandlers') {
// webSdkHandlers = data.supportedHandlers;
// return;
// }
if (type === 'VKWebAppSettings') {
webFrameId = frameId;
return;
}
__spreadArray([], subscribers, true).map(function (fn) { return fn({ detail: { type: type, data: data } }); });
}
// Subscribes to listening messages from a runtime for calling each
// subscribed event listener.
if (IS_REACT_NATIVE_WEBVIEW && /(android)/i.test(navigator.userAgent)) {
document.addEventListener(EVENT_TYPE, handleEvent);
}
else if (typeof window !== 'undefined' && 'addEventListener' in window) {
window.addEventListener(EVENT_TYPE, handleEvent);
}
/**
* Enhanced send functions for the ability to receive response data in
* the Promise object.
*/
var sendPromise = promisifySend(send, subscribe, instanceId);
function supportsAsync(method) {
return __awaiter(this, void 0, void 0, function () {
var response;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (IS_ANDROID_WEBVIEW || IS_IOS_WEBVIEW) {
return [2 /*return*/, supportsInner(method)];
}
if (supportedHandlers) {
return [2 /*return*/, supportedHandlers.has(method)];
}
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, sendPromise('SetSupportedHandlers')];
case 2:
response = _a.sent();
supportedHandlers = new Set(response.supportedHandlers);
return [3 /*break*/, 4];
case 3:
_a.sent();
supportedHandlers = new Set(['VKWebAppInit']);
return [3 /*break*/, 4];
case 4: return [2 /*return*/, supportedHandlers.has(method)];
}
});
});
}
subscribe(function (event) {
if (!event.detail) {
return;
}
switch (event.detail.type) {
case 'SetSupportedHandlers':
supportedHandlers = new Set(event.detail.data.supportedHandlers);
}
});
return {
send: sendPromise,
sendPromise: sendPromise,
subscribe: subscribe,
unsubscribe: unsubscribe,
supports: supports,
supportsAsync: supportsAsync,
isWebView: isWebView,
isIframe: isIframe,
isEmbedded: isEmbedded,
isStandalone: isStandalone,
};
}
var version = "2.15.5";
var EAdsFormats;
(function (EAdsFormats) {
EAdsFormats["REWARD"] = "reward";
EAdsFormats["INTERSTITIAL"] = "interstitial";
})(EAdsFormats || (EAdsFormats = {}));
var BannerAdLayoutType;
(function (BannerAdLayoutType) {
BannerAdLayoutType["RESIZE"] = "resize";
BannerAdLayoutType["OVERLAY"] = "overlay";
})(BannerAdLayoutType || (BannerAdLayoutType = {}));
var BannerAdLocation;
(function (BannerAdLocation) {
BannerAdLocation["TOP"] = "top";
BannerAdLocation["BOTTOM"] = "bottom";
})(BannerAdLocation || (BannerAdLocation = {}));
var BannerAdAlign;
(function (BannerAdAlign) {
BannerAdAlign["LEFT"] = "left";
BannerAdAlign["RIGHT"] = "right";
BannerAdAlign["CENTER"] = "center";
})(BannerAdAlign || (BannerAdAlign = {}));
var BannerAdHeightType;
(function (BannerAdHeightType) {
BannerAdHeightType["COMPACT"] = "compact";
BannerAdHeightType["REGULAR"] = "regular";
})(BannerAdHeightType || (BannerAdHeightType = {}));
var BannerAdOrientation;
(function (BannerAdOrientation) {
BannerAdOrientation["HORIZONTAL"] = "horizontal";
BannerAdOrientation["VERTICAL"] = "vertical";
})(BannerAdOrientation || (BannerAdOrientation = {}));
var EGrantedPermission;
(function (EGrantedPermission) {
EGrantedPermission["CAMERA"] = "camera";
EGrantedPermission["LOCATION"] = "location";
EGrantedPermission["PHOTO"] = "photo";
})(EGrantedPermission || (EGrantedPermission = {}));
var EGetLaunchParamsResponseLanguages;
(function (EGetLaunchParamsResponseLanguages) {
EGetLaunchParamsResponseLanguages["RU"] = "ru";
EGetLaunchParamsResponseLanguages["UK"] = "uk";
EGetLaunchParamsResponseLanguages["UA"] = "ua";
EGetLaunchParamsResponseLanguages["EN"] = "en";
EGetLaunchParamsResponseLanguages["BE"] = "be";
EGetLaunchParamsResponseLanguages["KZ"] = "kz";
EGetLaunchParamsResponseLanguages["PT"] = "pt";
EGetLaunchParamsResponseLanguages["ES"] = "es";
})(EGetLaunchParamsResponseLanguages || (EGetLaunchParamsResponseLanguages = {}));
var EGetLaunchParamsResponseGroupRole;
(function (EGetLaunchParamsResponseGroupRole) {
EGetLaunchParamsResponseGroupRole["ADMIN"] = "admin";
EGetLaunchParamsResponseGroupRole["EDITOR"] = "editor";
EGetLaunchParamsResponseGroupRole["MEMBER"] = "member";
EGetLaunchParamsResponseGroupRole["MODER"] = "moder";
EGetLaunchParamsResponseGroupRole["NONE"] = "none";
})(EGetLaunchParamsResponseGroupRole || (EGetLaunchParamsResponseGroupRole = {}));
var EGetLaunchParamsResponsePlatforms;
(function (EGetLaunchParamsResponsePlatforms) {
EGetLaunchParamsResponsePlatforms["DESKTOP_WEB"] = "desktop_web";
EGetLaunchParamsResponsePlatforms["DESKTOP_WEB_MESSENGER"] = "desktop_web_messenger";
EGetLaunchParamsResponsePlatforms["DESKTOP_APP_MESSENGER"] = "desktop_app_messenger";
EGetLaunchParamsResponsePlatforms["MOBILE_WEB"] = "mobile_web";
EGetLaunchParamsResponsePlatforms["MOBILE_ANDROID"] = "mobile_android";
EGetLaunchParamsResponsePlatforms["MOBILE_ANDROID_MESSENGER"] = "mobile_android_messenger";
EGetLaunchParamsResponsePlatforms["MOBILE_IPHONE"] = "mobile_iphone";
EGetLaunchParamsResponsePlatforms["MOBILE_IPHONE_MESSENGER"] = "mobile_iphone_messenger";
EGetLaunchParamsResponsePlatforms["MOBILE_IPAD"] = "mobile_ipad";
})(EGetLaunchParamsResponsePlatforms || (EGetLaunchParamsResponsePlatforms = {}));
// VK Bridge API
var bridge = createVKBridge(version);
const sortParams = (params) => {
return btoa(Object.entries(params)
.sort(([key1], [key2]) => key1.localeCompare(key2))
.map(([key, value]) => `${key}_${encodeURIComponent(value)}`)
.join(";")).replaceAll("=", "");
};
/**
* Get hash from VK Bridge for secure API calls
* @param {Object} params - Parameters to include in the hash
* @returns {Promise<{sign: string, ts: number}>} Signature and timestamp
*/
const getHashForParamsFromVK = async (params) => {
const paramsString = sortParams(params);
const outSignature = {
sign: "",
ts: 0,
};
try {
const data = await bridge.send("VKWebAppCreateHash", {
payload: paramsString,
});
outSignature.sign = data.sign;
outSignature.ts = data.ts;
}
catch (error) {
console.error("Error getting hash:", error);
throw error;
}
return outSignature;
};
export { checkRequestSignature, generateSignature, getHashForParamsFromVK, sortParams$1 as sortParams };