From 44f3e448f9cb7d20469e27fb1ebe3715dff8f96b Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Fri, 26 Jan 2024 04:50:15 +0300 Subject: [PATCH 01/18] [Analytics] Unify client analytics --- packages/unity-js-bridge/src/thirdweb-bridge.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/unity-js-bridge/src/thirdweb-bridge.ts b/packages/unity-js-bridge/src/thirdweb-bridge.ts index 99358a8145f..ff66a3aeaf2 100644 --- a/packages/unity-js-bridge/src/thirdweb-bridge.ts +++ b/packages/unity-js-bridge/src/thirdweb-bridge.ts @@ -132,6 +132,12 @@ class ThirdwebBridge implements TWBridge { } public initialize(chain: ChainIdOrName, options: string) { + if(typeof globalThis !== "undefined"){ + (globalThis as any).X_SDK_NAME = "UnitySDK"; + (globalThis as any).X_SDK_PLATFORM = "unity"; + (globalThis as any).X_SDK_VERSION = "4.5.1"; + (globalThis as any).X_SDK_OS = "webgl"; + } this.initializedChain = chain; console.debug("thirdwebSDK initialization:", chain, options); const sdkOptions = JSON.parse(options); From c57cf155b434fd67ed68ddf9f90dc23c7388e147 Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Mon, 29 Jan 2024 15:54:59 -0500 Subject: [PATCH 02/18] react and rn --- .changeset/silly-dolphins-fly.md | 7 +++++++ .../src/evm/providers/thirdweb-provider.tsx | 10 ++++++++++ packages/react-native/src/evm/utils/global.ts | 7 ------- packages/react-native/src/index.ts | 7 ------- packages/react/src/evm/providers/thirdweb-provider.tsx | 9 +++++++++ packages/react/src/evm/utils/isMobile.ts | 2 +- 6 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 .changeset/silly-dolphins-fly.md delete mode 100644 packages/react-native/src/evm/utils/global.ts diff --git a/.changeset/silly-dolphins-fly.md b/.changeset/silly-dolphins-fly.md new file mode 100644 index 00000000000..655fd559761 --- /dev/null +++ b/.changeset/silly-dolphins-fly.md @@ -0,0 +1,7 @@ +--- +"@thirdweb-dev/unity-js-bridge": patch +"@thirdweb-dev/react-native": patch +"@thirdweb-dev/react": patch +--- + +Adds global sdk vars diff --git a/packages/react-native/src/evm/providers/thirdweb-provider.tsx b/packages/react-native/src/evm/providers/thirdweb-provider.tsx index d635f5d335a..cef6f642a6a 100644 --- a/packages/react-native/src/evm/providers/thirdweb-provider.tsx +++ b/packages/react-native/src/evm/providers/thirdweb-provider.tsx @@ -17,6 +17,8 @@ import { SafeAreaProvider } from "react-native-safe-area-context"; import { walletIds } from "@thirdweb-dev/wallets"; import { ThirdwebStorage } from "../../core/storage/storage"; import type { Locale } from "../i18n/types"; +import { appBundleId, packageVersion } from "../utils/version"; +import { Platform } from "react-native"; export interface ThirdwebProviderProps extends Omit< @@ -148,6 +150,14 @@ export const ThirdwebProvider = ( ...restProps } = props; + if (typeof globalThis !== "undefined") { + (globalThis as any).X_SDK_NAME = packageVersion.name; + (globalThis as any).X_SDK_PLATFORM = "react-native"; + (globalThis as any).X_SDK_VERSION = packageVersion.version; + (globalThis as any).X_SDK_OS = Platform.OS; + (globalThis as any).APP_BUNDLE_ID = appBundleId; + } + const coinbaseWalletObj = supportedWallets.find( (w) => w.id === walletIds.coinbase, ); diff --git a/packages/react-native/src/evm/utils/global.ts b/packages/react-native/src/evm/utils/global.ts deleted file mode 100644 index a53c336eaa8..00000000000 --- a/packages/react-native/src/evm/utils/global.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function isGlobalThisPresent() { - return typeof globalThis !== "undefined"; -} - -export function isAppBundleIdPresentInGlobal() { - return isGlobalThisPresent() && "APP_BUNDLE_ID" in globalThis; -} diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index c7a96257bc2..5f9afe24706 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -1,8 +1 @@ -import { isGlobalThisPresent } from "./evm/utils/global"; -import { appBundleId } from "./evm/utils/version"; - export * from "./evm"; - -if (isGlobalThisPresent()) { - (globalThis as any).APP_BUNDLE_ID = appBundleId; -} diff --git a/packages/react/src/evm/providers/thirdweb-provider.tsx b/packages/react/src/evm/providers/thirdweb-provider.tsx index c83bb258da1..3d739432404 100644 --- a/packages/react/src/evm/providers/thirdweb-provider.tsx +++ b/packages/react/src/evm/providers/thirdweb-provider.tsx @@ -16,6 +16,8 @@ import { en } from "../locales/en"; import { ThirdwebLocaleContext } from "./locale-provider"; import { walletIds } from "@thirdweb-dev/wallets"; import { ThirdwebLocale } from "../locales/types"; +import packageJson from "../../../package.json"; +import { detectOS } from "../utils/isMobile"; export interface ThirdwebProviderProps extends Omit< @@ -479,6 +481,13 @@ export const ThirdwebProvider = ( ...restProps } = props; + if (typeof globalThis !== "undefined") { + (globalThis as any).X_SDK_NAME = packageJson.name; + (globalThis as any).X_SDK_PLATFORM = "react"; + (globalThis as any).X_SDK_VERSION = packageJson.version; + (globalThis as any).X_SDK_OS = detectOS(); + } + const wallets: WalletConfig[] = supportedWallets || defaultWallets; const theme = _theme || "dark"; diff --git a/packages/react/src/evm/utils/isMobile.ts b/packages/react/src/evm/utils/isMobile.ts index a556b8be17c..87be0fd35a0 100644 --- a/packages/react/src/evm/utils/isMobile.ts +++ b/packages/react/src/evm/utils/isMobile.ts @@ -41,7 +41,7 @@ export function isIOS(): boolean { /** * @internal */ -function detectOS() { +export function detectOS() { const env = detectEnv(); return env?.os ? env.os : undefined; } From 8fe15050c93e1ee877463d5d4f9d1ed9917ad315 Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Mon, 29 Jan 2024 16:26:19 -0500 Subject: [PATCH 03/18] platform --- packages/react-native/src/evm/providers/thirdweb-provider.tsx | 2 +- packages/react/src/evm/providers/thirdweb-provider.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native/src/evm/providers/thirdweb-provider.tsx b/packages/react-native/src/evm/providers/thirdweb-provider.tsx index cef6f642a6a..7ae247e6428 100644 --- a/packages/react-native/src/evm/providers/thirdweb-provider.tsx +++ b/packages/react-native/src/evm/providers/thirdweb-provider.tsx @@ -152,7 +152,7 @@ export const ThirdwebProvider = ( if (typeof globalThis !== "undefined") { (globalThis as any).X_SDK_NAME = packageVersion.name; - (globalThis as any).X_SDK_PLATFORM = "react-native"; + (globalThis as any).X_SDK_PLATFORM = "mobile"; (globalThis as any).X_SDK_VERSION = packageVersion.version; (globalThis as any).X_SDK_OS = Platform.OS; (globalThis as any).APP_BUNDLE_ID = appBundleId; diff --git a/packages/react/src/evm/providers/thirdweb-provider.tsx b/packages/react/src/evm/providers/thirdweb-provider.tsx index 3d739432404..4ac7e450eee 100644 --- a/packages/react/src/evm/providers/thirdweb-provider.tsx +++ b/packages/react/src/evm/providers/thirdweb-provider.tsx @@ -483,7 +483,7 @@ export const ThirdwebProvider = ( if (typeof globalThis !== "undefined") { (globalThis as any).X_SDK_NAME = packageJson.name; - (globalThis as any).X_SDK_PLATFORM = "react"; + (globalThis as any).X_SDK_PLATFORM = "browser"; (globalThis as any).X_SDK_VERSION = packageJson.version; (globalThis as any).X_SDK_OS = detectOS(); } From a35b725d322193c3eaec889b3a095da9f040b228 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Tue, 30 Jan 2024 00:53:07 +0300 Subject: [PATCH 04/18] UnitySDK -> UnitySDK_WebGL --- packages/unity-js-bridge/src/thirdweb-bridge.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/unity-js-bridge/src/thirdweb-bridge.ts b/packages/unity-js-bridge/src/thirdweb-bridge.ts index ff66a3aeaf2..583a44329fa 100644 --- a/packages/unity-js-bridge/src/thirdweb-bridge.ts +++ b/packages/unity-js-bridge/src/thirdweb-bridge.ts @@ -133,7 +133,7 @@ class ThirdwebBridge implements TWBridge { public initialize(chain: ChainIdOrName, options: string) { if(typeof globalThis !== "undefined"){ - (globalThis as any).X_SDK_NAME = "UnitySDK"; + (globalThis as any).X_SDK_NAME = "UnitySDK_WebGL"; (globalThis as any).X_SDK_PLATFORM = "unity"; (globalThis as any).X_SDK_VERSION = "4.5.1"; (globalThis as any).X_SDK_OS = "webgl"; From 110dd8fe306447ef58c843f6c89e2c934f665c5c Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Tue, 30 Jan 2024 01:14:44 +0300 Subject: [PATCH 05/18] detect-browser for webgl os --- packages/unity-js-bridge/package.json | 1 + packages/unity-js-bridge/src/thirdweb-bridge.ts | 10 +++++++++- pnpm-lock.yaml | 7 +++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/unity-js-bridge/package.json b/packages/unity-js-bridge/package.json index ce2d089d35a..2e12e5e3ec0 100644 --- a/packages/unity-js-bridge/package.json +++ b/packages/unity-js-bridge/package.json @@ -18,6 +18,7 @@ "@thirdweb-dev/sdk": "workspace:*", "@thirdweb-dev/storage": "workspace:*", "@thirdweb-dev/wallets": "workspace:*", + "detect-browser": "^5.3.0", "ethers": "^5.7.2" }, "devDependencies": { diff --git a/packages/unity-js-bridge/src/thirdweb-bridge.ts b/packages/unity-js-bridge/src/thirdweb-bridge.ts index 583a44329fa..e2aef2f6683 100644 --- a/packages/unity-js-bridge/src/thirdweb-bridge.ts +++ b/packages/unity-js-bridge/src/thirdweb-bridge.ts @@ -25,6 +25,7 @@ import { getChainByChainId, } from "@thirdweb-dev/chains"; import type { ContractInterface, Signer } from "ethers"; +import { detect } from "detect-browser"; declare global { interface Window { @@ -133,10 +134,17 @@ class ThirdwebBridge implements TWBridge { public initialize(chain: ChainIdOrName, options: string) { if(typeof globalThis !== "undefined"){ + let browser; + try { + browser = detect(); + } catch { + console.warn("Failed to detect browser"); + browser = undefined; + } (globalThis as any).X_SDK_NAME = "UnitySDK_WebGL"; (globalThis as any).X_SDK_PLATFORM = "unity"; (globalThis as any).X_SDK_VERSION = "4.5.1"; - (globalThis as any).X_SDK_OS = "webgl"; + (globalThis as any).X_SDK_OS = browser?.os ?? "unknown"; } this.initializedChain = chain; console.debug("thirdwebSDK initialization:", chain, options); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9bda465320c..26ed9b397dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1447,6 +1447,9 @@ importers: '@thirdweb-dev/wallets': specifier: workspace:* version: link:../wallets + detect-browser: + specifier: ^5.3.0 + version: 5.3.0 ethers: specifier: ^5.7.2 version: 5.7.2 @@ -18731,11 +18734,11 @@ packages: eslint-import-resolver-node: 0.3.7 eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.60.1)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.3)(eslint@8.56.0) has: 1.0.3 - is-core-module: 2.13.1 + is-core-module: 2.11.0 is-glob: 4.0.3 minimatch: 3.1.2 object.values: 1.1.6 - resolve: 1.22.8 + resolve: 1.22.1 semver: 7.5.4 tsconfig-paths: 3.14.2 transitivePeerDependencies: From 99ebaba9dab12b708b125db9728eb5ef784d6cf8 Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Mon, 29 Jan 2024 17:24:57 -0500 Subject: [PATCH 06/18] detect os --- packages/sdk/src/core/utils/os.ts | 340 ++++++++++++++++++++++++++++++ packages/sdk/src/evm/core/sdk.ts | 42 ++-- 2 files changed, 367 insertions(+), 15 deletions(-) create mode 100644 packages/sdk/src/core/utils/os.ts diff --git a/packages/sdk/src/core/utils/os.ts b/packages/sdk/src/core/utils/os.ts new file mode 100644 index 00000000000..660704793d6 --- /dev/null +++ b/packages/sdk/src/core/utils/os.ts @@ -0,0 +1,340 @@ +export function getOperatingSystem() { + if (typeof navigator !== "undefined" && navigator.product === "ReactNative") { + return ""; + } else if (typeof window !== "undefined") { + const userAgent = navigator.userAgent; + + return detectOS(userAgent) || ""; + } else { + return process.platform; + } +} + +export type DetectedInfoType = + | "browser" + | "node" + | "bot-device" + | "bot" + | "react-native"; + +interface DetectedInfo< + T extends DetectedInfoType, + N extends string, + O, + V = null, +> { + readonly type: T; + readonly name: N; + readonly version: V; + readonly os: O; +} + +export class BrowserInfo + implements DetectedInfo<"browser", Browser, OperatingSystem | null, string> +{ + public readonly type = "browser"; + constructor( + public readonly name: Browser, + public readonly version: string, + public readonly os: OperatingSystem | null, + ) {} +} + +export class NodeInfo + implements DetectedInfo<"node", "node", NodeJS.Platform, string> +{ + public readonly type = "node"; + public readonly name: "node" = "node"; + public readonly os: NodeJS.Platform = process.platform; + + constructor(public readonly version: string) {} +} + +export class SearchBotDeviceInfo + implements + DetectedInfo<"bot-device", Browser, OperatingSystem | null, string> +{ + public readonly type = "bot-device"; + constructor( + public readonly name: Browser, + public readonly version: string, + public readonly os: OperatingSystem | null, + public readonly bot: string, + ) {} +} + +export class BotInfo implements DetectedInfo<"bot", "bot", null, null> { + public readonly type = "bot"; + public readonly bot: true = true; // NOTE: deprecated test name instead + public readonly name: "bot" = "bot"; + public readonly version: null = null; + public readonly os: null = null; +} + +export class ReactNativeInfo + implements DetectedInfo<"react-native", "react-native", null, null> +{ + public readonly type = "react-native"; + public readonly name: "react-native" = "react-native"; + public readonly version: null = null; + public readonly os: null = null; +} + +export type Browser = + | "aol" + | "edge" + | "edge-ios" + | "yandexbrowser" + | "kakaotalk" + | "samsung" + | "silk" + | "miui" + | "beaker" + | "edge-chromium" + | "chrome" + | "chromium-webview" + | "phantomjs" + | "crios" + | "firefox" + | "fxios" + | "opera-mini" + | "opera" + | "pie" + | "netfront" + | "ie" + | "bb10" + | "android" + | "ios" + | "safari" + | "facebook" + | "instagram" + | "ios-webview" + | "curl" + | "searchbot"; +export type OperatingSystem = + | "iOS" + | "Android OS" + | "BlackBerry OS" + | "Windows Mobile" + | "Amazon OS" + | "Windows 3.11" + | "Windows 95" + | "Windows 98" + | "Windows 2000" + | "Windows XP" + | "Windows Server 2003" + | "Windows Vista" + | "Windows 7" + | "Windows 8" + | "Windows 8.1" + | "Windows 10" + | "Windows ME" + | "Windows CE" + | "Open BSD" + | "Sun OS" + | "Linux" + | "Mac OS" + | "QNX" + | "BeOS" + | "OS/2" + | "Chrome OS"; +type UserAgentRule = [Browser, RegExp]; +type UserAgentMatch = [Browser, RegExpExecArray] | false; +type OperatingSystemRule = [OperatingSystem, RegExp]; + +// tslint:disable-next-line:max-line-length +const SEARCHBOX_UA_REGEX = + /alexa|bot|crawl(er|ing)|facebookexternalhit|feedburner|google web preview|nagios|postrank|pingdom|slurp|spider|yahoo!|yandex/; +const SEARCHBOT_OS_REGEX = + /(nuhk|curl|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask\ Jeeves\/Teoma|ia_archiver)/; +const REQUIRED_VERSION_PARTS = 3; + +const userAgentRules: UserAgentRule[] = [ + ["aol", /AOLShield\/([0-9\._]+)/], + ["edge", /Edge\/([0-9\._]+)/], + ["edge-ios", /EdgiOS\/([0-9\._]+)/], + ["yandexbrowser", /YaBrowser\/([0-9\._]+)/], + ["kakaotalk", /KAKAOTALK\s([0-9\.]+)/], + ["samsung", /SamsungBrowser\/([0-9\.]+)/], + ["silk", /\bSilk\/([0-9._-]+)\b/], + ["miui", /MiuiBrowser\/([0-9\.]+)$/], + ["beaker", /BeakerBrowser\/([0-9\.]+)/], + ["edge-chromium", /EdgA?\/([0-9\.]+)/], + [ + "chromium-webview", + /(?!Chrom.*OPR)wv\).*Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/, + ], + ["chrome", /(?!Chrom.*OPR)Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/], + ["phantomjs", /PhantomJS\/([0-9\.]+)(:?\s|$)/], + ["crios", /CriOS\/([0-9\.]+)(:?\s|$)/], + ["firefox", /Firefox\/([0-9\.]+)(?:\s|$)/], + ["fxios", /FxiOS\/([0-9\.]+)/], + ["opera-mini", /Opera Mini.*Version\/([0-9\.]+)/], + ["opera", /Opera\/([0-9\.]+)(?:\s|$)/], + ["opera", /OPR\/([0-9\.]+)(:?\s|$)/], + ["pie", /^Microsoft Pocket Internet Explorer\/(\d+\.\d+)$/], + [ + "pie", + /^Mozilla\/\d\.\d+\s\(compatible;\s(?:MSP?IE|MSInternet Explorer) (\d+\.\d+);.*Windows CE.*\)$/, + ], + ["netfront", /^Mozilla\/\d\.\d+.*NetFront\/(\d.\d)/], + ["ie", /Trident\/7\.0.*rv\:([0-9\.]+).*\).*Gecko$/], + ["ie", /MSIE\s([0-9\.]+);.*Trident\/[4-7].0/], + ["ie", /MSIE\s(7\.0)/], + ["bb10", /BB10;\sTouch.*Version\/([0-9\.]+)/], + ["android", /Android\s([0-9\.]+)/], + ["ios", /Version\/([0-9\._]+).*Mobile.*Safari.*/], + ["safari", /Version\/([0-9\._]+).*Safari/], + ["facebook", /FB[AS]V\/([0-9\.]+)/], + ["instagram", /Instagram\s([0-9\.]+)/], + ["ios-webview", /AppleWebKit\/([0-9\.]+).*Mobile/], + ["ios-webview", /AppleWebKit\/([0-9\.]+).*Gecko\)$/], + ["curl", /^curl\/([0-9\.]+)$/], + ["searchbot", SEARCHBOX_UA_REGEX], +]; +const operatingSystemRules: OperatingSystemRule[] = [ + ["iOS", /iP(hone|od|ad)/], + ["Android OS", /Android/], + ["BlackBerry OS", /BlackBerry|BB10/], + ["Windows Mobile", /IEMobile/], + ["Amazon OS", /Kindle/], + ["Windows 3.11", /Win16/], + ["Windows 95", /(Windows 95)|(Win95)|(Windows_95)/], + ["Windows 98", /(Windows 98)|(Win98)/], + ["Windows 2000", /(Windows NT 5.0)|(Windows 2000)/], + ["Windows XP", /(Windows NT 5.1)|(Windows XP)/], + ["Windows Server 2003", /(Windows NT 5.2)/], + ["Windows Vista", /(Windows NT 6.0)/], + ["Windows 7", /(Windows NT 6.1)/], + ["Windows 8", /(Windows NT 6.2)/], + ["Windows 8.1", /(Windows NT 6.3)/], + ["Windows 10", /(Windows NT 10.0)/], + ["Windows ME", /Windows ME/], + ["Windows CE", /Windows CE|WinCE|Microsoft Pocket Internet Explorer/], + ["Open BSD", /OpenBSD/], + ["Sun OS", /SunOS/], + ["Chrome OS", /CrOS/], + ["Linux", /(Linux)|(X11)/], + ["Mac OS", /(Mac_PowerPC)|(Macintosh)/], + ["QNX", /QNX/], + ["BeOS", /BeOS/], + ["OS/2", /OS\/2/], +]; + +export function detect( + userAgent?: string, +): + | BrowserInfo + | SearchBotDeviceInfo + | BotInfo + | NodeInfo + | ReactNativeInfo + | null { + if (!!userAgent) { + return parseUserAgent(userAgent); + } + + if ( + typeof document === "undefined" && + typeof navigator !== "undefined" && + navigator.product === "ReactNative" + ) { + return new ReactNativeInfo(); + } + + if (typeof navigator !== "undefined") { + return parseUserAgent(navigator.userAgent); + } + + return getNodeVersion(); +} + +function matchUserAgent(ua: string): UserAgentMatch { + // opted for using reduce here rather than Array#first with a regex.test call + // this is primarily because using the reduce we only perform the regex + // execution once rather than once for the test and for the exec again below + // probably something that needs to be benchmarked though + return ( + ua !== "" && + userAgentRules.reduce( + (matched: UserAgentMatch, [browser, regex]) => { + if (matched) { + return matched; + } + + const uaMatch = regex.exec(ua); + return !!uaMatch && [browser, uaMatch]; + }, + false, + ) + ); +} + +export function browserName(ua: string): Browser | null { + const data = matchUserAgent(ua); + return data ? data[0] : null; +} + +export function parseUserAgent( + ua: string, +): BrowserInfo | SearchBotDeviceInfo | BotInfo | null { + const matchedRule: UserAgentMatch = matchUserAgent(ua); + + if (!matchedRule) { + return null; + } + + const [name, match] = matchedRule; + if (name === "searchbot") { + return new BotInfo(); + } + // Do not use RegExp for split operation as some browser do not support it (See: http://blog.stevenlevithan.com/archives/cross-browser-split) + let versionParts = + match[1] && match[1].split(".").join("_").split("_").slice(0, 3); + if (versionParts) { + if (versionParts.length < REQUIRED_VERSION_PARTS) { + versionParts = [ + ...versionParts, + ...createVersionParts(REQUIRED_VERSION_PARTS - versionParts.length), + ]; + } + } else { + versionParts = []; + } + + const version = versionParts.join("."); + const os = detectOS(ua); + const searchBotMatch = SEARCHBOT_OS_REGEX.exec(ua); + + if (searchBotMatch && searchBotMatch[1]) { + return new SearchBotDeviceInfo(name, version, os, searchBotMatch[1]); + } + + return new BrowserInfo(name, version, os); +} + +export function detectOS(ua: string): OperatingSystem | null { + for (let ii = 0, count = operatingSystemRules.length; ii < count; ii++) { + const [os, regex] = operatingSystemRules[ii]; + const match = regex.exec(ua); + if (match) { + return os; + } + } + + return null; +} + +export function getNodeVersion(): NodeInfo | null { + const isNode = typeof process !== "undefined" && process.version; + return isNode ? new NodeInfo(process.version.slice(1)) : null; +} + +function createVersionParts(count: number): string[] { + const output = []; + for (let ii = 0; ii < count; ii++) { + output.push("0"); + } + + return output; +} diff --git a/packages/sdk/src/evm/core/sdk.ts b/packages/sdk/src/evm/core/sdk.ts index f40ff080666..1806f50da8b 100644 --- a/packages/sdk/src/evm/core/sdk.ts +++ b/packages/sdk/src/evm/core/sdk.ts @@ -117,6 +117,8 @@ import { VoteContractDeployMetadata, } from "../types/deploy/deploy-metadata"; import { DeployMetadata, DeployOptions } from "../types/deploy/deploy-options"; +import pkg from "../../../package.json"; +import { getOperatingSystem } from "../../core/utils/os"; /** * The main entry point for the thirdweb SDK @@ -309,6 +311,21 @@ export class ThirdwebSDK extends RPCConnectionHandler { this.options, this.storageHandler, ); + + if ( + typeof globalThis !== "undefined" && + (globalThis as any).X_SDK_NAME === undefined + ) { + (globalThis as any).X_SDK_NAME = pkg.name; + (globalThis as any).X_SDK_PLATFORM = + typeof navigator !== "undefined" && navigator.product === "ReactNative" + ? "mobile" + : typeof window !== "undefined" + ? "browser" + : "node"; + (globalThis as any).X_SDK_VERSION = pkg.version; + (globalThis as any).X_SDK_OS = getOperatingSystem(); + } } get auth() { @@ -592,9 +609,8 @@ export class ThirdwebSDK extends RPCConnectionHandler { } catch (e) { // fallback to // try resolving the contract type (legacy contracts) - const resolvedContractType = await this.resolveContractType( - resolvedAddress, - ); + const resolvedContractType = + await this.resolveContractType(resolvedAddress); if (resolvedContractType && resolvedContractType !== "custom") { // otherwise if it's a prebuilt contract we can just use the contract type const contractAbi = await PREBUILT_CONTRACTS_MAP[ @@ -721,9 +737,8 @@ export class ThirdwebSDK extends RPCConnectionHandler { walletAddress: AddressOrEns, chains: Chain[] = defaultChains, ): Promise { - const contracts = await this.multiChainRegistry.getContractAddresses( - walletAddress, - ); + const contracts = + await this.multiChainRegistry.getContractAddresses(walletAddress); const chainMap = chains.reduce( (acc, chain) => { @@ -1019,9 +1034,8 @@ export class ContractDeployer extends RPCConnectionHandler { metadata: NFTContractDeployMetadata, options?: DeployOptions, ): Promise => { - const parsedMetadata = await LoyaltyCardContractDeploy.parseAsync( - metadata, - ); + const parsedMetadata = + await LoyaltyCardContractDeploy.parseAsync(metadata); const contractURI = await this.storage.upload(parsedMetadata); const trustedForwarders: string[] = []; @@ -1078,9 +1092,8 @@ export class ContractDeployer extends RPCConnectionHandler { metadata: OpenEditionContractDeployMetadata, options?: DeployOptions, ): Promise => { - const parsedMetadata = await DropErc721ContractSchema.deploy.parseAsync( - metadata, - ); + const parsedMetadata = + await DropErc721ContractSchema.deploy.parseAsync(metadata); const contractURI = await this.storage.upload(parsedMetadata); const trustedForwarders: string[] = []; @@ -1938,9 +1951,8 @@ export class ContractDeployer extends RPCConnectionHandler { ?.factoryDeploymentData?.customFactoryInput?.customFactoryAddresses[ chainId ] as AddressOrEns; - const resolvedCustomFactoryAddress = await resolveAddress( - customFactoryAddress, - ); + const resolvedCustomFactoryAddress = + await resolveAddress(customFactoryAddress); invariant( resolvedCustomFactoryAddress, From 122d9f94334d713af9a5a2dce1e4089eda851e21 Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Mon, 29 Jan 2024 17:25:06 -0500 Subject: [PATCH 07/18] detect os --- packages/sdk/src/core/utils/detect-browser.ts | 334 ++++++++++++++++++ packages/sdk/src/core/utils/os.ts | 331 +---------------- 2 files changed, 336 insertions(+), 329 deletions(-) create mode 100644 packages/sdk/src/core/utils/detect-browser.ts diff --git a/packages/sdk/src/core/utils/detect-browser.ts b/packages/sdk/src/core/utils/detect-browser.ts new file mode 100644 index 00000000000..70fca7fd61a --- /dev/null +++ b/packages/sdk/src/core/utils/detect-browser.ts @@ -0,0 +1,334 @@ +/** + * @internal + * + * The code below comes from the package https://github.com/DamonOehlman/detect-browser + */ + +export type DetectedInfoType = + | "browser" + | "node" + | "bot-device" + | "bot" + | "react-native"; + +interface DetectedInfo< + T extends DetectedInfoType, + N extends string, + O, + V = null, +> { + readonly type: T; + readonly name: N; + readonly version: V; + readonly os: O; +} + +export class BrowserInfo + implements DetectedInfo<"browser", Browser, OperatingSystem | null, string> +{ + public readonly type = "browser"; + constructor( + public readonly name: Browser, + public readonly version: string, + public readonly os: OperatingSystem | null, + ) {} +} + +export class NodeInfo + implements DetectedInfo<"node", "node", NodeJS.Platform, string> +{ + public readonly type = "node"; + public readonly name: "node" = "node"; + public readonly os: NodeJS.Platform = process.platform; + + constructor(public readonly version: string) {} +} + +export class SearchBotDeviceInfo + implements + DetectedInfo<"bot-device", Browser, OperatingSystem | null, string> +{ + public readonly type = "bot-device"; + constructor( + public readonly name: Browser, + public readonly version: string, + public readonly os: OperatingSystem | null, + public readonly bot: string, + ) {} +} + +export class BotInfo implements DetectedInfo<"bot", "bot", null, null> { + public readonly type = "bot"; + public readonly bot: true = true; // NOTE: deprecated test name instead + public readonly name: "bot" = "bot"; + public readonly version: null = null; + public readonly os: null = null; +} + +export class ReactNativeInfo + implements DetectedInfo<"react-native", "react-native", null, null> +{ + public readonly type = "react-native"; + public readonly name: "react-native" = "react-native"; + public readonly version: null = null; + public readonly os: null = null; +} + +export type Browser = + | "aol" + | "edge" + | "edge-ios" + | "yandexbrowser" + | "kakaotalk" + | "samsung" + | "silk" + | "miui" + | "beaker" + | "edge-chromium" + | "chrome" + | "chromium-webview" + | "phantomjs" + | "crios" + | "firefox" + | "fxios" + | "opera-mini" + | "opera" + | "pie" + | "netfront" + | "ie" + | "bb10" + | "android" + | "ios" + | "safari" + | "facebook" + | "instagram" + | "ios-webview" + | "curl" + | "searchbot"; +export type OperatingSystem = + | "iOS" + | "Android OS" + | "BlackBerry OS" + | "Windows Mobile" + | "Amazon OS" + | "Windows 3.11" + | "Windows 95" + | "Windows 98" + | "Windows 2000" + | "Windows XP" + | "Windows Server 2003" + | "Windows Vista" + | "Windows 7" + | "Windows 8" + | "Windows 8.1" + | "Windows 10" + | "Windows ME" + | "Windows CE" + | "Open BSD" + | "Sun OS" + | "Linux" + | "Mac OS" + | "QNX" + | "BeOS" + | "OS/2" + | "Chrome OS"; +type UserAgentRule = [Browser, RegExp]; +type UserAgentMatch = [Browser, RegExpExecArray] | false; +type OperatingSystemRule = [OperatingSystem, RegExp]; + +// tslint:disable-next-line:max-line-length +const SEARCHBOX_UA_REGEX = + /alexa|bot|crawl(er|ing)|facebookexternalhit|feedburner|google web preview|nagios|postrank|pingdom|slurp|spider|yahoo!|yandex/; +const SEARCHBOT_OS_REGEX = + /(nuhk|curl|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask\ Jeeves\/Teoma|ia_archiver)/; +const REQUIRED_VERSION_PARTS = 3; + +const userAgentRules: UserAgentRule[] = [ + ["aol", /AOLShield\/([0-9\._]+)/], + ["edge", /Edge\/([0-9\._]+)/], + ["edge-ios", /EdgiOS\/([0-9\._]+)/], + ["yandexbrowser", /YaBrowser\/([0-9\._]+)/], + ["kakaotalk", /KAKAOTALK\s([0-9\.]+)/], + ["samsung", /SamsungBrowser\/([0-9\.]+)/], + ["silk", /\bSilk\/([0-9._-]+)\b/], + ["miui", /MiuiBrowser\/([0-9\.]+)$/], + ["beaker", /BeakerBrowser\/([0-9\.]+)/], + ["edge-chromium", /EdgA?\/([0-9\.]+)/], + [ + "chromium-webview", + /(?!Chrom.*OPR)wv\).*Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/, + ], + ["chrome", /(?!Chrom.*OPR)Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/], + ["phantomjs", /PhantomJS\/([0-9\.]+)(:?\s|$)/], + ["crios", /CriOS\/([0-9\.]+)(:?\s|$)/], + ["firefox", /Firefox\/([0-9\.]+)(?:\s|$)/], + ["fxios", /FxiOS\/([0-9\.]+)/], + ["opera-mini", /Opera Mini.*Version\/([0-9\.]+)/], + ["opera", /Opera\/([0-9\.]+)(?:\s|$)/], + ["opera", /OPR\/([0-9\.]+)(:?\s|$)/], + ["pie", /^Microsoft Pocket Internet Explorer\/(\d+\.\d+)$/], + [ + "pie", + /^Mozilla\/\d\.\d+\s\(compatible;\s(?:MSP?IE|MSInternet Explorer) (\d+\.\d+);.*Windows CE.*\)$/, + ], + ["netfront", /^Mozilla\/\d\.\d+.*NetFront\/(\d.\d)/], + ["ie", /Trident\/7\.0.*rv\:([0-9\.]+).*\).*Gecko$/], + ["ie", /MSIE\s([0-9\.]+);.*Trident\/[4-7].0/], + ["ie", /MSIE\s(7\.0)/], + ["bb10", /BB10;\sTouch.*Version\/([0-9\.]+)/], + ["android", /Android\s([0-9\.]+)/], + ["ios", /Version\/([0-9\._]+).*Mobile.*Safari.*/], + ["safari", /Version\/([0-9\._]+).*Safari/], + ["facebook", /FB[AS]V\/([0-9\.]+)/], + ["instagram", /Instagram\s([0-9\.]+)/], + ["ios-webview", /AppleWebKit\/([0-9\.]+).*Mobile/], + ["ios-webview", /AppleWebKit\/([0-9\.]+).*Gecko\)$/], + ["curl", /^curl\/([0-9\.]+)$/], + ["searchbot", SEARCHBOX_UA_REGEX], +]; +const operatingSystemRules: OperatingSystemRule[] = [ + ["iOS", /iP(hone|od|ad)/], + ["Android OS", /Android/], + ["BlackBerry OS", /BlackBerry|BB10/], + ["Windows Mobile", /IEMobile/], + ["Amazon OS", /Kindle/], + ["Windows 3.11", /Win16/], + ["Windows 95", /(Windows 95)|(Win95)|(Windows_95)/], + ["Windows 98", /(Windows 98)|(Win98)/], + ["Windows 2000", /(Windows NT 5.0)|(Windows 2000)/], + ["Windows XP", /(Windows NT 5.1)|(Windows XP)/], + ["Windows Server 2003", /(Windows NT 5.2)/], + ["Windows Vista", /(Windows NT 6.0)/], + ["Windows 7", /(Windows NT 6.1)/], + ["Windows 8", /(Windows NT 6.2)/], + ["Windows 8.1", /(Windows NT 6.3)/], + ["Windows 10", /(Windows NT 10.0)/], + ["Windows ME", /Windows ME/], + ["Windows CE", /Windows CE|WinCE|Microsoft Pocket Internet Explorer/], + ["Open BSD", /OpenBSD/], + ["Sun OS", /SunOS/], + ["Chrome OS", /CrOS/], + ["Linux", /(Linux)|(X11)/], + ["Mac OS", /(Mac_PowerPC)|(Macintosh)/], + ["QNX", /QNX/], + ["BeOS", /BeOS/], + ["OS/2", /OS\/2/], +]; + +export function detect( + userAgent?: string, +): + | BrowserInfo + | SearchBotDeviceInfo + | BotInfo + | NodeInfo + | ReactNativeInfo + | null { + if (!!userAgent) { + return parseUserAgent(userAgent); + } + + if ( + typeof document === "undefined" && + typeof navigator !== "undefined" && + navigator.product === "ReactNative" + ) { + return new ReactNativeInfo(); + } + + if (typeof navigator !== "undefined") { + return parseUserAgent(navigator.userAgent); + } + + return getNodeVersion(); +} + +function matchUserAgent(ua: string): UserAgentMatch { + // opted for using reduce here rather than Array#first with a regex.test call + // this is primarily because using the reduce we only perform the regex + // execution once rather than once for the test and for the exec again below + // probably something that needs to be benchmarked though + return ( + ua !== "" && + userAgentRules.reduce( + (matched: UserAgentMatch, [browser, regex]) => { + if (matched) { + return matched; + } + + const uaMatch = regex.exec(ua); + return !!uaMatch && [browser, uaMatch]; + }, + false, + ) + ); +} + +export function browserName(ua: string): Browser | null { + const data = matchUserAgent(ua); + return data ? data[0] : null; +} + +export function parseUserAgent( + ua: string, +): BrowserInfo | SearchBotDeviceInfo | BotInfo | null { + const matchedRule: UserAgentMatch = matchUserAgent(ua); + + if (!matchedRule) { + return null; + } + + const [name, match] = matchedRule; + if (name === "searchbot") { + return new BotInfo(); + } + // Do not use RegExp for split operation as some browser do not support it (See: http://blog.stevenlevithan.com/archives/cross-browser-split) + let versionParts = + match[1] && match[1].split(".").join("_").split("_").slice(0, 3); + if (versionParts) { + if (versionParts.length < REQUIRED_VERSION_PARTS) { + versionParts = [ + ...versionParts, + ...createVersionParts(REQUIRED_VERSION_PARTS - versionParts.length), + ]; + } + } else { + versionParts = []; + } + + const version = versionParts.join("."); + const os = detectOS(ua); + const searchBotMatch = SEARCHBOT_OS_REGEX.exec(ua); + + if (searchBotMatch && searchBotMatch[1]) { + return new SearchBotDeviceInfo(name, version, os, searchBotMatch[1]); + } + + return new BrowserInfo(name, version, os); +} + +export function detectOS(ua: string): OperatingSystem | null { + for (let ii = 0, count = operatingSystemRules.length; ii < count; ii++) { + const [os, regex] = operatingSystemRules[ii]; + const match = regex.exec(ua); + if (match) { + return os; + } + } + + return null; +} + +export function getNodeVersion(): NodeInfo | null { + const isNode = typeof process !== "undefined" && process.version; + return isNode ? new NodeInfo(process.version.slice(1)) : null; +} + +function createVersionParts(count: number): string[] { + const output = []; + for (let ii = 0; ii < count; ii++) { + output.push("0"); + } + + return output; +} diff --git a/packages/sdk/src/core/utils/os.ts b/packages/sdk/src/core/utils/os.ts index 660704793d6..44ba468e4ed 100644 --- a/packages/sdk/src/core/utils/os.ts +++ b/packages/sdk/src/core/utils/os.ts @@ -1,3 +1,5 @@ +import { detectOS } from "./detect-browser"; + export function getOperatingSystem() { if (typeof navigator !== "undefined" && navigator.product === "ReactNative") { return ""; @@ -9,332 +11,3 @@ export function getOperatingSystem() { return process.platform; } } - -export type DetectedInfoType = - | "browser" - | "node" - | "bot-device" - | "bot" - | "react-native"; - -interface DetectedInfo< - T extends DetectedInfoType, - N extends string, - O, - V = null, -> { - readonly type: T; - readonly name: N; - readonly version: V; - readonly os: O; -} - -export class BrowserInfo - implements DetectedInfo<"browser", Browser, OperatingSystem | null, string> -{ - public readonly type = "browser"; - constructor( - public readonly name: Browser, - public readonly version: string, - public readonly os: OperatingSystem | null, - ) {} -} - -export class NodeInfo - implements DetectedInfo<"node", "node", NodeJS.Platform, string> -{ - public readonly type = "node"; - public readonly name: "node" = "node"; - public readonly os: NodeJS.Platform = process.platform; - - constructor(public readonly version: string) {} -} - -export class SearchBotDeviceInfo - implements - DetectedInfo<"bot-device", Browser, OperatingSystem | null, string> -{ - public readonly type = "bot-device"; - constructor( - public readonly name: Browser, - public readonly version: string, - public readonly os: OperatingSystem | null, - public readonly bot: string, - ) {} -} - -export class BotInfo implements DetectedInfo<"bot", "bot", null, null> { - public readonly type = "bot"; - public readonly bot: true = true; // NOTE: deprecated test name instead - public readonly name: "bot" = "bot"; - public readonly version: null = null; - public readonly os: null = null; -} - -export class ReactNativeInfo - implements DetectedInfo<"react-native", "react-native", null, null> -{ - public readonly type = "react-native"; - public readonly name: "react-native" = "react-native"; - public readonly version: null = null; - public readonly os: null = null; -} - -export type Browser = - | "aol" - | "edge" - | "edge-ios" - | "yandexbrowser" - | "kakaotalk" - | "samsung" - | "silk" - | "miui" - | "beaker" - | "edge-chromium" - | "chrome" - | "chromium-webview" - | "phantomjs" - | "crios" - | "firefox" - | "fxios" - | "opera-mini" - | "opera" - | "pie" - | "netfront" - | "ie" - | "bb10" - | "android" - | "ios" - | "safari" - | "facebook" - | "instagram" - | "ios-webview" - | "curl" - | "searchbot"; -export type OperatingSystem = - | "iOS" - | "Android OS" - | "BlackBerry OS" - | "Windows Mobile" - | "Amazon OS" - | "Windows 3.11" - | "Windows 95" - | "Windows 98" - | "Windows 2000" - | "Windows XP" - | "Windows Server 2003" - | "Windows Vista" - | "Windows 7" - | "Windows 8" - | "Windows 8.1" - | "Windows 10" - | "Windows ME" - | "Windows CE" - | "Open BSD" - | "Sun OS" - | "Linux" - | "Mac OS" - | "QNX" - | "BeOS" - | "OS/2" - | "Chrome OS"; -type UserAgentRule = [Browser, RegExp]; -type UserAgentMatch = [Browser, RegExpExecArray] | false; -type OperatingSystemRule = [OperatingSystem, RegExp]; - -// tslint:disable-next-line:max-line-length -const SEARCHBOX_UA_REGEX = - /alexa|bot|crawl(er|ing)|facebookexternalhit|feedburner|google web preview|nagios|postrank|pingdom|slurp|spider|yahoo!|yandex/; -const SEARCHBOT_OS_REGEX = - /(nuhk|curl|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask\ Jeeves\/Teoma|ia_archiver)/; -const REQUIRED_VERSION_PARTS = 3; - -const userAgentRules: UserAgentRule[] = [ - ["aol", /AOLShield\/([0-9\._]+)/], - ["edge", /Edge\/([0-9\._]+)/], - ["edge-ios", /EdgiOS\/([0-9\._]+)/], - ["yandexbrowser", /YaBrowser\/([0-9\._]+)/], - ["kakaotalk", /KAKAOTALK\s([0-9\.]+)/], - ["samsung", /SamsungBrowser\/([0-9\.]+)/], - ["silk", /\bSilk\/([0-9._-]+)\b/], - ["miui", /MiuiBrowser\/([0-9\.]+)$/], - ["beaker", /BeakerBrowser\/([0-9\.]+)/], - ["edge-chromium", /EdgA?\/([0-9\.]+)/], - [ - "chromium-webview", - /(?!Chrom.*OPR)wv\).*Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/, - ], - ["chrome", /(?!Chrom.*OPR)Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/], - ["phantomjs", /PhantomJS\/([0-9\.]+)(:?\s|$)/], - ["crios", /CriOS\/([0-9\.]+)(:?\s|$)/], - ["firefox", /Firefox\/([0-9\.]+)(?:\s|$)/], - ["fxios", /FxiOS\/([0-9\.]+)/], - ["opera-mini", /Opera Mini.*Version\/([0-9\.]+)/], - ["opera", /Opera\/([0-9\.]+)(?:\s|$)/], - ["opera", /OPR\/([0-9\.]+)(:?\s|$)/], - ["pie", /^Microsoft Pocket Internet Explorer\/(\d+\.\d+)$/], - [ - "pie", - /^Mozilla\/\d\.\d+\s\(compatible;\s(?:MSP?IE|MSInternet Explorer) (\d+\.\d+);.*Windows CE.*\)$/, - ], - ["netfront", /^Mozilla\/\d\.\d+.*NetFront\/(\d.\d)/], - ["ie", /Trident\/7\.0.*rv\:([0-9\.]+).*\).*Gecko$/], - ["ie", /MSIE\s([0-9\.]+);.*Trident\/[4-7].0/], - ["ie", /MSIE\s(7\.0)/], - ["bb10", /BB10;\sTouch.*Version\/([0-9\.]+)/], - ["android", /Android\s([0-9\.]+)/], - ["ios", /Version\/([0-9\._]+).*Mobile.*Safari.*/], - ["safari", /Version\/([0-9\._]+).*Safari/], - ["facebook", /FB[AS]V\/([0-9\.]+)/], - ["instagram", /Instagram\s([0-9\.]+)/], - ["ios-webview", /AppleWebKit\/([0-9\.]+).*Mobile/], - ["ios-webview", /AppleWebKit\/([0-9\.]+).*Gecko\)$/], - ["curl", /^curl\/([0-9\.]+)$/], - ["searchbot", SEARCHBOX_UA_REGEX], -]; -const operatingSystemRules: OperatingSystemRule[] = [ - ["iOS", /iP(hone|od|ad)/], - ["Android OS", /Android/], - ["BlackBerry OS", /BlackBerry|BB10/], - ["Windows Mobile", /IEMobile/], - ["Amazon OS", /Kindle/], - ["Windows 3.11", /Win16/], - ["Windows 95", /(Windows 95)|(Win95)|(Windows_95)/], - ["Windows 98", /(Windows 98)|(Win98)/], - ["Windows 2000", /(Windows NT 5.0)|(Windows 2000)/], - ["Windows XP", /(Windows NT 5.1)|(Windows XP)/], - ["Windows Server 2003", /(Windows NT 5.2)/], - ["Windows Vista", /(Windows NT 6.0)/], - ["Windows 7", /(Windows NT 6.1)/], - ["Windows 8", /(Windows NT 6.2)/], - ["Windows 8.1", /(Windows NT 6.3)/], - ["Windows 10", /(Windows NT 10.0)/], - ["Windows ME", /Windows ME/], - ["Windows CE", /Windows CE|WinCE|Microsoft Pocket Internet Explorer/], - ["Open BSD", /OpenBSD/], - ["Sun OS", /SunOS/], - ["Chrome OS", /CrOS/], - ["Linux", /(Linux)|(X11)/], - ["Mac OS", /(Mac_PowerPC)|(Macintosh)/], - ["QNX", /QNX/], - ["BeOS", /BeOS/], - ["OS/2", /OS\/2/], -]; - -export function detect( - userAgent?: string, -): - | BrowserInfo - | SearchBotDeviceInfo - | BotInfo - | NodeInfo - | ReactNativeInfo - | null { - if (!!userAgent) { - return parseUserAgent(userAgent); - } - - if ( - typeof document === "undefined" && - typeof navigator !== "undefined" && - navigator.product === "ReactNative" - ) { - return new ReactNativeInfo(); - } - - if (typeof navigator !== "undefined") { - return parseUserAgent(navigator.userAgent); - } - - return getNodeVersion(); -} - -function matchUserAgent(ua: string): UserAgentMatch { - // opted for using reduce here rather than Array#first with a regex.test call - // this is primarily because using the reduce we only perform the regex - // execution once rather than once for the test and for the exec again below - // probably something that needs to be benchmarked though - return ( - ua !== "" && - userAgentRules.reduce( - (matched: UserAgentMatch, [browser, regex]) => { - if (matched) { - return matched; - } - - const uaMatch = regex.exec(ua); - return !!uaMatch && [browser, uaMatch]; - }, - false, - ) - ); -} - -export function browserName(ua: string): Browser | null { - const data = matchUserAgent(ua); - return data ? data[0] : null; -} - -export function parseUserAgent( - ua: string, -): BrowserInfo | SearchBotDeviceInfo | BotInfo | null { - const matchedRule: UserAgentMatch = matchUserAgent(ua); - - if (!matchedRule) { - return null; - } - - const [name, match] = matchedRule; - if (name === "searchbot") { - return new BotInfo(); - } - // Do not use RegExp for split operation as some browser do not support it (See: http://blog.stevenlevithan.com/archives/cross-browser-split) - let versionParts = - match[1] && match[1].split(".").join("_").split("_").slice(0, 3); - if (versionParts) { - if (versionParts.length < REQUIRED_VERSION_PARTS) { - versionParts = [ - ...versionParts, - ...createVersionParts(REQUIRED_VERSION_PARTS - versionParts.length), - ]; - } - } else { - versionParts = []; - } - - const version = versionParts.join("."); - const os = detectOS(ua); - const searchBotMatch = SEARCHBOT_OS_REGEX.exec(ua); - - if (searchBotMatch && searchBotMatch[1]) { - return new SearchBotDeviceInfo(name, version, os, searchBotMatch[1]); - } - - return new BrowserInfo(name, version, os); -} - -export function detectOS(ua: string): OperatingSystem | null { - for (let ii = 0, count = operatingSystemRules.length; ii < count; ii++) { - const [os, regex] = operatingSystemRules[ii]; - const match = regex.exec(ua); - if (match) { - return os; - } - } - - return null; -} - -export function getNodeVersion(): NodeInfo | null { - const isNode = typeof process !== "undefined" && process.version; - return isNode ? new NodeInfo(process.version.slice(1)) : null; -} - -function createVersionParts(count: number): string[] { - const output = []; - for (let ii = 0; ii < count; ii++) { - output.push("0"); - } - - return output; -} From 68964289b6191634e4b683ddf088c42e8b7f30df Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Mon, 29 Jan 2024 18:03:52 -0500 Subject: [PATCH 08/18] report --- .../react-native/src/core/storage/uploader.ts | 31 ++++++++++++------- packages/sdk/src/evm/constants/urls.ts | 18 +++++------ .../core/downloaders/storage-downloader.ts | 13 +++----- .../src/core/uploaders/ipfs-uploader.ts | 13 +++----- .../smart-wallet/lib/http-rpc-client.ts | 17 +++------- 5 files changed, 38 insertions(+), 54 deletions(-) diff --git a/packages/react-native/src/core/storage/uploader.ts b/packages/react-native/src/core/storage/uploader.ts index b5f123fc564..c93e2b8fa3c 100644 --- a/packages/react-native/src/core/storage/uploader.ts +++ b/packages/react-native/src/core/storage/uploader.ts @@ -5,7 +5,6 @@ import { TW_UPLOAD_SERVER_URL, } from "@thirdweb-dev/storage"; import { IpfsUploaderOptions, UploadDataValue } from "./types"; -import { appBundleId, packageVersion } from "../../evm/utils/version"; import { BUNDLE_ID_HEADER } from "../../evm/constants/headers"; const METADATA_NAME = "Storage React Native SDK"; @@ -38,9 +37,6 @@ export class IpfsUploader implements IStorageUploader { keyvalues: { ...options?.metadata }, }; - const { version, name: packageName } = packageVersion; - const platform = "react-native"; - if ("uri" in data[0] && "type" in data[0] && "name" in data[0]) { // then it's an array of files return new Promise(async (resolve, reject) => { @@ -140,14 +136,24 @@ export class IpfsUploader implements IStorageUploader { }); xhr.open("POST", `${TW_UPLOAD_SERVER_URL}/ipfs/upload`); - xhr.setRequestHeader(BUNDLE_ID_HEADER, appBundleId || ""); // only empty on web + xhr.setRequestHeader( + BUNDLE_ID_HEADER, + (globalThis as any).APP_BUNDLE_ID, + ); // only empty on web if (this.clientId) { xhr.setRequestHeader("x-client-id", this.clientId); } - xhr.setRequestHeader("x-sdk-version", version); - xhr.setRequestHeader("x-sdk-name", packageName); - xhr.setRequestHeader("x-sdk-platform", platform); + xhr.setRequestHeader( + "x-sdk-version", + (globalThis as any).X_SDK_VERSION, + ); + xhr.setRequestHeader("x-sdk-os", (globalThis as any).X_SDK_OS); + xhr.setRequestHeader("x-sdk-name", (globalThis as any).X_SDK_NAME); + xhr.setRequestHeader( + "x-sdk-platform", + (globalThis as any).X_SDK_PLATFORM, + ); xhr.send(form); }); @@ -165,12 +171,13 @@ export class IpfsUploader implements IStorageUploader { { method: "POST", headers: { - [BUNDLE_ID_HEADER]: appBundleId || "", // only empty on web + [BUNDLE_ID_HEADER]: (globalThis as any).APP_BUNDLE_ID, ...(this.clientId ? { "x-client-id": this.clientId } : {}), "Content-Type": "application/json", - "x-sdk-version": version, - "x-sdk-name": packageName, - "x-sdk-platform": platform, + "x-sdk-version": (globalThis as any).X_SDK_VERSION, + "x-sdk-name": (globalThis as any).X_SDK_NAME, + "x-sdk-platform": (globalThis as any).X_SDK_PLATFORM, + "x-sdk-os": (globalThis as any).X_SDK_OS, }, body: fetchBody, }, diff --git a/packages/sdk/src/evm/constants/urls.ts b/packages/sdk/src/evm/constants/urls.ts index c5c3d7fc8a8..9c030c76b7f 100644 --- a/packages/sdk/src/evm/constants/urls.ts +++ b/packages/sdk/src/evm/constants/urls.ts @@ -260,15 +260,10 @@ export function getProviderFromRpcUrl( rpcUrl = rpcUrl + (bundleId ? `?bundleId=${bundleId}` : ""); } - headers["x-sdk-version"] = pkg.version; - headers["x-sdk-name"] = pkg.name; - headers["x-sdk-platform"] = bundleId - ? "react-native" - : isBrowser() - ? (window as any).bridge !== undefined - ? "webGL" - : "browser" - : "node"; + headers["x-sdk-version"] = (globalThis as any).X_SDK_VERSION; + headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME; + headers["x-sdk-platform"] = (globalThis as any).X_SDK_PLATFORM; + headers["x-sdk-os"] = (globalThis as any).X_SDK_OS; } const match = rpcUrl.match(/^(ws|http)s?:/i); // Try the JSON batch provider if available @@ -284,7 +279,7 @@ export function getProviderFromRpcUrl( if (existingProvider) { return existingProvider; } - + // TODO: remove below `skipFetchSetup` logic when ethers.js v6 support arrives let _skipFetchSetup = false; if ( @@ -292,7 +287,8 @@ export function getProviderFromRpcUrl( "TW_SKIP_FETCH_SETUP" in globalThis && typeof (globalThis as any).TW_SKIP_FETCH_SETUP === "boolean" ) { - _skipFetchSetup = (globalThis as any).TW_SKIP_FETCH_SETUP as boolean; + _skipFetchSetup = (globalThis as any) + .TW_SKIP_FETCH_SETUP as boolean; } // Otherwise, create a new provider on the specific network diff --git a/packages/storage/src/core/downloaders/storage-downloader.ts b/packages/storage/src/core/downloaders/storage-downloader.ts index 616bed225bd..0e6c5c468cb 100644 --- a/packages/storage/src/core/downloaders/storage-downloader.ts +++ b/packages/storage/src/core/downloaders/storage-downloader.ts @@ -126,15 +126,10 @@ export class StorageDownloader implements IStorageDownloader { headers["x-authorize-wallet"] = "true"; } - headers["x-sdk-version"] = pkg.version; - headers["x-sdk-name"] = pkg.name; - headers["x-sdk-platform"] = bundleId - ? "react-native" - : isBrowser() - ? (window as any).bridge !== undefined - ? "webGL" - : "browser" - : "node"; + headers["x-sdk-version"] = (globalThis as any).X_SDK_VERSION; + headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME; + headers["x-sdk-platform"] = (globalThis as any).X_SDK_PLATFORM; + headers["x-sdk-os"] = (globalThis as any).X_SDK_OS; } if (isTooManyRequests(resolvedUri)) { diff --git a/packages/storage/src/core/uploaders/ipfs-uploader.ts b/packages/storage/src/core/uploaders/ipfs-uploader.ts index 6be31b9ebf9..d36cfc7c80a 100644 --- a/packages/storage/src/core/uploaders/ipfs-uploader.ts +++ b/packages/storage/src/core/uploaders/ipfs-uploader.ts @@ -282,17 +282,12 @@ export class IpfsUploader implements IStorageUploader { xhr.setRequestHeader("x-bundle-id", bundleId); } - xhr.setRequestHeader("x-sdk-version", pkg.version); - xhr.setRequestHeader("x-sdk-name", pkg.name); + xhr.setRequestHeader("x-sdk-version", (globalThis as any).X_SDK_VERSION); + xhr.setRequestHeader("x-sdk-os", (globalThis as any).X_SDK_OS); + xhr.setRequestHeader("x-sdk-name", (globalThis as any).X_SDK_NAME); xhr.setRequestHeader( "x-sdk-platform", - bundleId - ? "react-native" - : isBrowser() - ? (window as any).bridge !== undefined - ? "webGL" - : "browser" - : "node", + (globalThis as any).X_SDK_PLATFORM, ); // if we have a authorization token on global context then add that to the headers, this is for the dashboard. diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/http-rpc-client.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/http-rpc-client.ts index b324c1d4ea7..feb5911fd39 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/http-rpc-client.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/http-rpc-client.ts @@ -1,13 +1,9 @@ import { providers, utils } from "ethers"; import { UserOperationStruct } from "@account-abstraction/contracts"; import { isTwUrl } from "../../../utils/url"; -import pkg from "../../../../../package.json"; import { hexlifyUserOp } from "./utils"; export const DEBUG = false; // TODO set as public flag -function isBrowser() { - return typeof window !== "undefined"; -} export class HttpRpcClient { private readonly userOpJsonRpcProvider: providers.JsonRpcProvider; @@ -69,15 +65,10 @@ export class HttpRpcClient { headers["x-authorize-wallet"] = "true"; } - headers["x-sdk-version"] = pkg.version; - headers["x-sdk-name"] = pkg.name; - headers["x-sdk-platform"] = bundleId - ? "react-native" - : isBrowser() - ? (window as any).bridge !== undefined - ? "webGL" - : "browser" - : "node"; + headers["x-sdk-version"] = (globalThis as any).X_SDK_VERSION; + headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME; + headers["x-sdk-platform"] = (globalThis as any).X_SDK_PLATFORM; + headers["x-sdk-os"] = (globalThis as any).X_SDK_OS; } this.userOpJsonRpcProvider = new providers.JsonRpcProvider( From 360d0322e2c1f8325adeb4694e43b3185d49e0ac Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Mon, 29 Jan 2024 18:24:28 -0500 Subject: [PATCH 09/18] add headers --- .../react-native/src/evm/constants/headers.ts | 5 ++++ .../embedded-wallet/embedded/auth.ts | 30 ++++++++++++------- .../embedded/helpers/api/fetchers.ts | 25 ++++++++++------ packages/sdk/src/evm/common/verification.ts | 6 ++-- .../core/classes/internal/contract-wrapper.ts | 1 + .../src/core/uploaders/ipfs-uploader.ts | 5 ++++ .../connectors/smart-wallet/lib/paymaster.ts | 5 ++++ packages/wallets/src/evm/utils/analytics.ts | 4 +++ 8 files changed, 59 insertions(+), 22 deletions(-) diff --git a/packages/react-native/src/evm/constants/headers.ts b/packages/react-native/src/evm/constants/headers.ts index 2db4c0c0e6e..885f71431c5 100644 --- a/packages/react-native/src/evm/constants/headers.ts +++ b/packages/react-native/src/evm/constants/headers.ts @@ -1 +1,6 @@ export const BUNDLE_ID_HEADER = "x-bundle-id"; + +export const X_SDK_VERSION_HEADER = "x-sdk-version"; +export const X_SDK_NAME_HEADER = "x-sdk-name"; +export const X_SDK_PLATFORM_HEADER = "x-sdk-platform"; +export const X_SDK_OS_HEADER = "x-sdk-os"; diff --git a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts index 38aded57990..d1a2698f5ac 100644 --- a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts +++ b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts @@ -44,9 +44,25 @@ import { appBundleId, reactNativePackageVersion, } from "../../../../utils/version"; -import { BUNDLE_ID_HEADER } from "../../../../constants/headers"; +import { + BUNDLE_ID_HEADER, + X_SDK_NAME_HEADER, + X_SDK_OS_HEADER, + X_SDK_PLATFORM_HEADER, + X_SDK_VERSION_HEADER, +} from "../../../../constants/headers"; import { ANALYTICS } from "./helpers/analytics"; +const HEADERS = { + [EWS_VERSION_HEADER]: (globalThis as any).X_SDK_VERSION, + [BUNDLE_ID_HEADER]: (globalThis as any).APP_BUNDLE_ID, + [THIRDWEB_SESSION_NONCE_HEADER]: ANALYTICS.nonce, + [X_SDK_NAME_HEADER]: (globalThis as any).X_SDK_NAME, + [X_SDK_OS_HEADER]: (globalThis as any).X_SDK_OS, + [X_SDK_PLATFORM_HEADER]: (globalThis as any).X_SDK_PLATFORM, + [X_SDK_VERSION_HEADER]: (globalThis as any).X_SDK_VERSION, +}; + export async function sendVerificationEmail(options: { email: string; clientId: string; @@ -195,9 +211,7 @@ export async function socialLogin(oauthOptions: OauthOption, clientId: string) { const resp = await fetch(headlessLoginLinkWithParams, { headers: { - [EWS_VERSION_HEADER]: reactNativePackageVersion, - [BUNDLE_ID_HEADER]: appBundleId, - [THIRDWEB_SESSION_NONCE_HEADER]: ANALYTICS.nonce, + ...HEADERS, }, }); @@ -278,9 +292,7 @@ export async function customJwt(authOptions: AuthOptions, clientId: string) { method: "POST", headers: { "Content-Type": "application/json", - [EWS_VERSION_HEADER]: reactNativePackageVersion, - [BUNDLE_ID_HEADER]: appBundleId, - [THIRDWEB_SESSION_NONCE_HEADER]: ANALYTICS.nonce, + ...HEADERS, }, body: JSON.stringify({ jwt: jwt, @@ -328,9 +340,7 @@ export async function authEndpoint( method: "POST", headers: { "Content-Type": "application/json", - [EWS_VERSION_HEADER]: reactNativePackageVersion, - [BUNDLE_ID_HEADER]: appBundleId, - [THIRDWEB_SESSION_NONCE_HEADER]: ANALYTICS.nonce, + ...HEADERS, }, body: JSON.stringify({ payload: payload, diff --git a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/api/fetchers.ts b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/api/fetchers.ts index 45e994378fd..d5107b15c26 100644 --- a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/api/fetchers.ts +++ b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/api/fetchers.ts @@ -24,16 +24,27 @@ import { appBundleId, reactNativePackageVersion, } from "../../../../../../utils/version"; -import { BUNDLE_ID_HEADER } from "../../../../../../constants/headers"; +import { + BUNDLE_ID_HEADER, + X_SDK_NAME_HEADER, + X_SDK_OS_HEADER, + X_SDK_PLATFORM_HEADER, + X_SDK_VERSION_HEADER, +} from "../../../../../../constants/headers"; import { ANALYTICS } from "../analytics"; const EMBEDDED_WALLET_TOKEN_HEADER = "embedded-wallet-token"; const PAPER_CLIENT_ID_HEADER = "x-thirdweb-client-id"; + const HEADERS = { "Content-Type": "application/json", - [BUNDLE_ID_HEADER]: appBundleId, - [EWS_VERSION_HEADER]: reactNativePackageVersion, + [EWS_VERSION_HEADER]: (globalThis as any).X_SDK_VERSION, + [BUNDLE_ID_HEADER]: (globalThis as any).APP_BUNDLE_ID, [THIRDWEB_SESSION_NONCE_HEADER]: ANALYTICS.nonce, + [X_SDK_NAME_HEADER]: (globalThis as any).X_SDK_NAME, + [X_SDK_OS_HEADER]: (globalThis as any).X_SDK_OS, + [X_SDK_PLATFORM_HEADER]: (globalThis as any).X_SDK_PLATFORM, + [X_SDK_VERSION_HEADER]: (globalThis as any).X_SDK_VERSION, }; export const verifyClientId = async (clientId: string) => { @@ -68,19 +79,15 @@ export const authFetchEmbeddedWalletUser = async ( Authorization: `Bearer ${EMBEDDED_WALLET_TOKEN_HEADER}:${ authTokenClient || "" }`, - [BUNDLE_ID_HEADER]: appBundleId, [PAPER_CLIENT_ID_HEADER]: clientId, - [EWS_VERSION_HEADER]: reactNativePackageVersion, - [THIRDWEB_SESSION_NONCE_HEADER]: ANALYTICS.nonce, + ...HEADERS, } : { Authorization: `Bearer ${EMBEDDED_WALLET_TOKEN_HEADER}:${ authTokenClient || "" }`, - [BUNDLE_ID_HEADER]: appBundleId, [PAPER_CLIENT_ID_HEADER]: clientId, - [EWS_VERSION_HEADER]: reactNativePackageVersion, - [THIRDWEB_SESSION_NONCE_HEADER]: ANALYTICS.nonce, + ...HEADERS, }; return fetch(url, params); }; diff --git a/packages/sdk/src/evm/common/verification.ts b/packages/sdk/src/evm/common/verification.ts index 4d611ca57bc..655fbf3cf50 100644 --- a/packages/sdk/src/evm/common/verification.ts +++ b/packages/sdk/src/evm/common/verification.ts @@ -215,6 +215,7 @@ export async function verify( }; const parameters = new URLSearchParams({ ...requestBody }); + // do we need to pass headers here? const result = await fetch(explorerAPIUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, @@ -474,9 +475,8 @@ async function fetchDeployBytecodeFromPublishedContractMetadata( getChainProvider("polygon", {}), ) as ContractPublisher; - const publishedMetadataUri = await contract.getPublishedUriFromCompilerUri( - compilerMetaUri, - ); + const publishedMetadataUri = + await contract.getPublishedUriFromCompilerUri(compilerMetaUri); if (publishedMetadataUri.length === 0) { throw Error( `Could not resolve published metadata URI from ${compilerMetaUri}`, diff --git a/packages/sdk/src/evm/core/classes/internal/contract-wrapper.ts b/packages/sdk/src/evm/core/classes/internal/contract-wrapper.ts index 344879b95f8..e1a4bea466e 100644 --- a/packages/sdk/src/evm/core/classes/internal/contract-wrapper.ts +++ b/packages/sdk/src/evm/core/classes/internal/contract-wrapper.ts @@ -654,6 +654,7 @@ export class ContractWrapper< const request = await this.enginePrepareRequest(transaction); + // should we pass headers here? const res = await fetch(this.options.gasless.engine.relayerUrl, { ...request, headers: { diff --git a/packages/storage/src/core/uploaders/ipfs-uploader.ts b/packages/storage/src/core/uploaders/ipfs-uploader.ts index d36cfc7c80a..4e0be773d5c 100644 --- a/packages/storage/src/core/uploaders/ipfs-uploader.ts +++ b/packages/storage/src/core/uploaders/ipfs-uploader.ts @@ -364,6 +364,11 @@ export class IpfsUploader implements IStorageUploader { headers["x-authorize-wallet"] = "true"; } + headers["x-sdk-version"] = (globalThis as any).X_SDK_VERSION; + headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME; + headers["x-sdk-platform"] = (globalThis as any).X_SDK_PLATFORM; + headers["x-sdk-os"] = (globalThis as any).X_SDK_OS; + const res = await fetch(`${this.uploadServerUrl}/ipfs/upload`, { method: "POST", headers: { diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts index c4629537fbf..c66de743cfc 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts @@ -75,6 +75,11 @@ class VerifyingPaymasterAPI extends PaymasterAPI { }`; headers["x-authorize-wallet"] = "true"; } + + headers["x-sdk-version"] = (globalThis as any).X_SDK_VERSION; + headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME; + headers["x-sdk-platform"] = (globalThis as any).X_SDK_PLATFORM; + headers["x-sdk-os"] = (globalThis as any).X_SDK_OS; } // Ask the paymaster to sign the transaction and return a valid paymasterAndData value. diff --git a/packages/wallets/src/evm/utils/analytics.ts b/packages/wallets/src/evm/utils/analytics.ts index b959ab0f11c..c8fab771930 100644 --- a/packages/wallets/src/evm/utils/analytics.ts +++ b/packages/wallets/src/evm/utils/analytics.ts @@ -25,6 +25,10 @@ export function track(args: { headers: { "Content-Type": "application/json", "x-client-id": clientId, + "x-sdk-version": (globalThis as any).X_SDK_VERSION, + "x-sdk-name": (globalThis as any).X_SDK_NAME, + "x-sdk-platform": (globalThis as any).X_SDK_PLATFORM, + "x-sdk-os": (globalThis as any).X_SDK_OS, }, body: JSON.stringify(body), }); From 5a93c340cf3e0cf60933e25bf9e533bfd48a9602 Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Mon, 29 Jan 2024 18:28:18 -0500 Subject: [PATCH 10/18] lint --- .../evm/wallets/connectors/embedded-wallet/embedded/auth.ts | 4 ---- .../embedded-wallet/embedded/helpers/api/fetchers.ts | 4 ---- 2 files changed, 8 deletions(-) diff --git a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts index d1a2698f5ac..b07c56a6b10 100644 --- a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts +++ b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts @@ -40,10 +40,6 @@ import { } from "../types"; import { InAppBrowser } from "react-native-inappbrowser-reborn"; import { createErrorMessage } from "./helpers/errors"; -import { - appBundleId, - reactNativePackageVersion, -} from "../../../../utils/version"; import { BUNDLE_ID_HEADER, X_SDK_NAME_HEADER, diff --git a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/api/fetchers.ts b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/api/fetchers.ts index d5107b15c26..499f5b62c0d 100644 --- a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/api/fetchers.ts +++ b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/api/fetchers.ts @@ -20,10 +20,6 @@ import { VerifiedTokenResponse, } from "../../../types"; import { createErrorMessage } from "../errors"; -import { - appBundleId, - reactNativePackageVersion, -} from "../../../../../../utils/version"; import { BUNDLE_ID_HEADER, X_SDK_NAME_HEADER, From 5b36c967fbe8a7975033d215eefef91174b14b17 Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Tue, 30 Jan 2024 16:49:02 -0500 Subject: [PATCH 11/18] set --- packages/sdk/src/core/utils/detect-browser.ts | 14 +- packages/sdk/src/evm/common/verification.ts | 1 - packages/sdk/src/evm/constants/urls.ts | 18 +- .../core/classes/internal/contract-wrapper.ts | 1 - packages/sdk/src/evm/core/sdk.ts | 17 - .../core/downloaders/storage-downloader.ts | 18 +- .../src/core/uploaders/ipfs-uploader.ts | 38 +- packages/storage/src/utils/detect-browser.ts | 338 ++++++++++++++++++ packages/storage/src/utils/os.ts | 13 + .../unity-js-bridge/src/thirdweb-bridge.ts | 9 +- .../connectors/smart-wallet/lib/paymaster.ts | 19 +- packages/wallets/src/evm/utils/analytics.ts | 17 +- .../src/evm/utils/os/detect-browser.ts | 338 ++++++++++++++++++ packages/wallets/src/evm/utils/os/os.ts | 13 + 14 files changed, 801 insertions(+), 53 deletions(-) create mode 100644 packages/storage/src/utils/detect-browser.ts create mode 100644 packages/storage/src/utils/os.ts create mode 100644 packages/wallets/src/evm/utils/os/detect-browser.ts create mode 100644 packages/wallets/src/evm/utils/os/os.ts diff --git a/packages/sdk/src/core/utils/detect-browser.ts b/packages/sdk/src/core/utils/detect-browser.ts index 70fca7fd61a..92bbac9c672 100644 --- a/packages/sdk/src/core/utils/detect-browser.ts +++ b/packages/sdk/src/core/utils/detect-browser.ts @@ -38,7 +38,7 @@ export class NodeInfo implements DetectedInfo<"node", "node", NodeJS.Platform, string> { public readonly type = "node"; - public readonly name: "node" = "node"; + public readonly name = "node"; public readonly os: NodeJS.Platform = process.platform; constructor(public readonly version: string) {} @@ -59,8 +59,8 @@ export class SearchBotDeviceInfo export class BotInfo implements DetectedInfo<"bot", "bot", null, null> { public readonly type = "bot"; - public readonly bot: true = true; // NOTE: deprecated test name instead - public readonly name: "bot" = "bot"; + public readonly bot = true; // NOTE: deprecated test name instead + public readonly name = "bot"; public readonly version: null = null; public readonly os: null = null; } @@ -69,7 +69,7 @@ export class ReactNativeInfo implements DetectedInfo<"react-native", "react-native", null, null> { public readonly type = "react-native"; - public readonly name: "react-native" = "react-native"; + public readonly name = "react-native"; public readonly version: null = null; public readonly os: null = null; } @@ -309,7 +309,11 @@ export function parseUserAgent( export function detectOS(ua: string): OperatingSystem | null { for (let ii = 0, count = operatingSystemRules.length; ii < count; ii++) { - const [os, regex] = operatingSystemRules[ii]; + const result = operatingSystemRules[ii]; + if (!result) { + continue; + } + const [os, regex] = result; const match = regex.exec(ua); if (match) { return os; diff --git a/packages/sdk/src/evm/common/verification.ts b/packages/sdk/src/evm/common/verification.ts index 655fbf3cf50..48c5173668c 100644 --- a/packages/sdk/src/evm/common/verification.ts +++ b/packages/sdk/src/evm/common/verification.ts @@ -215,7 +215,6 @@ export async function verify( }; const parameters = new URLSearchParams({ ...requestBody }); - // do we need to pass headers here? const result = await fetch(explorerAPIUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, diff --git a/packages/sdk/src/evm/constants/urls.ts b/packages/sdk/src/evm/constants/urls.ts index 9c030c76b7f..94912194847 100644 --- a/packages/sdk/src/evm/constants/urls.ts +++ b/packages/sdk/src/evm/constants/urls.ts @@ -11,6 +11,7 @@ import type { Signer } from "ethers"; import pkg from "../../../package.json"; import { isBrowser } from "@thirdweb-dev/storage"; import { sha256HexSync } from "@thirdweb-dev/crypto"; +import { getOperatingSystem } from "../../core/utils/os"; /** * @internal @@ -260,10 +261,19 @@ export function getProviderFromRpcUrl( rpcUrl = rpcUrl + (bundleId ? `?bundleId=${bundleId}` : ""); } - headers["x-sdk-version"] = (globalThis as any).X_SDK_VERSION; - headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME; - headers["x-sdk-platform"] = (globalThis as any).X_SDK_PLATFORM; - headers["x-sdk-os"] = (globalThis as any).X_SDK_OS; + headers["x-sdk-version"] = + (globalThis as any).X_SDK_VERSION || pkg.version; + headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME || pkg.name; + headers["x-sdk-platform"] = + (globalThis as any).X_SDK_PLATFORM || + (typeof navigator !== "undefined" && + navigator.product === "ReactNative") + ? "mobile" + : typeof window !== "undefined" + ? "browser" + : "node"; + headers["x-sdk-os"] = + (globalThis as any).X_SDK_OS || getOperatingSystem(); } const match = rpcUrl.match(/^(ws|http)s?:/i); // Try the JSON batch provider if available diff --git a/packages/sdk/src/evm/core/classes/internal/contract-wrapper.ts b/packages/sdk/src/evm/core/classes/internal/contract-wrapper.ts index e1a4bea466e..344879b95f8 100644 --- a/packages/sdk/src/evm/core/classes/internal/contract-wrapper.ts +++ b/packages/sdk/src/evm/core/classes/internal/contract-wrapper.ts @@ -654,7 +654,6 @@ export class ContractWrapper< const request = await this.enginePrepareRequest(transaction); - // should we pass headers here? const res = await fetch(this.options.gasless.engine.relayerUrl, { ...request, headers: { diff --git a/packages/sdk/src/evm/core/sdk.ts b/packages/sdk/src/evm/core/sdk.ts index 1806f50da8b..d46b78d5a8b 100644 --- a/packages/sdk/src/evm/core/sdk.ts +++ b/packages/sdk/src/evm/core/sdk.ts @@ -117,8 +117,6 @@ import { VoteContractDeployMetadata, } from "../types/deploy/deploy-metadata"; import { DeployMetadata, DeployOptions } from "../types/deploy/deploy-options"; -import pkg from "../../../package.json"; -import { getOperatingSystem } from "../../core/utils/os"; /** * The main entry point for the thirdweb SDK @@ -311,21 +309,6 @@ export class ThirdwebSDK extends RPCConnectionHandler { this.options, this.storageHandler, ); - - if ( - typeof globalThis !== "undefined" && - (globalThis as any).X_SDK_NAME === undefined - ) { - (globalThis as any).X_SDK_NAME = pkg.name; - (globalThis as any).X_SDK_PLATFORM = - typeof navigator !== "undefined" && navigator.product === "ReactNative" - ? "mobile" - : typeof window !== "undefined" - ? "browser" - : "node"; - (globalThis as any).X_SDK_VERSION = pkg.version; - (globalThis as any).X_SDK_OS = getOperatingSystem(); - } } get auth() { diff --git a/packages/storage/src/core/downloaders/storage-downloader.ts b/packages/storage/src/core/downloaders/storage-downloader.ts index 0e6c5c468cb..8e62e5952e2 100644 --- a/packages/storage/src/core/downloaders/storage-downloader.ts +++ b/packages/storage/src/core/downloaders/storage-downloader.ts @@ -7,6 +7,7 @@ import { SingleDownloadOptions, } from "../../types"; import pkg from "../../../package.json"; +import { getOperatingSystem } from "../../utils/os"; /** * Default downloader used - handles downloading from all schemes specified in the gateway URLs configuration. @@ -126,10 +127,19 @@ export class StorageDownloader implements IStorageDownloader { headers["x-authorize-wallet"] = "true"; } - headers["x-sdk-version"] = (globalThis as any).X_SDK_VERSION; - headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME; - headers["x-sdk-platform"] = (globalThis as any).X_SDK_PLATFORM; - headers["x-sdk-os"] = (globalThis as any).X_SDK_OS; + headers["x-sdk-version"] = + (globalThis as any).X_SDK_VERSION || pkg.version; + headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME || pkg.name; + headers["x-sdk-platform"] = + (globalThis as any).X_SDK_PLATFORM || + (typeof navigator !== "undefined" && + navigator.product === "ReactNative") + ? "mobile" + : typeof window !== "undefined" + ? "browser" + : "node"; + headers["x-sdk-os"] = + (globalThis as any).X_SDK_OS || getOperatingSystem(); } if (isTooManyRequests(resolvedUri)) { diff --git a/packages/storage/src/core/uploaders/ipfs-uploader.ts b/packages/storage/src/core/uploaders/ipfs-uploader.ts index 4e0be773d5c..cc5d275d211 100644 --- a/packages/storage/src/core/uploaders/ipfs-uploader.ts +++ b/packages/storage/src/core/uploaders/ipfs-uploader.ts @@ -13,6 +13,7 @@ import { } from "../../types"; import FormData from "form-data"; import pkg from "../../../package.json"; +import { getOperatingSystem } from "../../utils/os"; /** * Default uploader used - handles uploading arbitrary data to IPFS @@ -282,12 +283,27 @@ export class IpfsUploader implements IStorageUploader { xhr.setRequestHeader("x-bundle-id", bundleId); } - xhr.setRequestHeader("x-sdk-version", (globalThis as any).X_SDK_VERSION); - xhr.setRequestHeader("x-sdk-os", (globalThis as any).X_SDK_OS); - xhr.setRequestHeader("x-sdk-name", (globalThis as any).X_SDK_NAME); + xhr.setRequestHeader( + "x-sdk-version", + (globalThis as any).X_SDK_VERSION || pkg.version, + ); + xhr.setRequestHeader( + "x-sdk-os", + (globalThis as any).X_SDK_OS || getOperatingSystem(), + ); + xhr.setRequestHeader( + "x-sdk-name", + (globalThis as any).X_SDK_NAME || pkg.name, + ); xhr.setRequestHeader( "x-sdk-platform", - (globalThis as any).X_SDK_PLATFORM, + (globalThis as any).X_SDK_PLATFORM || + (typeof navigator !== "undefined" && + navigator.product === "ReactNative") + ? "mobile" + : typeof window !== "undefined" + ? "browser" + : "node", ); // if we have a authorization token on global context then add that to the headers, this is for the dashboard. @@ -364,10 +380,16 @@ export class IpfsUploader implements IStorageUploader { headers["x-authorize-wallet"] = "true"; } - headers["x-sdk-version"] = (globalThis as any).X_SDK_VERSION; - headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME; - headers["x-sdk-platform"] = (globalThis as any).X_SDK_PLATFORM; - headers["x-sdk-os"] = (globalThis as any).X_SDK_OS; + headers["x-sdk-version"] = (globalThis as any).X_SDK_VERSION || pkg.version; + headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME || pkg.name; + headers["x-sdk-platform"] = + (globalThis as any).X_SDK_PLATFORM || + (typeof navigator !== "undefined" && navigator.product === "ReactNative") + ? "mobile" + : typeof window !== "undefined" + ? "browser" + : "node"; + headers["x-sdk-os"] = (globalThis as any).X_SDK_OS || getOperatingSystem(); const res = await fetch(`${this.uploadServerUrl}/ipfs/upload`, { method: "POST", diff --git a/packages/storage/src/utils/detect-browser.ts b/packages/storage/src/utils/detect-browser.ts new file mode 100644 index 00000000000..92bbac9c672 --- /dev/null +++ b/packages/storage/src/utils/detect-browser.ts @@ -0,0 +1,338 @@ +/** + * @internal + * + * The code below comes from the package https://github.com/DamonOehlman/detect-browser + */ + +export type DetectedInfoType = + | "browser" + | "node" + | "bot-device" + | "bot" + | "react-native"; + +interface DetectedInfo< + T extends DetectedInfoType, + N extends string, + O, + V = null, +> { + readonly type: T; + readonly name: N; + readonly version: V; + readonly os: O; +} + +export class BrowserInfo + implements DetectedInfo<"browser", Browser, OperatingSystem | null, string> +{ + public readonly type = "browser"; + constructor( + public readonly name: Browser, + public readonly version: string, + public readonly os: OperatingSystem | null, + ) {} +} + +export class NodeInfo + implements DetectedInfo<"node", "node", NodeJS.Platform, string> +{ + public readonly type = "node"; + public readonly name = "node"; + public readonly os: NodeJS.Platform = process.platform; + + constructor(public readonly version: string) {} +} + +export class SearchBotDeviceInfo + implements + DetectedInfo<"bot-device", Browser, OperatingSystem | null, string> +{ + public readonly type = "bot-device"; + constructor( + public readonly name: Browser, + public readonly version: string, + public readonly os: OperatingSystem | null, + public readonly bot: string, + ) {} +} + +export class BotInfo implements DetectedInfo<"bot", "bot", null, null> { + public readonly type = "bot"; + public readonly bot = true; // NOTE: deprecated test name instead + public readonly name = "bot"; + public readonly version: null = null; + public readonly os: null = null; +} + +export class ReactNativeInfo + implements DetectedInfo<"react-native", "react-native", null, null> +{ + public readonly type = "react-native"; + public readonly name = "react-native"; + public readonly version: null = null; + public readonly os: null = null; +} + +export type Browser = + | "aol" + | "edge" + | "edge-ios" + | "yandexbrowser" + | "kakaotalk" + | "samsung" + | "silk" + | "miui" + | "beaker" + | "edge-chromium" + | "chrome" + | "chromium-webview" + | "phantomjs" + | "crios" + | "firefox" + | "fxios" + | "opera-mini" + | "opera" + | "pie" + | "netfront" + | "ie" + | "bb10" + | "android" + | "ios" + | "safari" + | "facebook" + | "instagram" + | "ios-webview" + | "curl" + | "searchbot"; +export type OperatingSystem = + | "iOS" + | "Android OS" + | "BlackBerry OS" + | "Windows Mobile" + | "Amazon OS" + | "Windows 3.11" + | "Windows 95" + | "Windows 98" + | "Windows 2000" + | "Windows XP" + | "Windows Server 2003" + | "Windows Vista" + | "Windows 7" + | "Windows 8" + | "Windows 8.1" + | "Windows 10" + | "Windows ME" + | "Windows CE" + | "Open BSD" + | "Sun OS" + | "Linux" + | "Mac OS" + | "QNX" + | "BeOS" + | "OS/2" + | "Chrome OS"; +type UserAgentRule = [Browser, RegExp]; +type UserAgentMatch = [Browser, RegExpExecArray] | false; +type OperatingSystemRule = [OperatingSystem, RegExp]; + +// tslint:disable-next-line:max-line-length +const SEARCHBOX_UA_REGEX = + /alexa|bot|crawl(er|ing)|facebookexternalhit|feedburner|google web preview|nagios|postrank|pingdom|slurp|spider|yahoo!|yandex/; +const SEARCHBOT_OS_REGEX = + /(nuhk|curl|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask\ Jeeves\/Teoma|ia_archiver)/; +const REQUIRED_VERSION_PARTS = 3; + +const userAgentRules: UserAgentRule[] = [ + ["aol", /AOLShield\/([0-9\._]+)/], + ["edge", /Edge\/([0-9\._]+)/], + ["edge-ios", /EdgiOS\/([0-9\._]+)/], + ["yandexbrowser", /YaBrowser\/([0-9\._]+)/], + ["kakaotalk", /KAKAOTALK\s([0-9\.]+)/], + ["samsung", /SamsungBrowser\/([0-9\.]+)/], + ["silk", /\bSilk\/([0-9._-]+)\b/], + ["miui", /MiuiBrowser\/([0-9\.]+)$/], + ["beaker", /BeakerBrowser\/([0-9\.]+)/], + ["edge-chromium", /EdgA?\/([0-9\.]+)/], + [ + "chromium-webview", + /(?!Chrom.*OPR)wv\).*Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/, + ], + ["chrome", /(?!Chrom.*OPR)Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/], + ["phantomjs", /PhantomJS\/([0-9\.]+)(:?\s|$)/], + ["crios", /CriOS\/([0-9\.]+)(:?\s|$)/], + ["firefox", /Firefox\/([0-9\.]+)(?:\s|$)/], + ["fxios", /FxiOS\/([0-9\.]+)/], + ["opera-mini", /Opera Mini.*Version\/([0-9\.]+)/], + ["opera", /Opera\/([0-9\.]+)(?:\s|$)/], + ["opera", /OPR\/([0-9\.]+)(:?\s|$)/], + ["pie", /^Microsoft Pocket Internet Explorer\/(\d+\.\d+)$/], + [ + "pie", + /^Mozilla\/\d\.\d+\s\(compatible;\s(?:MSP?IE|MSInternet Explorer) (\d+\.\d+);.*Windows CE.*\)$/, + ], + ["netfront", /^Mozilla\/\d\.\d+.*NetFront\/(\d.\d)/], + ["ie", /Trident\/7\.0.*rv\:([0-9\.]+).*\).*Gecko$/], + ["ie", /MSIE\s([0-9\.]+);.*Trident\/[4-7].0/], + ["ie", /MSIE\s(7\.0)/], + ["bb10", /BB10;\sTouch.*Version\/([0-9\.]+)/], + ["android", /Android\s([0-9\.]+)/], + ["ios", /Version\/([0-9\._]+).*Mobile.*Safari.*/], + ["safari", /Version\/([0-9\._]+).*Safari/], + ["facebook", /FB[AS]V\/([0-9\.]+)/], + ["instagram", /Instagram\s([0-9\.]+)/], + ["ios-webview", /AppleWebKit\/([0-9\.]+).*Mobile/], + ["ios-webview", /AppleWebKit\/([0-9\.]+).*Gecko\)$/], + ["curl", /^curl\/([0-9\.]+)$/], + ["searchbot", SEARCHBOX_UA_REGEX], +]; +const operatingSystemRules: OperatingSystemRule[] = [ + ["iOS", /iP(hone|od|ad)/], + ["Android OS", /Android/], + ["BlackBerry OS", /BlackBerry|BB10/], + ["Windows Mobile", /IEMobile/], + ["Amazon OS", /Kindle/], + ["Windows 3.11", /Win16/], + ["Windows 95", /(Windows 95)|(Win95)|(Windows_95)/], + ["Windows 98", /(Windows 98)|(Win98)/], + ["Windows 2000", /(Windows NT 5.0)|(Windows 2000)/], + ["Windows XP", /(Windows NT 5.1)|(Windows XP)/], + ["Windows Server 2003", /(Windows NT 5.2)/], + ["Windows Vista", /(Windows NT 6.0)/], + ["Windows 7", /(Windows NT 6.1)/], + ["Windows 8", /(Windows NT 6.2)/], + ["Windows 8.1", /(Windows NT 6.3)/], + ["Windows 10", /(Windows NT 10.0)/], + ["Windows ME", /Windows ME/], + ["Windows CE", /Windows CE|WinCE|Microsoft Pocket Internet Explorer/], + ["Open BSD", /OpenBSD/], + ["Sun OS", /SunOS/], + ["Chrome OS", /CrOS/], + ["Linux", /(Linux)|(X11)/], + ["Mac OS", /(Mac_PowerPC)|(Macintosh)/], + ["QNX", /QNX/], + ["BeOS", /BeOS/], + ["OS/2", /OS\/2/], +]; + +export function detect( + userAgent?: string, +): + | BrowserInfo + | SearchBotDeviceInfo + | BotInfo + | NodeInfo + | ReactNativeInfo + | null { + if (!!userAgent) { + return parseUserAgent(userAgent); + } + + if ( + typeof document === "undefined" && + typeof navigator !== "undefined" && + navigator.product === "ReactNative" + ) { + return new ReactNativeInfo(); + } + + if (typeof navigator !== "undefined") { + return parseUserAgent(navigator.userAgent); + } + + return getNodeVersion(); +} + +function matchUserAgent(ua: string): UserAgentMatch { + // opted for using reduce here rather than Array#first with a regex.test call + // this is primarily because using the reduce we only perform the regex + // execution once rather than once for the test and for the exec again below + // probably something that needs to be benchmarked though + return ( + ua !== "" && + userAgentRules.reduce( + (matched: UserAgentMatch, [browser, regex]) => { + if (matched) { + return matched; + } + + const uaMatch = regex.exec(ua); + return !!uaMatch && [browser, uaMatch]; + }, + false, + ) + ); +} + +export function browserName(ua: string): Browser | null { + const data = matchUserAgent(ua); + return data ? data[0] : null; +} + +export function parseUserAgent( + ua: string, +): BrowserInfo | SearchBotDeviceInfo | BotInfo | null { + const matchedRule: UserAgentMatch = matchUserAgent(ua); + + if (!matchedRule) { + return null; + } + + const [name, match] = matchedRule; + if (name === "searchbot") { + return new BotInfo(); + } + // Do not use RegExp for split operation as some browser do not support it (See: http://blog.stevenlevithan.com/archives/cross-browser-split) + let versionParts = + match[1] && match[1].split(".").join("_").split("_").slice(0, 3); + if (versionParts) { + if (versionParts.length < REQUIRED_VERSION_PARTS) { + versionParts = [ + ...versionParts, + ...createVersionParts(REQUIRED_VERSION_PARTS - versionParts.length), + ]; + } + } else { + versionParts = []; + } + + const version = versionParts.join("."); + const os = detectOS(ua); + const searchBotMatch = SEARCHBOT_OS_REGEX.exec(ua); + + if (searchBotMatch && searchBotMatch[1]) { + return new SearchBotDeviceInfo(name, version, os, searchBotMatch[1]); + } + + return new BrowserInfo(name, version, os); +} + +export function detectOS(ua: string): OperatingSystem | null { + for (let ii = 0, count = operatingSystemRules.length; ii < count; ii++) { + const result = operatingSystemRules[ii]; + if (!result) { + continue; + } + const [os, regex] = result; + const match = regex.exec(ua); + if (match) { + return os; + } + } + + return null; +} + +export function getNodeVersion(): NodeInfo | null { + const isNode = typeof process !== "undefined" && process.version; + return isNode ? new NodeInfo(process.version.slice(1)) : null; +} + +function createVersionParts(count: number): string[] { + const output = []; + for (let ii = 0; ii < count; ii++) { + output.push("0"); + } + + return output; +} diff --git a/packages/storage/src/utils/os.ts b/packages/storage/src/utils/os.ts new file mode 100644 index 00000000000..44ba468e4ed --- /dev/null +++ b/packages/storage/src/utils/os.ts @@ -0,0 +1,13 @@ +import { detectOS } from "./detect-browser"; + +export function getOperatingSystem() { + if (typeof navigator !== "undefined" && navigator.product === "ReactNative") { + return ""; + } else if (typeof window !== "undefined") { + const userAgent = navigator.userAgent; + + return detectOS(userAgent) || ""; + } else { + return process.platform; + } +} diff --git a/packages/unity-js-bridge/src/thirdweb-bridge.ts b/packages/unity-js-bridge/src/thirdweb-bridge.ts index e2aef2f6683..0f16cf93ab5 100644 --- a/packages/unity-js-bridge/src/thirdweb-bridge.ts +++ b/packages/unity-js-bridge/src/thirdweb-bridge.ts @@ -133,15 +133,15 @@ class ThirdwebBridge implements TWBridge { } public initialize(chain: ChainIdOrName, options: string) { - if(typeof globalThis !== "undefined"){ + if (typeof globalThis !== "undefined") { let browser; try { - browser = detect(); + browser = detect(); } catch { console.warn("Failed to detect browser"); browser = undefined; } - (globalThis as any).X_SDK_NAME = "UnitySDK_WebGL"; + (globalThis as any).X_SDK_NAME = "UnitySDK"; (globalThis as any).X_SDK_PLATFORM = "unity"; (globalThis as any).X_SDK_VERSION = "4.5.1"; (globalThis as any).X_SDK_OS = browser?.os ?? "unknown"; @@ -755,8 +755,7 @@ class ThirdwebBridge implements TWBridge { return JSON.stringify({ result: result }, bigNumberReplacer); } - - public async smartWalletGetAllActiveSigners(){ + public async smartWalletGetAllActiveSigners() { if (!this.activeWallet) { throw new Error("No wallet connected"); } diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts index c66de743cfc..889bd5f9fe3 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts @@ -3,6 +3,8 @@ import { hexlifyUserOp } from "./utils"; import { isTwUrl } from "../../../utils/url"; import { PaymasterAPI, PaymasterResult } from "../types"; import { DEBUG } from "./http-rpc-client"; +import { getOperatingSystem } from "../../../utils/os/os"; +import pkg from "../../../../../package.json"; export const SIG_SIZE = 65; export const DUMMY_PAYMASTER_AND_DATA = @@ -76,10 +78,19 @@ class VerifyingPaymasterAPI extends PaymasterAPI { headers["x-authorize-wallet"] = "true"; } - headers["x-sdk-version"] = (globalThis as any).X_SDK_VERSION; - headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME; - headers["x-sdk-platform"] = (globalThis as any).X_SDK_PLATFORM; - headers["x-sdk-os"] = (globalThis as any).X_SDK_OS; + headers["x-sdk-version"] = + (globalThis as any).X_SDK_VERSION || pkg.version; + headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME || pkg.name; + headers["x-sdk-platform"] = + (globalThis as any).X_SDK_PLATFORM || + (typeof navigator !== "undefined" && + navigator.product === "ReactNative") + ? "mobile" + : typeof window !== "undefined" + ? "browser" + : "node"; + headers["x-sdk-os"] = + (globalThis as any).X_SDK_OS || getOperatingSystem(); } // Ask the paymaster to sign the transaction and return a valid paymasterAndData value. diff --git a/packages/wallets/src/evm/utils/analytics.ts b/packages/wallets/src/evm/utils/analytics.ts index c8fab771930..0bd1263a568 100644 --- a/packages/wallets/src/evm/utils/analytics.ts +++ b/packages/wallets/src/evm/utils/analytics.ts @@ -1,4 +1,6 @@ import { isWalletAnalyticsEnabled } from "./setWalletAnaltyicsEnabled"; +import pkg from "../../../package.json"; +import { getOperatingSystem } from "./os/os"; const ANALYTICS_ENDPOINT = "https://c.thirdweb.com/event"; @@ -25,10 +27,17 @@ export function track(args: { headers: { "Content-Type": "application/json", "x-client-id": clientId, - "x-sdk-version": (globalThis as any).X_SDK_VERSION, - "x-sdk-name": (globalThis as any).X_SDK_NAME, - "x-sdk-platform": (globalThis as any).X_SDK_PLATFORM, - "x-sdk-os": (globalThis as any).X_SDK_OS, + "x-sdk-version": (globalThis as any).X_SDK_VERSION || pkg.version, + "x-sdk-name": (globalThis as any).X_SDK_NAME || pkg.name, + "x-sdk-platform": + (globalThis as any).X_SDK_PLATFORM || + (typeof navigator !== "undefined" && + navigator.product === "ReactNative") + ? "mobile" + : typeof window !== "undefined" + ? "browser" + : "node", + "x-sdk-os": (globalThis as any).X_SDK_OS || getOperatingSystem(), }, body: JSON.stringify(body), }); diff --git a/packages/wallets/src/evm/utils/os/detect-browser.ts b/packages/wallets/src/evm/utils/os/detect-browser.ts new file mode 100644 index 00000000000..92bbac9c672 --- /dev/null +++ b/packages/wallets/src/evm/utils/os/detect-browser.ts @@ -0,0 +1,338 @@ +/** + * @internal + * + * The code below comes from the package https://github.com/DamonOehlman/detect-browser + */ + +export type DetectedInfoType = + | "browser" + | "node" + | "bot-device" + | "bot" + | "react-native"; + +interface DetectedInfo< + T extends DetectedInfoType, + N extends string, + O, + V = null, +> { + readonly type: T; + readonly name: N; + readonly version: V; + readonly os: O; +} + +export class BrowserInfo + implements DetectedInfo<"browser", Browser, OperatingSystem | null, string> +{ + public readonly type = "browser"; + constructor( + public readonly name: Browser, + public readonly version: string, + public readonly os: OperatingSystem | null, + ) {} +} + +export class NodeInfo + implements DetectedInfo<"node", "node", NodeJS.Platform, string> +{ + public readonly type = "node"; + public readonly name = "node"; + public readonly os: NodeJS.Platform = process.platform; + + constructor(public readonly version: string) {} +} + +export class SearchBotDeviceInfo + implements + DetectedInfo<"bot-device", Browser, OperatingSystem | null, string> +{ + public readonly type = "bot-device"; + constructor( + public readonly name: Browser, + public readonly version: string, + public readonly os: OperatingSystem | null, + public readonly bot: string, + ) {} +} + +export class BotInfo implements DetectedInfo<"bot", "bot", null, null> { + public readonly type = "bot"; + public readonly bot = true; // NOTE: deprecated test name instead + public readonly name = "bot"; + public readonly version: null = null; + public readonly os: null = null; +} + +export class ReactNativeInfo + implements DetectedInfo<"react-native", "react-native", null, null> +{ + public readonly type = "react-native"; + public readonly name = "react-native"; + public readonly version: null = null; + public readonly os: null = null; +} + +export type Browser = + | "aol" + | "edge" + | "edge-ios" + | "yandexbrowser" + | "kakaotalk" + | "samsung" + | "silk" + | "miui" + | "beaker" + | "edge-chromium" + | "chrome" + | "chromium-webview" + | "phantomjs" + | "crios" + | "firefox" + | "fxios" + | "opera-mini" + | "opera" + | "pie" + | "netfront" + | "ie" + | "bb10" + | "android" + | "ios" + | "safari" + | "facebook" + | "instagram" + | "ios-webview" + | "curl" + | "searchbot"; +export type OperatingSystem = + | "iOS" + | "Android OS" + | "BlackBerry OS" + | "Windows Mobile" + | "Amazon OS" + | "Windows 3.11" + | "Windows 95" + | "Windows 98" + | "Windows 2000" + | "Windows XP" + | "Windows Server 2003" + | "Windows Vista" + | "Windows 7" + | "Windows 8" + | "Windows 8.1" + | "Windows 10" + | "Windows ME" + | "Windows CE" + | "Open BSD" + | "Sun OS" + | "Linux" + | "Mac OS" + | "QNX" + | "BeOS" + | "OS/2" + | "Chrome OS"; +type UserAgentRule = [Browser, RegExp]; +type UserAgentMatch = [Browser, RegExpExecArray] | false; +type OperatingSystemRule = [OperatingSystem, RegExp]; + +// tslint:disable-next-line:max-line-length +const SEARCHBOX_UA_REGEX = + /alexa|bot|crawl(er|ing)|facebookexternalhit|feedburner|google web preview|nagios|postrank|pingdom|slurp|spider|yahoo!|yandex/; +const SEARCHBOT_OS_REGEX = + /(nuhk|curl|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask\ Jeeves\/Teoma|ia_archiver)/; +const REQUIRED_VERSION_PARTS = 3; + +const userAgentRules: UserAgentRule[] = [ + ["aol", /AOLShield\/([0-9\._]+)/], + ["edge", /Edge\/([0-9\._]+)/], + ["edge-ios", /EdgiOS\/([0-9\._]+)/], + ["yandexbrowser", /YaBrowser\/([0-9\._]+)/], + ["kakaotalk", /KAKAOTALK\s([0-9\.]+)/], + ["samsung", /SamsungBrowser\/([0-9\.]+)/], + ["silk", /\bSilk\/([0-9._-]+)\b/], + ["miui", /MiuiBrowser\/([0-9\.]+)$/], + ["beaker", /BeakerBrowser\/([0-9\.]+)/], + ["edge-chromium", /EdgA?\/([0-9\.]+)/], + [ + "chromium-webview", + /(?!Chrom.*OPR)wv\).*Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/, + ], + ["chrome", /(?!Chrom.*OPR)Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/], + ["phantomjs", /PhantomJS\/([0-9\.]+)(:?\s|$)/], + ["crios", /CriOS\/([0-9\.]+)(:?\s|$)/], + ["firefox", /Firefox\/([0-9\.]+)(?:\s|$)/], + ["fxios", /FxiOS\/([0-9\.]+)/], + ["opera-mini", /Opera Mini.*Version\/([0-9\.]+)/], + ["opera", /Opera\/([0-9\.]+)(?:\s|$)/], + ["opera", /OPR\/([0-9\.]+)(:?\s|$)/], + ["pie", /^Microsoft Pocket Internet Explorer\/(\d+\.\d+)$/], + [ + "pie", + /^Mozilla\/\d\.\d+\s\(compatible;\s(?:MSP?IE|MSInternet Explorer) (\d+\.\d+);.*Windows CE.*\)$/, + ], + ["netfront", /^Mozilla\/\d\.\d+.*NetFront\/(\d.\d)/], + ["ie", /Trident\/7\.0.*rv\:([0-9\.]+).*\).*Gecko$/], + ["ie", /MSIE\s([0-9\.]+);.*Trident\/[4-7].0/], + ["ie", /MSIE\s(7\.0)/], + ["bb10", /BB10;\sTouch.*Version\/([0-9\.]+)/], + ["android", /Android\s([0-9\.]+)/], + ["ios", /Version\/([0-9\._]+).*Mobile.*Safari.*/], + ["safari", /Version\/([0-9\._]+).*Safari/], + ["facebook", /FB[AS]V\/([0-9\.]+)/], + ["instagram", /Instagram\s([0-9\.]+)/], + ["ios-webview", /AppleWebKit\/([0-9\.]+).*Mobile/], + ["ios-webview", /AppleWebKit\/([0-9\.]+).*Gecko\)$/], + ["curl", /^curl\/([0-9\.]+)$/], + ["searchbot", SEARCHBOX_UA_REGEX], +]; +const operatingSystemRules: OperatingSystemRule[] = [ + ["iOS", /iP(hone|od|ad)/], + ["Android OS", /Android/], + ["BlackBerry OS", /BlackBerry|BB10/], + ["Windows Mobile", /IEMobile/], + ["Amazon OS", /Kindle/], + ["Windows 3.11", /Win16/], + ["Windows 95", /(Windows 95)|(Win95)|(Windows_95)/], + ["Windows 98", /(Windows 98)|(Win98)/], + ["Windows 2000", /(Windows NT 5.0)|(Windows 2000)/], + ["Windows XP", /(Windows NT 5.1)|(Windows XP)/], + ["Windows Server 2003", /(Windows NT 5.2)/], + ["Windows Vista", /(Windows NT 6.0)/], + ["Windows 7", /(Windows NT 6.1)/], + ["Windows 8", /(Windows NT 6.2)/], + ["Windows 8.1", /(Windows NT 6.3)/], + ["Windows 10", /(Windows NT 10.0)/], + ["Windows ME", /Windows ME/], + ["Windows CE", /Windows CE|WinCE|Microsoft Pocket Internet Explorer/], + ["Open BSD", /OpenBSD/], + ["Sun OS", /SunOS/], + ["Chrome OS", /CrOS/], + ["Linux", /(Linux)|(X11)/], + ["Mac OS", /(Mac_PowerPC)|(Macintosh)/], + ["QNX", /QNX/], + ["BeOS", /BeOS/], + ["OS/2", /OS\/2/], +]; + +export function detect( + userAgent?: string, +): + | BrowserInfo + | SearchBotDeviceInfo + | BotInfo + | NodeInfo + | ReactNativeInfo + | null { + if (!!userAgent) { + return parseUserAgent(userAgent); + } + + if ( + typeof document === "undefined" && + typeof navigator !== "undefined" && + navigator.product === "ReactNative" + ) { + return new ReactNativeInfo(); + } + + if (typeof navigator !== "undefined") { + return parseUserAgent(navigator.userAgent); + } + + return getNodeVersion(); +} + +function matchUserAgent(ua: string): UserAgentMatch { + // opted for using reduce here rather than Array#first with a regex.test call + // this is primarily because using the reduce we only perform the regex + // execution once rather than once for the test and for the exec again below + // probably something that needs to be benchmarked though + return ( + ua !== "" && + userAgentRules.reduce( + (matched: UserAgentMatch, [browser, regex]) => { + if (matched) { + return matched; + } + + const uaMatch = regex.exec(ua); + return !!uaMatch && [browser, uaMatch]; + }, + false, + ) + ); +} + +export function browserName(ua: string): Browser | null { + const data = matchUserAgent(ua); + return data ? data[0] : null; +} + +export function parseUserAgent( + ua: string, +): BrowserInfo | SearchBotDeviceInfo | BotInfo | null { + const matchedRule: UserAgentMatch = matchUserAgent(ua); + + if (!matchedRule) { + return null; + } + + const [name, match] = matchedRule; + if (name === "searchbot") { + return new BotInfo(); + } + // Do not use RegExp for split operation as some browser do not support it (See: http://blog.stevenlevithan.com/archives/cross-browser-split) + let versionParts = + match[1] && match[1].split(".").join("_").split("_").slice(0, 3); + if (versionParts) { + if (versionParts.length < REQUIRED_VERSION_PARTS) { + versionParts = [ + ...versionParts, + ...createVersionParts(REQUIRED_VERSION_PARTS - versionParts.length), + ]; + } + } else { + versionParts = []; + } + + const version = versionParts.join("."); + const os = detectOS(ua); + const searchBotMatch = SEARCHBOT_OS_REGEX.exec(ua); + + if (searchBotMatch && searchBotMatch[1]) { + return new SearchBotDeviceInfo(name, version, os, searchBotMatch[1]); + } + + return new BrowserInfo(name, version, os); +} + +export function detectOS(ua: string): OperatingSystem | null { + for (let ii = 0, count = operatingSystemRules.length; ii < count; ii++) { + const result = operatingSystemRules[ii]; + if (!result) { + continue; + } + const [os, regex] = result; + const match = regex.exec(ua); + if (match) { + return os; + } + } + + return null; +} + +export function getNodeVersion(): NodeInfo | null { + const isNode = typeof process !== "undefined" && process.version; + return isNode ? new NodeInfo(process.version.slice(1)) : null; +} + +function createVersionParts(count: number): string[] { + const output = []; + for (let ii = 0; ii < count; ii++) { + output.push("0"); + } + + return output; +} diff --git a/packages/wallets/src/evm/utils/os/os.ts b/packages/wallets/src/evm/utils/os/os.ts new file mode 100644 index 00000000000..44ba468e4ed --- /dev/null +++ b/packages/wallets/src/evm/utils/os/os.ts @@ -0,0 +1,13 @@ +import { detectOS } from "./detect-browser"; + +export function getOperatingSystem() { + if (typeof navigator !== "undefined" && navigator.product === "ReactNative") { + return ""; + } else if (typeof window !== "undefined") { + const userAgent = navigator.userAgent; + + return detectOS(userAgent) || ""; + } else { + return process.platform; + } +} From 31f44a6b7c3fa0525e646c5cc053e9c87a910fe2 Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Tue, 30 Jan 2024 16:54:44 -0500 Subject: [PATCH 12/18] fix --- packages/unity-js-bridge/src/thirdweb-bridge.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/unity-js-bridge/src/thirdweb-bridge.ts b/packages/unity-js-bridge/src/thirdweb-bridge.ts index 0f16cf93ab5..01f9d60f0b6 100644 --- a/packages/unity-js-bridge/src/thirdweb-bridge.ts +++ b/packages/unity-js-bridge/src/thirdweb-bridge.ts @@ -141,7 +141,7 @@ class ThirdwebBridge implements TWBridge { console.warn("Failed to detect browser"); browser = undefined; } - (globalThis as any).X_SDK_NAME = "UnitySDK"; + (globalThis as any).X_SDK_NAME = "UnitySDK_WebGL"; (globalThis as any).X_SDK_PLATFORM = "unity"; (globalThis as any).X_SDK_VERSION = "4.5.1"; (globalThis as any).X_SDK_OS = browser?.os ?? "unknown"; From 2dd74debe71afa08c08ca7d19f9c405fd32ec150 Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Tue, 30 Jan 2024 22:21:39 -0500 Subject: [PATCH 13/18] revamp --- .../react-native/src/core/storage/uploader.ts | 23 +------ .../react-native/src/core/storage/utils.ts | 50 ++++++++++++++ .../react-native/src/evm/constants/headers.ts | 6 -- .../src/evm/providers/thirdweb-provider.tsx | 11 +-- .../embedded-wallet/embedded/auth.ts | 14 +--- .../embedded/helpers/api/fetchers.ts | 14 +--- .../src/evm/providers/thirdweb-provider.tsx | 10 +-- packages/react/src/evm/utils/headers.ts | 30 ++++++++ .../core/downloaders/storage-downloader.ts | 24 ++----- .../src/core/uploaders/ipfs-uploader.ts | 51 ++------------ packages/storage/src/utils/headers.ts | 68 +++++++++++++++++++ .../smart-wallet/lib/http-rpc-client.ts | 6 +- .../connectors/smart-wallet/lib/paymaster.ts | 15 +--- packages/wallets/src/evm/utils/analytics.ts | 15 +--- packages/wallets/src/evm/utils/headers.ts | 68 +++++++++++++++++++ 15 files changed, 243 insertions(+), 162 deletions(-) delete mode 100644 packages/react-native/src/evm/constants/headers.ts create mode 100644 packages/react/src/evm/utils/headers.ts create mode 100644 packages/storage/src/utils/headers.ts create mode 100644 packages/wallets/src/evm/utils/headers.ts diff --git a/packages/react-native/src/core/storage/uploader.ts b/packages/react-native/src/core/storage/uploader.ts index c93e2b8fa3c..736e354d478 100644 --- a/packages/react-native/src/core/storage/uploader.ts +++ b/packages/react-native/src/core/storage/uploader.ts @@ -5,7 +5,7 @@ import { TW_UPLOAD_SERVER_URL, } from "@thirdweb-dev/storage"; import { IpfsUploaderOptions, UploadDataValue } from "./types"; -import { BUNDLE_ID_HEADER } from "../../evm/constants/headers"; +import { getAnalyticsHeaders, setAnalyticsHeaders } from "./utils"; const METADATA_NAME = "Storage React Native SDK"; @@ -136,24 +136,11 @@ export class IpfsUploader implements IStorageUploader { }); xhr.open("POST", `${TW_UPLOAD_SERVER_URL}/ipfs/upload`); - xhr.setRequestHeader( - BUNDLE_ID_HEADER, - (globalThis as any).APP_BUNDLE_ID, - ); // only empty on web if (this.clientId) { xhr.setRequestHeader("x-client-id", this.clientId); } - xhr.setRequestHeader( - "x-sdk-version", - (globalThis as any).X_SDK_VERSION, - ); - xhr.setRequestHeader("x-sdk-os", (globalThis as any).X_SDK_OS); - xhr.setRequestHeader("x-sdk-name", (globalThis as any).X_SDK_NAME); - xhr.setRequestHeader( - "x-sdk-platform", - (globalThis as any).X_SDK_PLATFORM, - ); + setAnalyticsHeaders(xhr); xhr.send(form); }); @@ -171,13 +158,9 @@ export class IpfsUploader implements IStorageUploader { { method: "POST", headers: { - [BUNDLE_ID_HEADER]: (globalThis as any).APP_BUNDLE_ID, ...(this.clientId ? { "x-client-id": this.clientId } : {}), "Content-Type": "application/json", - "x-sdk-version": (globalThis as any).X_SDK_VERSION, - "x-sdk-name": (globalThis as any).X_SDK_NAME, - "x-sdk-platform": (globalThis as any).X_SDK_PLATFORM, - "x-sdk-os": (globalThis as any).X_SDK_OS, + ...getAnalyticsHeaders(), }, body: fetchBody, }, diff --git a/packages/react-native/src/core/storage/utils.ts b/packages/react-native/src/core/storage/utils.ts index 362217c9bca..fddda77039b 100644 --- a/packages/react-native/src/core/storage/utils.ts +++ b/packages/react-native/src/core/storage/utils.ts @@ -1,4 +1,6 @@ import { DEFAULT_GATEWAY_URLS, GatewayUrls } from "@thirdweb-dev/storage"; +import { Platform } from "react-native"; +import { appBundleId, packageVersion } from "../../evm/utils/version"; /** * @internal @@ -32,3 +34,51 @@ export function prepareGatewayUrls( return allGatewayUrls; } + +export function setAnalyticsHeaders(xhr: XMLHttpRequest) { + const globals = getAnalyticsGlobals(); + xhr.setRequestHeader("x-sdk-version", globals.x_sdk_version); + xhr.setRequestHeader("x-sdk-os", globals.x_sdk_os); + xhr.setRequestHeader("x-sdk-name", globals.x_sdk_name); + xhr.setRequestHeader("x-sdk-platform", globals.x_sdk_platform); + xhr.setRequestHeader("x-bundle-id", globals.app_bundle_id); +} + +export function getAnalyticsHeaders() { + const globals = getAnalyticsGlobals(); + return { + "x-sdk-version": globals.x_sdk_version, + "x-sdk-os": globals.x_sdk_os, + "x-sdk-name": globals.x_sdk_name, + "x-sdk-platform": globals.x_sdk_platform, + "x-bundle-id": globals.app_bundle_id, + }; +} + +export function getAnalyticsGlobals() { + if (typeof globalThis === "undefined") { + return { + x_sdk_name: packageVersion.name, + x_sdk_platform: "mobile", + x_sdk_version: packageVersion.version, + x_sdk_os: Platform.OS, + app_bundle_id: appBundleId, + }; + } + + if ((globalThis as any).X_SDK_NAME === undefined) { + (globalThis as any).X_SDK_NAME = packageVersion.name; + (globalThis as any).X_SDK_PLATFORM = "mobile"; + (globalThis as any).X_SDK_VERSION = packageVersion.version; + (globalThis as any).X_SDK_OS = Platform.OS; + (globalThis as any).APP_BUNDLE_ID = appBundleId; + } + + return { + x_sdk_name: (globalThis as any).X_SDK_NAME, + x_sdk_platform: (globalThis as any).X_SDK_PLATFORM, + x_sdk_version: (globalThis as any).X_SDK_VERSION, + x_sdk_os: (globalThis as any).X_SDK_OS, + app_bundle_id: (globalThis as any).APP_BUNDLE_ID, + }; +} diff --git a/packages/react-native/src/evm/constants/headers.ts b/packages/react-native/src/evm/constants/headers.ts deleted file mode 100644 index 885f71431c5..00000000000 --- a/packages/react-native/src/evm/constants/headers.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const BUNDLE_ID_HEADER = "x-bundle-id"; - -export const X_SDK_VERSION_HEADER = "x-sdk-version"; -export const X_SDK_NAME_HEADER = "x-sdk-name"; -export const X_SDK_PLATFORM_HEADER = "x-sdk-platform"; -export const X_SDK_OS_HEADER = "x-sdk-os"; diff --git a/packages/react-native/src/evm/providers/thirdweb-provider.tsx b/packages/react-native/src/evm/providers/thirdweb-provider.tsx index 7ae247e6428..17449722d7b 100644 --- a/packages/react-native/src/evm/providers/thirdweb-provider.tsx +++ b/packages/react-native/src/evm/providers/thirdweb-provider.tsx @@ -17,8 +17,7 @@ import { SafeAreaProvider } from "react-native-safe-area-context"; import { walletIds } from "@thirdweb-dev/wallets"; import { ThirdwebStorage } from "../../core/storage/storage"; import type { Locale } from "../i18n/types"; -import { appBundleId, packageVersion } from "../utils/version"; -import { Platform } from "react-native"; +import { getAnalyticsGlobals } from "../../core/storage/utils"; export interface ThirdwebProviderProps extends Omit< @@ -150,13 +149,7 @@ export const ThirdwebProvider = ( ...restProps } = props; - if (typeof globalThis !== "undefined") { - (globalThis as any).X_SDK_NAME = packageVersion.name; - (globalThis as any).X_SDK_PLATFORM = "mobile"; - (globalThis as any).X_SDK_VERSION = packageVersion.version; - (globalThis as any).X_SDK_OS = Platform.OS; - (globalThis as any).APP_BUNDLE_ID = appBundleId; - } + getAnalyticsGlobals(); const coinbaseWalletObj = supportedWallets.find( (w) => w.id === walletIds.coinbase, diff --git a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts index 7fbea913052..62e82c34b4c 100644 --- a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts +++ b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/auth.ts @@ -41,23 +41,13 @@ import { } from "../types"; import { InAppBrowser } from "react-native-inappbrowser-reborn"; import { createErrorMessage } from "./helpers/errors"; -import { - BUNDLE_ID_HEADER, - X_SDK_NAME_HEADER, - X_SDK_OS_HEADER, - X_SDK_PLATFORM_HEADER, - X_SDK_VERSION_HEADER, -} from "../../../../constants/headers"; import { ANALYTICS } from "./helpers/analytics"; +import { getAnalyticsHeaders } from "../../../../../core/storage/utils"; const HEADERS = { [EWS_VERSION_HEADER]: (globalThis as any).X_SDK_VERSION, - [BUNDLE_ID_HEADER]: (globalThis as any).APP_BUNDLE_ID, [THIRDWEB_SESSION_NONCE_HEADER]: ANALYTICS.nonce, - [X_SDK_NAME_HEADER]: (globalThis as any).X_SDK_NAME, - [X_SDK_OS_HEADER]: (globalThis as any).X_SDK_OS, - [X_SDK_PLATFORM_HEADER]: (globalThis as any).X_SDK_PLATFORM, - [X_SDK_VERSION_HEADER]: (globalThis as any).X_SDK_VERSION, + ...getAnalyticsHeaders(), }; export async function sendVerificationEmail(options: { diff --git a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/api/fetchers.ts b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/api/fetchers.ts index 34f41a9e382..92b36228335 100644 --- a/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/api/fetchers.ts +++ b/packages/react-native/src/evm/wallets/connectors/embedded-wallet/embedded/helpers/api/fetchers.ts @@ -20,14 +20,8 @@ import { VerifiedTokenResponse, } from "../../../types"; import { createErrorMessage } from "../errors"; -import { - BUNDLE_ID_HEADER, - X_SDK_NAME_HEADER, - X_SDK_OS_HEADER, - X_SDK_PLATFORM_HEADER, - X_SDK_VERSION_HEADER, -} from "../../../../../../constants/headers"; import { ANALYTICS } from "../analytics"; +import { getAnalyticsHeaders } from "../../../../../../../core/storage/utils"; const EMBEDDED_WALLET_TOKEN_HEADER = "embedded-wallet-token"; const PAPER_CLIENT_ID_HEADER = "x-thirdweb-client-id"; @@ -35,12 +29,8 @@ const PAPER_CLIENT_ID_HEADER = "x-thirdweb-client-id"; const HEADERS = { "Content-Type": "application/json", [EWS_VERSION_HEADER]: (globalThis as any).X_SDK_VERSION, - [BUNDLE_ID_HEADER]: (globalThis as any).APP_BUNDLE_ID, [THIRDWEB_SESSION_NONCE_HEADER]: ANALYTICS.nonce, - [X_SDK_NAME_HEADER]: (globalThis as any).X_SDK_NAME, - [X_SDK_OS_HEADER]: (globalThis as any).X_SDK_OS, - [X_SDK_PLATFORM_HEADER]: (globalThis as any).X_SDK_PLATFORM, - [X_SDK_VERSION_HEADER]: (globalThis as any).X_SDK_VERSION, + ...getAnalyticsHeaders(), }; export const verifyClientId = async (clientId: string) => { diff --git a/packages/react/src/evm/providers/thirdweb-provider.tsx b/packages/react/src/evm/providers/thirdweb-provider.tsx index 4ac7e450eee..cb084a2e3ad 100644 --- a/packages/react/src/evm/providers/thirdweb-provider.tsx +++ b/packages/react/src/evm/providers/thirdweb-provider.tsx @@ -16,8 +16,7 @@ import { en } from "../locales/en"; import { ThirdwebLocaleContext } from "./locale-provider"; import { walletIds } from "@thirdweb-dev/wallets"; import { ThirdwebLocale } from "../locales/types"; -import packageJson from "../../../package.json"; -import { detectOS } from "../utils/isMobile"; +import { getAnalyticsGlobals } from "../utils/headers"; export interface ThirdwebProviderProps extends Omit< @@ -481,12 +480,7 @@ export const ThirdwebProvider = ( ...restProps } = props; - if (typeof globalThis !== "undefined") { - (globalThis as any).X_SDK_NAME = packageJson.name; - (globalThis as any).X_SDK_PLATFORM = "browser"; - (globalThis as any).X_SDK_VERSION = packageJson.version; - (globalThis as any).X_SDK_OS = detectOS(); - } + getAnalyticsGlobals(); const wallets: WalletConfig[] = supportedWallets || defaultWallets; const theme = _theme || "dark"; diff --git a/packages/react/src/evm/utils/headers.ts b/packages/react/src/evm/utils/headers.ts new file mode 100644 index 00000000000..4fe05de9e52 --- /dev/null +++ b/packages/react/src/evm/utils/headers.ts @@ -0,0 +1,30 @@ +import packageJson from "../../../package.json"; +import { detectOS } from "./isMobile"; + +export function getAnalyticsGlobals() { + if (typeof globalThis === "undefined") { + return { + x_sdk_name: packageJson.name, + x_sdk_platform: "browser", + x_sdk_version: packageJson.version, + x_sdk_os: detectOS(), + app_bundle_id: undefined, + }; + } + + if ((globalThis as any).X_SDK_NAME === undefined) { + (globalThis as any).X_SDK_NAME = packageJson.name; + (globalThis as any).X_SDK_PLATFORM = "browser"; + (globalThis as any).X_SDK_VERSION = packageJson.version; + (globalThis as any).X_SDK_OS = detectOS(); + (globalThis as any).APP_BUNDLE_ID = undefined; + } + + return { + x_sdk_name: (globalThis as any).X_SDK_NAME, + x_sdk_platform: (globalThis as any).X_SDK_PLATFORM, + x_sdk_version: (globalThis as any).X_SDK_VERSION, + x_sdk_os: (globalThis as any).X_SDK_OS, + app_bundle_id: (globalThis as any).APP_BUNDLE_ID, + }; +} diff --git a/packages/storage/src/core/downloaders/storage-downloader.ts b/packages/storage/src/core/downloaders/storage-downloader.ts index 8e62e5952e2..fa532fbb9f4 100644 --- a/packages/storage/src/core/downloaders/storage-downloader.ts +++ b/packages/storage/src/core/downloaders/storage-downloader.ts @@ -1,13 +1,12 @@ import { isTwGatewayUrl } from "../../common/urls"; -import { isBrowser, replaceSchemeWithGatewayUrl } from "../../common/utils"; +import { replaceSchemeWithGatewayUrl } from "../../common/utils"; import { GatewayUrls, IStorageDownloader, IpfsDownloaderOptions, SingleDownloadOptions, } from "../../types"; -import pkg from "../../../package.json"; -import { getOperatingSystem } from "../../utils/os"; +import { getAnalyticsGlobals, setAnalyticsHeaders } from "../../utils/headers"; /** * Default downloader used - handles downloading from all schemes specified in the gateway URLs configuration. @@ -87,10 +86,7 @@ export class StorageDownloader implements IStorageDownloader { let headers: HeadersInit = {}; if (isTwGatewayUrl(resolvedUri)) { - const bundleId = - typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis - ? ((globalThis as any).APP_BUNDLE_ID as string) - : undefined; + const bundleId = getAnalyticsGlobals().app_bundle_id; if (this.secretKey) { headers = { "x-secret-key": this.secretKey }; } else if (this.clientId) { @@ -127,19 +123,7 @@ export class StorageDownloader implements IStorageDownloader { headers["x-authorize-wallet"] = "true"; } - headers["x-sdk-version"] = - (globalThis as any).X_SDK_VERSION || pkg.version; - headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME || pkg.name; - headers["x-sdk-platform"] = - (globalThis as any).X_SDK_PLATFORM || - (typeof navigator !== "undefined" && - navigator.product === "ReactNative") - ? "mobile" - : typeof window !== "undefined" - ? "browser" - : "node"; - headers["x-sdk-os"] = - (globalThis as any).X_SDK_OS || getOperatingSystem(); + setAnalyticsHeaders(headers); } if (isTooManyRequests(resolvedUri)) { diff --git a/packages/storage/src/core/uploaders/ipfs-uploader.ts b/packages/storage/src/core/uploaders/ipfs-uploader.ts index cc5d275d211..781621e3555 100644 --- a/packages/storage/src/core/uploaders/ipfs-uploader.ts +++ b/packages/storage/src/core/uploaders/ipfs-uploader.ts @@ -14,6 +14,10 @@ import { import FormData from "form-data"; import pkg from "../../../package.json"; import { getOperatingSystem } from "../../utils/os"; +import { + setAnalyticsHeaders, + setAnalyticsHeadersForXhr, +} from "../../utils/headers"; /** * Default uploader used - handles uploading arbitrary data to IPFS @@ -275,36 +279,7 @@ export class IpfsUploader implements IStorageUploader { xhr.setRequestHeader("x-client-id", this.clientId); } - const bundleId = - typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis - ? ((globalThis as any).APP_BUNDLE_ID as string) - : undefined; - if (bundleId) { - xhr.setRequestHeader("x-bundle-id", bundleId); - } - - xhr.setRequestHeader( - "x-sdk-version", - (globalThis as any).X_SDK_VERSION || pkg.version, - ); - xhr.setRequestHeader( - "x-sdk-os", - (globalThis as any).X_SDK_OS || getOperatingSystem(), - ); - xhr.setRequestHeader( - "x-sdk-name", - (globalThis as any).X_SDK_NAME || pkg.name, - ); - xhr.setRequestHeader( - "x-sdk-platform", - (globalThis as any).X_SDK_PLATFORM || - (typeof navigator !== "undefined" && - navigator.product === "ReactNative") - ? "mobile" - : typeof window !== "undefined" - ? "browser" - : "node", - ); + setAnalyticsHeadersForXhr(xhr); // if we have a authorization token on global context then add that to the headers, this is for the dashboard. if ( @@ -352,11 +327,6 @@ export class IpfsUploader implements IStorageUploader { headers["x-client-id"] = this.clientId; } - // if we have a bundle id on global context then add that to the headers - if (typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis) { - headers["x-bundle-id"] = (globalThis as any).APP_BUNDLE_ID as string; - } - // if we have a authorization token on global context then add that to the headers, this is for the dashboard. if ( typeof globalThis !== "undefined" && @@ -380,16 +350,7 @@ export class IpfsUploader implements IStorageUploader { headers["x-authorize-wallet"] = "true"; } - headers["x-sdk-version"] = (globalThis as any).X_SDK_VERSION || pkg.version; - headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME || pkg.name; - headers["x-sdk-platform"] = - (globalThis as any).X_SDK_PLATFORM || - (typeof navigator !== "undefined" && navigator.product === "ReactNative") - ? "mobile" - : typeof window !== "undefined" - ? "browser" - : "node"; - headers["x-sdk-os"] = (globalThis as any).X_SDK_OS || getOperatingSystem(); + setAnalyticsHeaders(headers); const res = await fetch(`${this.uploadServerUrl}/ipfs/upload`, { method: "POST", diff --git a/packages/storage/src/utils/headers.ts b/packages/storage/src/utils/headers.ts new file mode 100644 index 00000000000..dc1bed07944 --- /dev/null +++ b/packages/storage/src/utils/headers.ts @@ -0,0 +1,68 @@ +import pkg from "../../../../package.json"; +import { getOperatingSystem } from "./os"; + +export function setAnalyticsHeaders(headers: Record) { + const globals = getAnalyticsGlobals(); + + headers["x-sdk-version"] = globals.x_sdk_version; + headers["x-sdk-name"] = globals.x_sdk_name; + headers["x-sdk-platform"] = globals.x_sdk_platform; + headers["x-sdk-os"] = globals.x_sdk_os; +} + +export function setAnalyticsHeadersForXhr(xhr: XMLHttpRequest) { + const globals = getAnalyticsGlobals(); + + xhr.setRequestHeader("x-sdk-version", globals.x_sdk_version); + xhr.setRequestHeader("x-sdk-os", globals.x_sdk_os); + xhr.setRequestHeader("x-sdk-name", globals.x_sdk_name); + xhr.setRequestHeader("x-sdk-platform", globals.x_sdk_platform); + xhr.setRequestHeader("x-bundle-id", globals.app_bundle_id); +} + +export function getAnalyticsHeaders() { + const globals = getAnalyticsGlobals(); + return { + "x-sdk-version": globals.x_sdk_version, + "x-sdk-os": globals.x_sdk_os, + "x-sdk-name": globals.x_sdk_name, + "x-sdk-platform": globals.x_sdk_platform, + "x-bundle-id": globals.app_bundle_id, + }; +} + +export function getAnalyticsGlobals() { + if (typeof globalThis === "undefined") { + return { + x_sdk_name: pkg.name, + x_sdk_platform: getPlatform(), + x_sdk_version: pkg.version, + x_sdk_os: getOperatingSystem(), + app_bundle_id: undefined, + }; + } + + if ((globalThis as any).X_SDK_NAME === undefined) { + (globalThis as any).X_SDK_NAME = pkg.name; + (globalThis as any).X_SDK_PLATFORM = getPlatform(); + (globalThis as any).X_SDK_VERSION = pkg.version; + (globalThis as any).X_SDK_OS = getOperatingSystem(); + (globalThis as any).APP_BUNDLE_ID = undefined; + } + + return { + x_sdk_name: (globalThis as any).X_SDK_NAME, + x_sdk_platform: (globalThis as any).X_SDK_PLATFORM, + x_sdk_version: (globalThis as any).X_SDK_VERSION, + x_sdk_os: (globalThis as any).X_SDK_OS, + app_bundle_id: (globalThis as any).APP_BUNDLE_ID || "", // if react, this will be empty + }; +} + +export function getPlatform() { + return typeof navigator !== "undefined" && navigator.product === "ReactNative" + ? "mobile" + : typeof window !== "undefined" + ? "browser" + : "node"; +} diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/http-rpc-client.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/http-rpc-client.ts index feb5911fd39..2bf5a7578cc 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/http-rpc-client.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/http-rpc-client.ts @@ -2,6 +2,7 @@ import { providers, utils } from "ethers"; import { UserOperationStruct } from "@account-abstraction/contracts"; import { isTwUrl } from "../../../utils/url"; import { hexlifyUserOp } from "./utils"; +import { setAnalyticsHeaders } from "../../../utils/headers"; export const DEBUG = false; // TODO set as public flag @@ -65,10 +66,7 @@ export class HttpRpcClient { headers["x-authorize-wallet"] = "true"; } - headers["x-sdk-version"] = (globalThis as any).X_SDK_VERSION; - headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME; - headers["x-sdk-platform"] = (globalThis as any).X_SDK_PLATFORM; - headers["x-sdk-os"] = (globalThis as any).X_SDK_OS; + setAnalyticsHeaders(headers); } this.userOpJsonRpcProvider = new providers.JsonRpcProvider( diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts index 889bd5f9fe3..c12262abac2 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts @@ -5,6 +5,7 @@ import { PaymasterAPI, PaymasterResult } from "../types"; import { DEBUG } from "./http-rpc-client"; import { getOperatingSystem } from "../../../utils/os/os"; import pkg from "../../../../../package.json"; +import { setAnalyticsHeaders } from "../../../utils/headers"; export const SIG_SIZE = 65; export const DUMMY_PAYMASTER_AND_DATA = @@ -78,19 +79,7 @@ class VerifyingPaymasterAPI extends PaymasterAPI { headers["x-authorize-wallet"] = "true"; } - headers["x-sdk-version"] = - (globalThis as any).X_SDK_VERSION || pkg.version; - headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME || pkg.name; - headers["x-sdk-platform"] = - (globalThis as any).X_SDK_PLATFORM || - (typeof navigator !== "undefined" && - navigator.product === "ReactNative") - ? "mobile" - : typeof window !== "undefined" - ? "browser" - : "node"; - headers["x-sdk-os"] = - (globalThis as any).X_SDK_OS || getOperatingSystem(); + setAnalyticsHeaders(headers); } // Ask the paymaster to sign the transaction and return a valid paymasterAndData value. diff --git a/packages/wallets/src/evm/utils/analytics.ts b/packages/wallets/src/evm/utils/analytics.ts index 0bd1263a568..06a8dee0d85 100644 --- a/packages/wallets/src/evm/utils/analytics.ts +++ b/packages/wallets/src/evm/utils/analytics.ts @@ -1,6 +1,5 @@ import { isWalletAnalyticsEnabled } from "./setWalletAnaltyicsEnabled"; -import pkg from "../../../package.json"; -import { getOperatingSystem } from "./os/os"; +import { getAnalyticsHeaders } from "./headers"; const ANALYTICS_ENDPOINT = "https://c.thirdweb.com/event"; @@ -27,17 +26,7 @@ export function track(args: { headers: { "Content-Type": "application/json", "x-client-id": clientId, - "x-sdk-version": (globalThis as any).X_SDK_VERSION || pkg.version, - "x-sdk-name": (globalThis as any).X_SDK_NAME || pkg.name, - "x-sdk-platform": - (globalThis as any).X_SDK_PLATFORM || - (typeof navigator !== "undefined" && - navigator.product === "ReactNative") - ? "mobile" - : typeof window !== "undefined" - ? "browser" - : "node", - "x-sdk-os": (globalThis as any).X_SDK_OS || getOperatingSystem(), + ...getAnalyticsHeaders(), }, body: JSON.stringify(body), }); diff --git a/packages/wallets/src/evm/utils/headers.ts b/packages/wallets/src/evm/utils/headers.ts new file mode 100644 index 00000000000..6b7fe47217d --- /dev/null +++ b/packages/wallets/src/evm/utils/headers.ts @@ -0,0 +1,68 @@ +import pkg from "../../../../../package.json"; +import { getOperatingSystem } from "./os/os"; + +export function setAnalyticsHeaders(headers: Record) { + const globals = getAnalyticsGlobals(); + + headers["x-sdk-version"] = globals.x_sdk_version; + headers["x-sdk-name"] = globals.x_sdk_name; + headers["x-sdk-platform"] = globals.x_sdk_platform; + headers["x-sdk-os"] = globals.x_sdk_os; +} + +export function setAnalyticsHeadersForXhr(xhr: XMLHttpRequest) { + const globals = getAnalyticsGlobals(); + + xhr.setRequestHeader("x-sdk-version", globals.x_sdk_version); + xhr.setRequestHeader("x-sdk-os", globals.x_sdk_os); + xhr.setRequestHeader("x-sdk-name", globals.x_sdk_name); + xhr.setRequestHeader("x-sdk-platform", globals.x_sdk_platform); + xhr.setRequestHeader("x-bundle-id", globals.app_bundle_id); +} + +export function getAnalyticsHeaders() { + const globals = getAnalyticsGlobals(); + return { + "x-sdk-version": globals.x_sdk_version, + "x-sdk-os": globals.x_sdk_os, + "x-sdk-name": globals.x_sdk_name, + "x-sdk-platform": globals.x_sdk_platform, + "x-bundle-id": globals.app_bundle_id, + }; +} + +export function getAnalyticsGlobals() { + if (typeof globalThis === "undefined") { + return { + x_sdk_name: pkg.name, + x_sdk_platform: getPlatform(), + x_sdk_version: pkg.version, + x_sdk_os: getOperatingSystem(), + app_bundle_id: undefined, + }; + } + + if ((globalThis as any).X_SDK_NAME === undefined) { + (globalThis as any).X_SDK_NAME = pkg.name; + (globalThis as any).X_SDK_PLATFORM = getPlatform(); + (globalThis as any).X_SDK_VERSION = pkg.version; + (globalThis as any).X_SDK_OS = getOperatingSystem(); + (globalThis as any).APP_BUNDLE_ID = undefined; + } + + return { + x_sdk_name: (globalThis as any).X_SDK_NAME, + x_sdk_platform: (globalThis as any).X_SDK_PLATFORM, + x_sdk_version: (globalThis as any).X_SDK_VERSION, + x_sdk_os: (globalThis as any).X_SDK_OS, + app_bundle_id: (globalThis as any).APP_BUNDLE_ID || "", // if not RN, this will be empty + }; +} + +export function getPlatform() { + return typeof navigator !== "undefined" && navigator.product === "ReactNative" + ? "mobile" + : typeof window !== "undefined" + ? "browser" + : "node"; +} From 2946b036e0ffd847863bc285a0742d649de49eca Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Tue, 30 Jan 2024 22:25:49 -0500 Subject: [PATCH 14/18] improve --- packages/react-native/src/evm/providers/thirdweb-provider.tsx | 3 --- packages/react/src/evm/providers/thirdweb-provider.tsx | 3 --- 2 files changed, 6 deletions(-) diff --git a/packages/react-native/src/evm/providers/thirdweb-provider.tsx b/packages/react-native/src/evm/providers/thirdweb-provider.tsx index 17449722d7b..d635f5d335a 100644 --- a/packages/react-native/src/evm/providers/thirdweb-provider.tsx +++ b/packages/react-native/src/evm/providers/thirdweb-provider.tsx @@ -17,7 +17,6 @@ import { SafeAreaProvider } from "react-native-safe-area-context"; import { walletIds } from "@thirdweb-dev/wallets"; import { ThirdwebStorage } from "../../core/storage/storage"; import type { Locale } from "../i18n/types"; -import { getAnalyticsGlobals } from "../../core/storage/utils"; export interface ThirdwebProviderProps extends Omit< @@ -149,8 +148,6 @@ export const ThirdwebProvider = ( ...restProps } = props; - getAnalyticsGlobals(); - const coinbaseWalletObj = supportedWallets.find( (w) => w.id === walletIds.coinbase, ); diff --git a/packages/react/src/evm/providers/thirdweb-provider.tsx b/packages/react/src/evm/providers/thirdweb-provider.tsx index cb084a2e3ad..c83bb258da1 100644 --- a/packages/react/src/evm/providers/thirdweb-provider.tsx +++ b/packages/react/src/evm/providers/thirdweb-provider.tsx @@ -16,7 +16,6 @@ import { en } from "../locales/en"; import { ThirdwebLocaleContext } from "./locale-provider"; import { walletIds } from "@thirdweb-dev/wallets"; import { ThirdwebLocale } from "../locales/types"; -import { getAnalyticsGlobals } from "../utils/headers"; export interface ThirdwebProviderProps extends Omit< @@ -480,8 +479,6 @@ export const ThirdwebProvider = ( ...restProps } = props; - getAnalyticsGlobals(); - const wallets: WalletConfig[] = supportedWallets || defaultWallets; const theme = _theme || "dark"; From a86280d087283b477e7b50070409165bd94e8f40 Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Wed, 31 Jan 2024 00:04:11 -0500 Subject: [PATCH 15/18] lint --- .../react-native/src/evm/providers/thirdweb-provider.tsx | 3 +++ packages/react/src/evm/providers/thirdweb-provider.tsx | 3 +++ packages/sdk/src/evm/constants/urls.ts | 1 - packages/storage/src/core/uploaders/ipfs-uploader.ts | 2 -- packages/storage/src/utils/headers.ts | 2 +- .../src/evm/connectors/smart-wallet/lib/paymaster.ts | 2 -- packages/wallets/src/evm/utils/headers.ts | 9 ++++++++- 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/react-native/src/evm/providers/thirdweb-provider.tsx b/packages/react-native/src/evm/providers/thirdweb-provider.tsx index d635f5d335a..17449722d7b 100644 --- a/packages/react-native/src/evm/providers/thirdweb-provider.tsx +++ b/packages/react-native/src/evm/providers/thirdweb-provider.tsx @@ -17,6 +17,7 @@ import { SafeAreaProvider } from "react-native-safe-area-context"; import { walletIds } from "@thirdweb-dev/wallets"; import { ThirdwebStorage } from "../../core/storage/storage"; import type { Locale } from "../i18n/types"; +import { getAnalyticsGlobals } from "../../core/storage/utils"; export interface ThirdwebProviderProps extends Omit< @@ -148,6 +149,8 @@ export const ThirdwebProvider = ( ...restProps } = props; + getAnalyticsGlobals(); + const coinbaseWalletObj = supportedWallets.find( (w) => w.id === walletIds.coinbase, ); diff --git a/packages/react/src/evm/providers/thirdweb-provider.tsx b/packages/react/src/evm/providers/thirdweb-provider.tsx index c83bb258da1..cb084a2e3ad 100644 --- a/packages/react/src/evm/providers/thirdweb-provider.tsx +++ b/packages/react/src/evm/providers/thirdweb-provider.tsx @@ -16,6 +16,7 @@ import { en } from "../locales/en"; import { ThirdwebLocaleContext } from "./locale-provider"; import { walletIds } from "@thirdweb-dev/wallets"; import { ThirdwebLocale } from "../locales/types"; +import { getAnalyticsGlobals } from "../utils/headers"; export interface ThirdwebProviderProps extends Omit< @@ -479,6 +480,8 @@ export const ThirdwebProvider = ( ...restProps } = props; + getAnalyticsGlobals(); + const wallets: WalletConfig[] = supportedWallets || defaultWallets; const theme = _theme || "dark"; diff --git a/packages/sdk/src/evm/constants/urls.ts b/packages/sdk/src/evm/constants/urls.ts index 94912194847..760fb7cce90 100644 --- a/packages/sdk/src/evm/constants/urls.ts +++ b/packages/sdk/src/evm/constants/urls.ts @@ -9,7 +9,6 @@ import type { Chain } from "@thirdweb-dev/chains"; import { providers } from "ethers"; import type { Signer } from "ethers"; import pkg from "../../../package.json"; -import { isBrowser } from "@thirdweb-dev/storage"; import { sha256HexSync } from "@thirdweb-dev/crypto"; import { getOperatingSystem } from "../../core/utils/os"; diff --git a/packages/storage/src/core/uploaders/ipfs-uploader.ts b/packages/storage/src/core/uploaders/ipfs-uploader.ts index 781621e3555..518087dc9f3 100644 --- a/packages/storage/src/core/uploaders/ipfs-uploader.ts +++ b/packages/storage/src/core/uploaders/ipfs-uploader.ts @@ -12,8 +12,6 @@ import { IStorageUploader, } from "../../types"; import FormData from "form-data"; -import pkg from "../../../package.json"; -import { getOperatingSystem } from "../../utils/os"; import { setAnalyticsHeaders, setAnalyticsHeadersForXhr, diff --git a/packages/storage/src/utils/headers.ts b/packages/storage/src/utils/headers.ts index dc1bed07944..9bfdf5bbc6d 100644 --- a/packages/storage/src/utils/headers.ts +++ b/packages/storage/src/utils/headers.ts @@ -1,4 +1,4 @@ -import pkg from "../../../../package.json"; +import pkg from "../../package.json"; import { getOperatingSystem } from "./os"; export function setAnalyticsHeaders(headers: Record) { diff --git a/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts b/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts index c12262abac2..a354c424ab4 100644 --- a/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts +++ b/packages/wallets/src/evm/connectors/smart-wallet/lib/paymaster.ts @@ -3,8 +3,6 @@ import { hexlifyUserOp } from "./utils"; import { isTwUrl } from "../../../utils/url"; import { PaymasterAPI, PaymasterResult } from "../types"; import { DEBUG } from "./http-rpc-client"; -import { getOperatingSystem } from "../../../utils/os/os"; -import pkg from "../../../../../package.json"; import { setAnalyticsHeaders } from "../../../utils/headers"; export const SIG_SIZE = 65; diff --git a/packages/wallets/src/evm/utils/headers.ts b/packages/wallets/src/evm/utils/headers.ts index 6b7fe47217d..eda32428798 100644 --- a/packages/wallets/src/evm/utils/headers.ts +++ b/packages/wallets/src/evm/utils/headers.ts @@ -1,6 +1,13 @@ -import pkg from "../../../../../package.json"; +import pkg from "../../../package.json"; import { getOperatingSystem } from "./os/os"; +// const pkg: { +// version: string; +// name: string; +// // this is on purpose because we can't import package.json as a module as it is outside rootDir +// // eslint-disable-next-line @typescript-eslint/no-var-requires, better-tree-shaking/no-top-level-side-effects +// } = require("../../../package.json"); + export function setAnalyticsHeaders(headers: Record) { const globals = getAnalyticsGlobals(); From 9bc5d8552d0de98222f3229a39af011d5d501b0a Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 31 Jan 2024 21:23:07 +0300 Subject: [PATCH 16/18] ver --- packages/unity-js-bridge/src/thirdweb-bridge.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/unity-js-bridge/src/thirdweb-bridge.ts b/packages/unity-js-bridge/src/thirdweb-bridge.ts index 01f9d60f0b6..39d06a2bef6 100644 --- a/packages/unity-js-bridge/src/thirdweb-bridge.ts +++ b/packages/unity-js-bridge/src/thirdweb-bridge.ts @@ -143,7 +143,7 @@ class ThirdwebBridge implements TWBridge { } (globalThis as any).X_SDK_NAME = "UnitySDK_WebGL"; (globalThis as any).X_SDK_PLATFORM = "unity"; - (globalThis as any).X_SDK_VERSION = "4.5.1"; + (globalThis as any).X_SDK_VERSION = "4.6.0"; (globalThis as any).X_SDK_OS = browser?.os ?? "unknown"; } this.initializedChain = chain; From b0f3bfd3c283fe4641bfe6f4e7c21984e4f0ce05 Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Wed, 31 Jan 2024 15:05:41 -0500 Subject: [PATCH 17/18] changeset --- .changeset/lemon-numbers-knock.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/lemon-numbers-knock.md diff --git a/.changeset/lemon-numbers-knock.md b/.changeset/lemon-numbers-knock.md new file mode 100644 index 00000000000..8997975c976 --- /dev/null +++ b/.changeset/lemon-numbers-knock.md @@ -0,0 +1,10 @@ +--- +"@thirdweb-dev/unity-js-bridge": patch +"@thirdweb-dev/react-native": patch +"@thirdweb-dev/storage": patch +"@thirdweb-dev/wallets": patch +"@thirdweb-dev/react": patch +"@thirdweb-dev/sdk": patch +--- + +Adds correct reporing headers From 3c414a9a8da5d5d674b368b1e0545de76123760f Mon Sep 17 00:00:00 2001 From: ikethirdweb Date: Wed, 31 Jan 2024 15:54:33 -0500 Subject: [PATCH 18/18] add contract headers --- packages/sdk/src/core/utils/headers.ts | 68 +++++++++++++++++++ .../sdk/src/evm/common/metadata-resolver.ts | 6 ++ packages/sdk/src/evm/constants/urls.ts | 19 +----- 3 files changed, 77 insertions(+), 16 deletions(-) create mode 100644 packages/sdk/src/core/utils/headers.ts diff --git a/packages/sdk/src/core/utils/headers.ts b/packages/sdk/src/core/utils/headers.ts new file mode 100644 index 00000000000..5fa707631ef --- /dev/null +++ b/packages/sdk/src/core/utils/headers.ts @@ -0,0 +1,68 @@ +import pkg from "../../../package.json"; +import { getOperatingSystem } from "./os"; + +export function setAnalyticsHeaders(headers: Record) { + const globals = getAnalyticsGlobals(); + + headers["x-sdk-version"] = globals.x_sdk_version; + headers["x-sdk-name"] = globals.x_sdk_name; + headers["x-sdk-platform"] = globals.x_sdk_platform; + headers["x-sdk-os"] = globals.x_sdk_os; +} + +export function setAnalyticsHeadersForXhr(xhr: XMLHttpRequest) { + const globals = getAnalyticsGlobals(); + + xhr.setRequestHeader("x-sdk-version", globals.x_sdk_version); + xhr.setRequestHeader("x-sdk-os", globals.x_sdk_os); + xhr.setRequestHeader("x-sdk-name", globals.x_sdk_name); + xhr.setRequestHeader("x-sdk-platform", globals.x_sdk_platform); + xhr.setRequestHeader("x-bundle-id", globals.app_bundle_id); +} + +export function getAnalyticsHeaders() { + const globals = getAnalyticsGlobals(); + return { + "x-sdk-version": globals.x_sdk_version, + "x-sdk-os": globals.x_sdk_os, + "x-sdk-name": globals.x_sdk_name, + "x-sdk-platform": globals.x_sdk_platform, + "x-bundle-id": globals.app_bundle_id, + }; +} + +export function getAnalyticsGlobals() { + if (typeof globalThis === "undefined") { + return { + x_sdk_name: pkg.name, + x_sdk_platform: getPlatform(), + x_sdk_version: pkg.version, + x_sdk_os: getOperatingSystem(), + app_bundle_id: undefined, + }; + } + + if ((globalThis as any).X_SDK_NAME === undefined) { + (globalThis as any).X_SDK_NAME = pkg.name; + (globalThis as any).X_SDK_PLATFORM = getPlatform(); + (globalThis as any).X_SDK_VERSION = pkg.version; + (globalThis as any).X_SDK_OS = getOperatingSystem(); + (globalThis as any).APP_BUNDLE_ID = undefined; + } + + return { + x_sdk_name: (globalThis as any).X_SDK_NAME, + x_sdk_platform: (globalThis as any).X_SDK_PLATFORM, + x_sdk_version: (globalThis as any).X_SDK_VERSION, + x_sdk_os: (globalThis as any).X_SDK_OS, + app_bundle_id: (globalThis as any).APP_BUNDLE_ID || "", // if react, this will be empty + }; +} + +export function getPlatform() { + return typeof navigator !== "undefined" && navigator.product === "ReactNative" + ? "mobile" + : typeof window !== "undefined" + ? "browser" + : "node"; +} diff --git a/packages/sdk/src/evm/common/metadata-resolver.ts b/packages/sdk/src/evm/common/metadata-resolver.ts index 9efbca8b15f..dde40856d0b 100644 --- a/packages/sdk/src/evm/common/metadata-resolver.ts +++ b/packages/sdk/src/evm/common/metadata-resolver.ts @@ -14,6 +14,7 @@ import { constructAbiFromBytecode } from "./feature-detection/getAllDetectedFeat import { SDKOptions } from "../schema/sdk-options"; import { Polygon } from "@thirdweb-dev/chains"; import { createLruCache } from "./utils"; +import { getAnalyticsHeaders } from "../../core/utils/headers"; const CONTRACT_RESOLVER_BASE_URL = "https://contract.thirdweb.com/metadata"; @@ -68,6 +69,11 @@ export async function fetchContractMetadataFromAddress( try { const response = await fetch( `${CONTRACT_RESOLVER_BASE_URL}/${chainId}/${address}`, + { + headers: { + ...getAnalyticsHeaders(), + }, + }, ); if (response.ok) { const resolvedData = await response.json(); diff --git a/packages/sdk/src/evm/constants/urls.ts b/packages/sdk/src/evm/constants/urls.ts index 760fb7cce90..18cb3ff7d8d 100644 --- a/packages/sdk/src/evm/constants/urls.ts +++ b/packages/sdk/src/evm/constants/urls.ts @@ -8,9 +8,8 @@ import { getValidChainRPCs } from "@thirdweb-dev/chains"; import type { Chain } from "@thirdweb-dev/chains"; import { providers } from "ethers"; import type { Signer } from "ethers"; -import pkg from "../../../package.json"; import { sha256HexSync } from "@thirdweb-dev/crypto"; -import { getOperatingSystem } from "../../core/utils/os"; +import { setAnalyticsHeaders } from "../../core/utils/headers"; /** * @internal @@ -252,6 +251,8 @@ export function getProviderFromRpcUrl( headers["x-authorize-wallet"] = "true"; } + setAnalyticsHeaders(headers); + const bundleId = typeof globalThis !== "undefined" && "APP_BUNDLE_ID" in globalThis ? ((globalThis as any).APP_BUNDLE_ID as string) @@ -259,20 +260,6 @@ export function getProviderFromRpcUrl( if (!rpcUrl.includes("bundleId")) { rpcUrl = rpcUrl + (bundleId ? `?bundleId=${bundleId}` : ""); } - - headers["x-sdk-version"] = - (globalThis as any).X_SDK_VERSION || pkg.version; - headers["x-sdk-name"] = (globalThis as any).X_SDK_NAME || pkg.name; - headers["x-sdk-platform"] = - (globalThis as any).X_SDK_PLATFORM || - (typeof navigator !== "undefined" && - navigator.product === "ReactNative") - ? "mobile" - : typeof window !== "undefined" - ? "browser" - : "node"; - headers["x-sdk-os"] = - (globalThis as any).X_SDK_OS || getOperatingSystem(); } const match = rpcUrl.match(/^(ws|http)s?:/i); // Try the JSON batch provider if available