diff --git a/package-lock.json b/package-lock.json index e3b2806e..93e9d5b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@commitlint/config-conventional": "^19.1.0", "@jest/types": "^29.6.3", "@rollup/plugin-commonjs": "^26.0.0", + "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-typescript": "^11.1.6", "@types/events": "^3.0.0", @@ -118,6 +119,15 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/eslint-parser": { "version": "7.25.1", "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.25.1.tgz", @@ -136,6 +146,15 @@ "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" } }, + "node_modules/@babel/eslint-parser/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.25.5", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz", @@ -167,6 +186,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-module-imports": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", @@ -675,18 +703,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/is-ignored/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@commitlint/lint": { "version": "19.2.2", "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.2.2.tgz", @@ -2033,6 +2049,26 @@ } } }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/plugin-node-resolve": { "version": "15.2.3", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", @@ -2733,18 +2769,6 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz", @@ -3211,6 +3235,15 @@ "node": ">=8" } }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/babel-plugin-jest-hoist": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", @@ -4651,6 +4684,15 @@ "node": "*" } }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-plugin-no-unsanitized": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.0.2.tgz", @@ -6300,18 +6342,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -7937,18 +7967,6 @@ "node": ">=8" } }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jest-snapshot/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8879,18 +8897,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -9955,12 +9961,15 @@ } }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/set-function-length": { @@ -10588,18 +10597,6 @@ } } }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", diff --git a/package.json b/package.json index 849fccaa..95226a58 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@rollup/plugin-commonjs": "^26.0.0", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-typescript": "^11.1.6", + "@rollup/plugin-json": "^6.1.0", "@types/events": "^3.0.0", "@types/jest": "^29.5.12", "@typescript-eslint/eslint-plugin": "^8.6.0", diff --git a/rollup.config.mjs b/rollup.config.mjs index a09ddd95..808710c3 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,6 +1,7 @@ import typescript from '@rollup/plugin-typescript'; import {nodeResolve} from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; +import json from '@rollup/plugin-json'; import {join} from 'path'; const srcDir = join(__dirname, 'src'); @@ -53,8 +54,9 @@ function buildConfig(format) { declaration: true, outDir: outDir, }), + json(), ], }; } -export default [buildConfig('cjs'), buildConfig('esm')]; \ No newline at end of file +export default [buildConfig('cjs'), buildConfig('esm')]; diff --git a/src/Constants.ts b/src/Constants.ts index b7f9568a..076030cb 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -36,3 +36,6 @@ export const Permissions = Object.freeze({ CREATE_INSTANT_INVITE: BigFlagUtils.getFlag(0), ADMINISTRATOR: BigFlagUtils.getFlag(3), }); + +export const UNKNOWN_VERSION_NUMBER = -1; +export const HANDSHAKE_SDK_VERSION_MINIUM_MOBILE_VERSION = 250; diff --git a/src/Discord.ts b/src/Discord.ts index 9ec03ffa..3c8b43d7 100644 --- a/src/Discord.ts +++ b/src/Discord.ts @@ -6,11 +6,17 @@ import commands, {Commands} from './commands'; import {v4 as uuidv4} from 'uuid'; import {SDKError} from './error'; import {EventSchema, ERROR, Events as RPCEvents} from './schema/events'; -import {Platform, RPCCloseCodes} from './Constants'; +import { + Platform, + RPCCloseCodes, + HANDSHAKE_SDK_VERSION_MINIUM_MOBILE_VERSION, + UNKNOWN_VERSION_NUMBER, +} from './Constants'; import getDefaultSdkConfiguration from './utils/getDefaultSdkConfiguration'; import {ConsoleLevel, consoleLevels, wrapConsoleMethod} from './utils/console'; import type {TSendCommand, TSendCommandPayload} from './schema/types'; import {IDiscordSDK, MaybeZodObjectArray, SdkConfiguration} from './interface'; +import {version as sdkVersion} from '../package.json'; export enum Opcodes { HANDSHAKE = 0, @@ -46,12 +52,22 @@ function getRPCServerSource(): [Window, string] { return [window.parent.opener ?? window.parent, !!document.referrer ? document.referrer : '*']; } +interface HandshakePayload { + v: number; + encoding: string; + client_id: string; + frame_id: string; + sdk_version?: string; +} + export class DiscordSDK implements IDiscordSDK { readonly clientId: string; readonly instanceId: string; readonly platform: Platform; readonly guildId: string | null; readonly channelId: string | null; + readonly sdkVersion: string = sdkVersion; + readonly mobileAppVersion: string | null = null; readonly configuration: SdkConfiguration; readonly source: Window | WindowProxy | null = null; readonly sourceOrigin: string = ''; @@ -135,6 +151,8 @@ export class DiscordSDK implements IDiscordSDK { this.guildId = urlParams.get('guild_id'); this.channelId = urlParams.get('channel_id'); + + this.mobileAppVersion = urlParams.get('mobile_app_version'); // END Capture URL Query Params [this.source, this.sourceOrigin] = getRPCServerSource(); @@ -195,19 +213,29 @@ export class DiscordSDK implements IDiscordSDK { } } + private parseMajorMobileVersion(): number { + if (this.mobileAppVersion && this.mobileAppVersion.includes('.')) { + try { + return parseInt(this.mobileAppVersion.split('.')[0]); + } catch { + return UNKNOWN_VERSION_NUMBER; + } + } + return UNKNOWN_VERSION_NUMBER; + } + private handshake() { - this.source?.postMessage( - [ - Opcodes.HANDSHAKE, - { - v: 1, - encoding: 'json', - client_id: this.clientId, - frame_id: this.frameId, - }, - ], - this.sourceOrigin, - ); + const handshakePayload: HandshakePayload = { + v: 1, + encoding: 'json', + client_id: this.clientId, + frame_id: this.frameId, + }; + const majorMobileVersion = this.parseMajorMobileVersion(); + if (this.platform === Platform.DESKTOP || majorMobileVersion >= HANDSHAKE_SDK_VERSION_MINIUM_MOBILE_VERSION) { + handshakePayload['sdk_version'] = this.sdkVersion; + } + this.source?.postMessage([Opcodes.HANDSHAKE, handshakePayload], this.sourceOrigin); } private addOnReadyListener() { diff --git a/src/__tests__/discordSdk.test.ts b/src/__tests__/discordSdk.test.ts index d594f22d..8706ec25 100644 --- a/src/__tests__/discordSdk.test.ts +++ b/src/__tests__/discordSdk.test.ts @@ -4,6 +4,7 @@ import {Opcodes} from '../Discord'; import {DiscordSDK, Events, Platform} from '../index'; import {DISPATCH} from '../schema/common'; +import {version as sdkVersion} from '../../package.json'; describe('DiscordSDK', () => { it('Can be constructed and await a "ready" event', async () => { @@ -48,6 +49,7 @@ describe('DiscordSDK', () => { encoding: 'json', frame_id: frame_id, v: 1, + sdk_version: sdkVersion, }, ], '*', diff --git a/src/interface.ts b/src/interface.ts index f3a596aa..8ea4ea78 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -22,6 +22,8 @@ export interface IDiscordSDK { readonly clientId: string; readonly instanceId: string; readonly platform: Platform; + readonly mobileAppVersion: string | null; + readonly sdkVersion: string; readonly commands: ReturnType; readonly configuration: SdkConfiguration; readonly channelId: string | null; diff --git a/src/mock.ts b/src/mock.ts index 9d325810..69126fd9 100644 --- a/src/mock.ts +++ b/src/mock.ts @@ -16,6 +16,8 @@ export class DiscordSDKMock implements IDiscordSDK { readonly configuration = getDefaultSdkConfiguration(); readonly source: Window | WindowProxy | null = null; readonly sourceOrigin: string = ''; + readonly sdkVersion = 'mock'; + readonly mobileAppVersion = 'unknown'; private frameId = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'; private eventBus = new EventEmitter(); diff --git a/tsconfig.json b/tsconfig.json index 28b5a7c9..0e48b723 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,8 @@ // https://app.asana.com/0/1202090529698493/1205406173366739/f "noUncheckedIndexedAccess": false, // TODO(gabemeola): Enable this check to ensure type imports are clear - "verbatimModuleSyntax": false + "verbatimModuleSyntax": false, + "resolveJsonModule": true }, "include": ["src"], "exclude": ["**/__tests__/*", "node_modules"]