inapp-spy
Version:
detect in-app browsers
395 lines (386 loc) • 11.5 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// src/index.ts
var index_exports = {};
__export(index_exports, {
SFSVCExperimental: () => SFSVCExperimental,
default: () => index_default
});
module.exports = __toCommonJS(index_exports);
// src/utils.ts
var WIN_ERROR = "Window is not available and no user agent was provided.";
var getUA = () => {
var _a, _b;
if (typeof window !== "undefined") {
const ua = ((_a = window == null ? void 0 : window.navigator) == null ? void 0 : _a.userAgent) || ((_b = window == null ? void 0 : window.navigator) == null ? void 0 : _b.vendor) || // @ts-ignore
(window == null ? void 0 : window.opera);
if (ua) return ua;
}
console.error(WIN_ERROR);
return "";
};
var empty = {
isInApp: false,
appKey: void 0,
appName: void 0,
skipped: false
};
var getIsAppleDevice = (ua) => {
return ua.match(/(iPhone|iPad|iPod|Macintosh)/) !== null;
};
var checkSkip = ({
skip,
appKey,
ua
}) => {
if (!skip || skip.length === 0) return false;
const isApple = getIsAppleDevice(ua);
return skip.some(
({ appKey: excludeAppKey, platform }) => appKey === excludeAppKey && (!platform || isApple && platform === "apple" || !isApple && platform === "android")
);
};
var isSafariRegex = new RegExp(
/Mozilla\/5\.0 \([^\)]+\) AppleWebKit\/[^\s]+ \(KHTML, like Gecko\) Version\/[^\s]+ (Mobile\/[^\s]+ )?Safari\/[^\s]+$/
);
var getIsSafariUA = (ua) => {
return isSafariRegex.test(ua);
};
var isiOS = (ua) => {
return [
"iPad Simulator",
"iPhone Simulator",
"iPod Simulator",
"iPad",
"iPhone",
"iPod"
].includes(ua) || // iPad on iOS 13 detection
window && window.document && ua.includes("Mac") && "ontouchend" in window.document;
};
var getSafariVersion = (ua) => {
const match = [...ua.matchAll(/Version\/([^\s]+)/g)];
return match[0][1];
};
function waitForPageLoad() {
return __async(this, null, function* () {
return new Promise((resolve) => {
if (window.document.readyState === "complete") {
resolve(true);
} else {
const onLoad = () => {
resolve(true);
window.removeEventListener("load", onLoad);
};
window.addEventListener("load", onLoad);
}
});
});
}
var pollForProperties = (_0) => __async(null, [_0], function* ({
maxTime,
properties,
interval
}) {
let elapsed = 0;
return new Promise((resolve) => {
const intervalId = setInterval(() => {
elapsed += interval;
for (const property of properties) {
if (property in window) {
clearInterval(intervalId);
resolve(true);
return;
}
}
if (elapsed >= maxTime) {
clearInterval(intervalId);
resolve(false);
}
}, interval);
});
});
// src/detectTelegram.ts
var getIsTelegram = () => {
return "TelegramWebview" in window || // Android
"TelegramWebviewProxy" in window || // iPhone
"TelegramWebviewProxyProto" in window;
};
// src/detectClientSide.ts
var appNameCustom = {
telegram: {
name: "Telegram"
}
};
var getDetectClientSide = () => {
if (typeof window === "undefined") return;
if (getIsTelegram()) return "telegram";
return;
};
var appKeysDetectByCustom = Object.keys(
appNameCustom
);
// src/regexAppName.ts
var appNameRegExps = {
messenger: {
regex: /(\bFB[\w_]+\/(Messenger))|(^(?!.*\buseragents)(?!.*\bIABMV).*(FB_IAB|FBAN).*)/i,
// Experimental for newer UAs - don't have `"useragents:" or end in "IABMV"
name: "Facebook Messenger"
},
instagram: {
regex: /\bInstagram/i,
name: "Instagram"
},
facebook: {
regex: /\bFB[\w_]+\//,
name: "Facebook"
},
twitter: {
regex: /\bTwitter/i,
name: "Twitter"
},
line: {
regex: /\bLine\//i,
name: "Line"
},
wechat: {
regex: /\bMicroMessenger\//i,
name: "WeChat"
},
threads: {
regex: /\bBarcelona/i,
name: "Threads"
},
tiktok: {
regex: /musical_ly|Bytedance/i,
name: "TikTok"
},
snapchat: {
regex: /Snapchat/i,
name: "Snapchat"
},
linkedin: {
regex: /LinkedInApp/i,
name: "LinkedIn"
},
gsa: {
regex: /GSA/i,
name: "Google Search App"
}
};
var appKeysDetectByUA = Object.keys(
appNameRegExps
);
var getAppKey = (ua) => {
return appKeysDetectByUA.find(
(appName) => appNameRegExps[appName].regex.test(ua)
);
};
// src/regexInApp.ts
var inAppRegExps = [
"WebView",
// Apple devices but not with "Safari/" following
"(iPhone|iPod|iPad)(?!.*Safari/)",
"Android.*wv\\)",
// Match Facebook FB_ or FB then word char (Android)
"FB_\\w|FB\\w",
// Can end in Safari (iPhone)
"Snapchat",
"GSA",
"Instagram"
];
var inappRegex = new RegExp(
`${inAppRegExps.map((reg) => `(${reg})`).join("|")}`,
"ig"
);
// src/detectSafariPrivate.ts
var currentSafariTest = () => __async(null, null, function* () {
try {
yield navigator.storage.getDirectory();
return false;
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
return msg.toLowerCase().includes("unknown transient reason");
}
});
var safari13to18Test = () => {
let settled = false;
return new Promise((resolve) => {
const name = `idb${Math.random()}`;
const openReq = indexedDB.open(name, 1);
const finish = (isPrivate, db) => {
if (settled) return;
settled = true;
if (db) db.close();
indexedDB.deleteDatabase(name);
resolve(isPrivate);
};
openReq.onupgradeneeded = (ev) => {
const db = ev.target.result;
const putReq = db.createObjectStore("t", { autoIncrement: true }).put(new Blob());
putReq.onerror = (event) => {
var _a;
const req = event.target;
const message = ((_a = req.error) == null ? void 0 : _a.message) || "";
finish(message.includes("are not yet supported"), db);
};
putReq.onsuccess = () => finish(false, db);
};
openReq.onerror = () => finish(false);
openReq.onsuccess = () => finish(false, openReq.result);
});
};
var getIsSafariPrivate = () => __async(null, null, function* () {
var _a;
if (typeof navigator === "undefined") return false;
if (((_a = navigator.storage) == null ? void 0 : _a.getDirectory) !== void 0) {
return yield currentSafariTest();
} else if (navigator.maxTouchPoints !== void 0) {
return yield safari13to18Test();
}
return false;
});
// src/detectSFSVC.ts
var compare = (a, b) => {
return a.localeCompare(b, void 0, { numeric: true, sensitivity: "base" });
};
var consoleDebug = ({
note,
debug,
last
}) => {
if (debug) {
console.log(
note,
"SchemaDataExtractor" in window || "MicrodataExtractor" in window ? "This is Safari" : last ? "Did not detect Safari - assuming SFSVC" : "Still checking if Safari",
performance.now()
);
}
};
var minSafariVersion = "17";
var getSFSVCExperimental = (..._0) => __async(null, [..._0], function* ({
debug = false,
maxVersion,
maxTime = 300
// Max time to figure out if it is Safari (ie not SFSVC)
} = {}) {
const ua = getUA();
if (!ua) return false;
if (!isiOS(ua)) return false;
if (!getIsSafariUA(ua)) return false;
if ("clearAppBadge" in ((window == null ? void 0 : window.navigator) || {})) return false;
const isSafariPrivate = yield getIsSafariPrivate();
if (isSafariPrivate) return false;
if (getIsTelegram()) return false;
const version = getSafariVersion(ua);
if (compare(version, minSafariVersion) < 0) return false;
if (maxVersion !== void 0 && (compare(maxVersion, minSafariVersion) < 0 || compare(version, maxVersion) > 0))
return false;
yield waitForPageLoad();
if (debug) consoleDebug({ note: "Page loaded", debug });
const isSafari = yield pollForProperties({
interval: 60,
maxTime,
properties: ["SchemaDataExtractor", "MicrodataExtractor"]
});
if (debug) consoleDebug({ note: "Extra polling done", debug, last: true });
return !isSafari;
});
// src/index.ts
var InAppSpy = (options = {}) => {
var _a, _b;
const { skip, ua = "" } = options;
const userAgent = ua || getUA();
if (!userAgent)
return __spreadProps(__spreadValues({}, empty), {
ua: userAgent
});
const skipFn = (key) => checkSkip({ skip, appKey: key, ua: userAgent });
if (userAgent.match(inappRegex) !== null) {
const appKey2 = getAppKey(userAgent);
if (skipFn(appKey2)) return __spreadProps(__spreadValues({}, empty), { ua: userAgent, skipped: true });
return {
isInApp: true,
appKey: appKey2,
appName: appKey2 ? appNameRegExps[appKey2].name : void 0,
ua: userAgent,
skipped: false
};
}
const appKey = getDetectClientSide();
if (appKey) {
if (skipFn(appKey)) return __spreadProps(__spreadValues({}, empty), { ua: userAgent, skipped: true });
return {
isInApp: true,
appKey,
appName: (_b = (_a = appNameCustom) == null ? void 0 : _a[appKey]) == null ? void 0 : _b.name,
ua: userAgent,
skipped: false
};
}
return __spreadProps(__spreadValues({}, empty), {
ua: userAgent
});
};
var SFSVCExperimental = getSFSVCExperimental;
var index_default = InAppSpy;
if (typeof window !== "undefined" && false) {
window.InAppSpy = InAppSpy;
window.SFSVCExperimental = SFSVCExperimental;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
SFSVCExperimental
});
//# sourceMappingURL=index.js.map