@leonxin/meetgames 0.1.7 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/.agents/skills/meet-sdk-regression/SKILL.md +93 -0
  2. package/.cursor/mcp.example.json +16 -0
  3. package/.cursor/mcp.json +11 -0
  4. package/.cursor/skills/meetgames-mcp/SKILL.md +48 -0
  5. package/.vite/vitest/results.json +1 -0
  6. package/README.md +31 -8
  7. package/dist/aab-converter/aab-entry.d.ts +3 -0
  8. package/dist/aab-converter/aab-entry.d.ts.map +1 -0
  9. package/dist/aab-converter/aab-entry.js +49 -0
  10. package/dist/aab-converter/aab-entry.js.map +1 -0
  11. package/dist/aab-converter/apksExtractor.d.ts +2 -0
  12. package/dist/aab-converter/apksExtractor.d.ts.map +1 -0
  13. package/dist/aab-converter/apksExtractor.js +108 -0
  14. package/dist/aab-converter/apksExtractor.js.map +1 -0
  15. package/dist/aab-converter/bundletoolRunner.d.ts +15 -0
  16. package/dist/aab-converter/bundletoolRunner.d.ts.map +1 -0
  17. package/dist/aab-converter/bundletoolRunner.js +46 -0
  18. package/dist/aab-converter/bundletoolRunner.js.map +1 -0
  19. package/dist/aab-converter/cliArgs.d.ts +27 -0
  20. package/dist/aab-converter/cliArgs.d.ts.map +1 -0
  21. package/dist/aab-converter/cliArgs.js +170 -0
  22. package/dist/aab-converter/cliArgs.js.map +1 -0
  23. package/dist/aab-converter/convertAabToApk.d.ts +7 -0
  24. package/dist/aab-converter/convertAabToApk.d.ts.map +1 -0
  25. package/dist/aab-converter/convertAabToApk.js +69 -0
  26. package/dist/aab-converter/convertAabToApk.js.map +1 -0
  27. package/dist/aab-converter/resourcePaths.d.ts +4 -0
  28. package/dist/aab-converter/resourcePaths.d.ts.map +1 -0
  29. package/dist/aab-converter/resourcePaths.js +42 -0
  30. package/dist/aab-converter/resourcePaths.js.map +1 -0
  31. package/dist/aab-converter/signingOptions.d.ts +9 -0
  32. package/dist/aab-converter/signingOptions.d.ts.map +1 -0
  33. package/dist/aab-converter/signingOptions.js +21 -0
  34. package/dist/aab-converter/signingOptions.js.map +1 -0
  35. package/dist/aab-converter/types.d.ts +24 -0
  36. package/dist/aab-converter/types.d.ts.map +1 -0
  37. package/dist/aab-converter/types.js +2 -0
  38. package/dist/aab-converter/types.js.map +1 -0
  39. package/dist/android/adapter.d.ts.map +1 -1
  40. package/dist/android/adapter.js +2 -2
  41. package/dist/android/adapter.js.map +1 -1
  42. package/dist/android/detect.d.ts +2 -2
  43. package/dist/android/detect.d.ts.map +1 -1
  44. package/dist/android/detect.js +36 -8
  45. package/dist/android/detect.js.map +1 -1
  46. package/dist/android/meetSdkRemoteGradle.d.ts +0 -3
  47. package/dist/android/meetSdkRemoteGradle.d.ts.map +1 -1
  48. package/dist/android/meetSdkRemoteGradle.js +13 -20
  49. package/dist/android/meetSdkRemoteGradle.js.map +1 -1
  50. package/dist/cli.d.ts.map +1 -1
  51. package/dist/cli.js +157 -31
  52. package/dist/cli.js.map +1 -1
  53. package/dist/config/meetSdkDefaultConfig.d.ts +19 -2
  54. package/dist/config/meetSdkDefaultConfig.d.ts.map +1 -1
  55. package/dist/config/meetSdkDefaultConfig.js +67 -5
  56. package/dist/config/meetSdkDefaultConfig.js.map +1 -1
  57. package/dist/config/meetSdkIosConfig.d.ts +21 -0
  58. package/dist/config/meetSdkIosConfig.d.ts.map +1 -0
  59. package/dist/config/meetSdkIosConfig.js +66 -0
  60. package/dist/config/meetSdkIosConfig.js.map +1 -0
  61. package/dist/config/meetSdkRemoteConfig.d.ts +19 -11
  62. package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
  63. package/dist/config/meetSdkRemoteConfig.js +89 -69
  64. package/dist/config/meetSdkRemoteConfig.js.map +1 -1
  65. package/dist/config/topsdkFeatureModules.d.ts +5 -0
  66. package/dist/config/topsdkFeatureModules.d.ts.map +1 -1
  67. package/dist/config/topsdkFeatureModules.js +26 -0
  68. package/dist/config/topsdkFeatureModules.js.map +1 -1
  69. package/dist/contracts/types.d.ts +19 -6
  70. package/dist/contracts/types.d.ts.map +1 -1
  71. package/dist/core/doctor.d.ts +17 -0
  72. package/dist/core/doctor.d.ts.map +1 -0
  73. package/dist/core/doctor.js +444 -0
  74. package/dist/core/doctor.js.map +1 -0
  75. package/dist/core/pipeline.d.ts.map +1 -1
  76. package/dist/core/pipeline.js +0 -15
  77. package/dist/core/pipeline.js.map +1 -1
  78. package/dist/core/platform.d.ts +12 -0
  79. package/dist/core/platform.d.ts.map +1 -0
  80. package/dist/core/platform.js +40 -0
  81. package/dist/core/platform.js.map +1 -0
  82. package/dist/core/reporter.js +1 -1
  83. package/dist/core/reporter.js.map +1 -1
  84. package/dist/core/workspace.d.ts +2 -2
  85. package/dist/core/workspace.d.ts.map +1 -1
  86. package/dist/core/workspace.js +4 -5
  87. package/dist/core/workspace.js.map +1 -1
  88. package/dist/index.d.ts +3 -1
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +3 -1
  91. package/dist/index.js.map +1 -1
  92. package/dist/ios/channelConfig.d.ts +1 -0
  93. package/dist/ios/channelConfig.d.ts.map +1 -1
  94. package/dist/ios/channelConfig.js +82 -0
  95. package/dist/ios/channelConfig.js.map +1 -1
  96. package/dist/ios/codeUtils.d.ts +1 -0
  97. package/dist/ios/codeUtils.d.ts.map +1 -1
  98. package/dist/ios/codeUtils.js +11 -2
  99. package/dist/ios/codeUtils.js.map +1 -1
  100. package/dist/ios/detect.d.ts +2 -2
  101. package/dist/ios/detect.d.ts.map +1 -1
  102. package/dist/ios/detect.js +49 -10
  103. package/dist/ios/detect.js.map +1 -1
  104. package/dist/ios/entitlements.d.ts +4 -0
  105. package/dist/ios/entitlements.d.ts.map +1 -0
  106. package/dist/ios/entitlements.js +53 -0
  107. package/dist/ios/entitlements.js.map +1 -0
  108. package/dist/ios/fileManager.d.ts.map +1 -1
  109. package/dist/ios/fileManager.js +3 -2
  110. package/dist/ios/fileManager.js.map +1 -1
  111. package/dist/ios/infoPlist.d.ts +1 -1
  112. package/dist/ios/infoPlist.d.ts.map +1 -1
  113. package/dist/ios/infoPlist.js.map +1 -1
  114. package/dist/ios/integrate.d.ts.map +1 -1
  115. package/dist/ios/integrate.js +211 -36
  116. package/dist/ios/integrate.js.map +1 -1
  117. package/dist/ios/pbxprojEditor.d.ts +2 -0
  118. package/dist/ios/pbxprojEditor.d.ts.map +1 -1
  119. package/dist/ios/pbxprojEditor.js +179 -1
  120. package/dist/ios/pbxprojEditor.js.map +1 -1
  121. package/dist/ios/pluginConfig.d.ts +1 -0
  122. package/dist/ios/pluginConfig.d.ts.map +1 -1
  123. package/dist/ios/pluginConfig.js +36 -4
  124. package/dist/ios/pluginConfig.js.map +1 -1
  125. package/dist/ios/sdkBundle.d.ts +1 -1
  126. package/dist/ios/sdkBundle.d.ts.map +1 -1
  127. package/dist/ios/sdkBundle.js +7 -5
  128. package/dist/ios/sdkBundle.js.map +1 -1
  129. package/dist/ios/template.d.ts +1 -0
  130. package/dist/ios/template.d.ts.map +1 -1
  131. package/dist/ios/template.js +14 -1
  132. package/dist/ios/template.js.map +1 -1
  133. package/dist/ios/types.d.ts +2 -2
  134. package/dist/ios/types.d.ts.map +1 -1
  135. package/dist/mcp/server.d.ts.map +1 -1
  136. package/dist/mcp/server.js +14 -13
  137. package/dist/mcp/server.js.map +1 -1
  138. package/dist/mcp/service.d.ts +8 -6
  139. package/dist/mcp/service.d.ts.map +1 -1
  140. package/dist/mcp/service.js +34 -14
  141. package/dist/mcp/service.js.map +1 -1
  142. package/dist/ops/handlers.d.ts.map +1 -1
  143. package/dist/ops/handlers.js +10 -4
  144. package/dist/ops/handlers.js.map +1 -1
  145. package/dist/remote/sdkHomeDownload.d.ts +65 -0
  146. package/dist/remote/sdkHomeDownload.d.ts.map +1 -0
  147. package/dist/remote/sdkHomeDownload.js +208 -0
  148. package/dist/remote/sdkHomeDownload.js.map +1 -0
  149. package/dist/remote/topsdkDownloadSdkConfig.d.ts.map +1 -1
  150. package/dist/remote/topsdkDownloadSdkConfig.js +11 -1
  151. package/dist/remote/topsdkDownloadSdkConfig.js.map +1 -1
  152. package/dist/shared/errors.d.ts +7 -0
  153. package/dist/shared/errors.d.ts.map +1 -0
  154. package/dist/shared/errors.js +16 -0
  155. package/dist/shared/errors.js.map +1 -0
  156. package/dist/shared/fileUtils.d.ts +5 -0
  157. package/dist/shared/fileUtils.d.ts.map +1 -0
  158. package/dist/shared/fileUtils.js +35 -0
  159. package/dist/shared/fileUtils.js.map +1 -0
  160. package/dist/shared/logger.d.ts +10 -0
  161. package/dist/shared/logger.d.ts.map +1 -0
  162. package/dist/shared/logger.js +37 -0
  163. package/dist/shared/logger.js.map +1 -0
  164. package/dist/shared/pathUtils.d.ts +4 -0
  165. package/dist/shared/pathUtils.d.ts.map +1 -0
  166. package/dist/shared/pathUtils.js +22 -0
  167. package/dist/shared/pathUtils.js.map +1 -0
  168. package/dist/shared/processRunner.d.ts +12 -0
  169. package/dist/shared/processRunner.d.ts.map +1 -0
  170. package/dist/shared/processRunner.js +31 -0
  171. package/dist/shared/processRunner.js.map +1 -0
  172. package/docs/AAB_CONVERTER_CLI_PLAN.md +392 -0
  173. package/docs/API.md +246 -32
  174. package/docs/CLI.md +292 -0
  175. package/docs/INTEGRATION.md +116 -0
  176. package/docs/MCP.md +86 -0
  177. package/docs/README.md +19 -10
  178. package/docs/{api → archive/api}/downloadSDKConfig.md +8 -6
  179. package/docs/{api → archive/api}/getChannelConfig-meetgames.md +1 -1
  180. package/docs/archive/ios-migration.md +2139 -0
  181. package/docs/{ → archive/product/}/346/212/200/346/234/257/346/226/271/346/241/210/350/260/203/347/240/224.md +7 -7
  182. package/docs/{ → archive/product/}/351/234/200/346/261/202/346/226/207/346/241/243.md +15 -14
  183. package/logs/convert-20260622-155037.log +5 -0
  184. package/logs/convert-20260622-155226.log +6 -0
  185. package/meetsdk-android.json +2 -1
  186. package/meetsdk-ios.json +15 -0
  187. package/package.json +10 -35
  188. package/scripts/package-aab-cli-win.mjs +193 -0
  189. package/src/aab-converter/aab-entry.ts +48 -0
  190. package/src/aab-converter/apksExtractor.ts +119 -0
  191. package/src/aab-converter/bundletoolRunner.ts +63 -0
  192. package/src/aab-converter/cliArgs.ts +194 -0
  193. package/src/aab-converter/convertAabToApk.ts +81 -0
  194. package/src/aab-converter/resourcePaths.ts +43 -0
  195. package/src/aab-converter/signingOptions.ts +29 -0
  196. package/src/aab-converter/types.ts +26 -0
  197. package/src/android/adapter.ts +9 -0
  198. package/src/android/assembleIntegrationJson.ts +33 -0
  199. package/src/android/detect.ts +132 -0
  200. package/src/android/downloadGoogleServicesJson.ts +56 -0
  201. package/src/android/gradle.ts +116 -0
  202. package/src/android/manifest.ts +50 -0
  203. package/src/android/meetSdkRemoteGradle.ts +837 -0
  204. package/src/cli.ts +488 -0
  205. package/src/config/fetchConfigWrite.ts +30 -0
  206. package/src/config/loadAndroidIntegration.ts +41 -0
  207. package/src/config/loadManifest.ts +40 -0
  208. package/src/config/meetSdkDefaultConfig.ts +99 -0
  209. package/src/config/meetSdkIosConfig.ts +87 -0
  210. package/src/config/meetSdkRemoteConfig.ts +1211 -0
  211. package/src/config/topsdkFeatureModules.ts +92 -0
  212. package/src/contracts/types.ts +121 -0
  213. package/src/core/doctor.ts +485 -0
  214. package/src/core/patch.ts +64 -0
  215. package/src/core/pipeline.ts +107 -0
  216. package/src/core/platform.ts +47 -0
  217. package/src/core/previewPatches.ts +23 -0
  218. package/src/core/reporter.ts +24 -0
  219. package/src/core/workspace.ts +29 -0
  220. package/src/entry.ts +7 -0
  221. package/src/index.ts +133 -0
  222. package/src/ios/channelConfig.ts +128 -0
  223. package/src/ios/codeUtils.ts +160 -0
  224. package/src/ios/detect.ts +105 -0
  225. package/src/ios/entitlements.ts +61 -0
  226. package/src/ios/fileManager.ts +48 -0
  227. package/src/ios/infoPlist.ts +55 -0
  228. package/src/ios/integrate.ts +516 -0
  229. package/src/ios/pbxprojEditor.ts +383 -0
  230. package/src/ios/pluginConfig.ts +97 -0
  231. package/src/ios/reserved.ts +8 -0
  232. package/src/ios/sdkBundle.ts +36 -0
  233. package/src/ios/template.ts +36 -0
  234. package/src/ios/types.ts +65 -0
  235. package/src/mcp/server.ts +170 -0
  236. package/src/mcp/service.ts +222 -0
  237. package/src/mcp-entry.ts +7 -0
  238. package/src/ops/fileStore.ts +56 -0
  239. package/src/ops/handlers.ts +304 -0
  240. package/src/remote/fetchJson.ts +22 -0
  241. package/src/remote/sdkHomeDownload.ts +274 -0
  242. package/src/remote/topsdkDownloadSdkConfig.ts +93 -0
  243. package/src/remote/topsdkGetSdkConfig.ts +122 -0
  244. package/src/remote/topsdkSign.ts +10 -0
  245. package/src/shared/errors.ts +16 -0
  246. package/src/shared/fileUtils.ts +41 -0
  247. package/src/shared/logger.ts +49 -0
  248. package/src/shared/pathUtils.ts +24 -0
  249. package/src/shared/processRunner.ts +43 -0
  250. package/test-projects/README.md +51 -0
  251. package/test-projects/_preview/pipeline.patch +281 -0
  252. package/tests/aab-converter.test.ts +213 -0
  253. package/tests/assemble.test.ts +12 -0
  254. package/tests/doctor.test.ts +89 -0
  255. package/tests/downloadGoogleServicesJson.test.ts +47 -0
  256. package/tests/fetch-remote.test.ts +23 -0
  257. package/tests/fetchConfigOverrides.test.ts +28 -0
  258. package/tests/fetchConfigWrite.test.ts +54 -0
  259. package/tests/gradle.test.ts +33 -0
  260. package/tests/integration-json.test.ts +29 -0
  261. package/tests/ios.codeUtils.test.ts +23 -0
  262. package/tests/ios.sdkBundle.test.ts +16 -0
  263. package/tests/loadManifest.test.ts +15 -0
  264. package/tests/manifest-xml.test.ts +30 -0
  265. package/tests/mcp.e2e.ts +217 -0
  266. package/tests/mcp.service.test.ts +53 -0
  267. package/tests/meetSdkRemoteConfig.test.ts +456 -0
  268. package/tests/meetSdkRemoteGradle.test.ts +414 -0
  269. package/tests/pipeline.android.test.ts +96 -0
  270. package/tests/pipeline.integration-json.test.ts +58 -0
  271. package/tests/pipeline.ios.test.ts +385 -0
  272. package/tests/pipeline.preview.patch.test.ts +85 -0
  273. package/tests/platformSelection.test.ts +77 -0
  274. package/tests/sdkHomeDownload.test.ts +124 -0
  275. package/tests/sdkVersionConfig.test.ts +130 -0
  276. package/tests/test-projects-hosts.test.ts +78 -0
  277. package/tests/topsdk.test.ts +53 -0
  278. package/tests/topsdkDownloadSdkConfig.test.ts +81 -0
  279. package/tests/topsdkFeatureModules.test.ts +116 -0
  280. package/tsconfig.json +19 -0
  281. package/vitest.config.ts +9 -0
  282. package/vitest.mcp.config.ts +11 -0
  283. package/bundled/android/sample.txt +0 -1
  284. package/docs/ANDROID.md +0 -133
  285. package/docs/CURSOR-MCP-SETUP.md +0 -72
  286. package/docs/MCP-SKILL.md +0 -63
  287. package/fixtures/api-samples/getChannelConfig-meetgames.sample.json +0 -123
  288. package/fixtures/meetsdk-remote-config.download-shape.json +0 -20
  289. package/fixtures/meetsdk-remote-config.mock.json +0 -69
  290. package/fixtures/recipes/android-default.fixture.yaml +0 -15
  291. package/fixtures/recipes/android-integration.fixture.json +0 -29
  292. package/fixtures/topsdk-config-reference.json +0 -39
  293. /package/docs/{api → archive/api}/getSDKConfig.md +0 -0
@@ -0,0 +1,304 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import type { StepResult, WorkspaceContext } from "../contracts/types.js";
4
+ import { applicationModuleRelPath } from "../android/detect.js";
5
+ import { updateModuleBuildGradleDependencies, updateRootBuildGradleRepositories } from "../android/gradle.js";
6
+ import {
7
+ detectModuleGradlePluginStyle,
8
+ updateModuleBuildGradleMeetSdkRemote,
9
+ updateRootBuildGradleMeetSdkRemote,
10
+ updateSettingsGradleMeetSdkRemote,
11
+ } from "../android/meetSdkRemoteGradle.js";
12
+ import {
13
+ MEETSDK_REMOTE_CONFIG_FILENAME,
14
+ applyMeetSdkDefaultConfig,
15
+ collectMeetSdkRemoteApplyPlugins,
16
+ collectMeetSdkRemoteBuildscriptClasspaths,
17
+ collectMeetSdkRemoteBuildscriptRepositories,
18
+ collectMeetSdkRemotePluginsDslForModule,
19
+ collectMeetSdkRemotePluginsDslForRoot,
20
+ collectMeetSdkRemoteRepositories,
21
+ tryParseAsMeetSdkRemoteConfig,
22
+ validateMeetSdkRemoteConfig,
23
+ } from "../config/meetSdkRemoteConfig.js";
24
+ import { loadMeetSdkDefaultConfigWithLatestAndroidVersion } from "../config/meetSdkDefaultConfig.js";
25
+ import { insertPermissions } from "../android/manifest.js";
26
+ import type { TextFileStore } from "./fileStore.js";
27
+
28
+ function ok(changed: string[], warnings: string[] = []): StepResult {
29
+ return { ok: true, changedFiles: changed, warnings, errors: [] };
30
+ }
31
+
32
+ function fail(errors: string[]): StepResult {
33
+ return { ok: false, changedFiles: [], warnings: [], errors };
34
+ }
35
+
36
+ function readRootBuildGradle(projectRoot: string): { rel: string; abs: string } | null {
37
+ const a = path.join(projectRoot, "build.gradle");
38
+ if (fs.existsSync(a)) return { rel: "build.gradle", abs: a };
39
+ const b = path.join(projectRoot, "build.gradle.kts");
40
+ if (fs.existsSync(b)) return { rel: "build.gradle.kts", abs: b };
41
+ return null;
42
+ }
43
+
44
+ function readSettingsGradle(projectRoot: string): { rel: string; abs: string } | null {
45
+ const a = path.join(projectRoot, "settings.gradle");
46
+ if (fs.existsSync(a)) return { rel: "settings.gradle", abs: a };
47
+ const b = path.join(projectRoot, "settings.gradle.kts");
48
+ if (fs.existsSync(b)) return { rel: "settings.gradle.kts", abs: b };
49
+ return null;
50
+ }
51
+
52
+ function moduleGradleRel(ctx: WorkspaceContext): string | null {
53
+ if (!ctx.android?.ok) return null;
54
+ const moduleDir = ctx.android.moduleDir;
55
+ const a = path.join(moduleDir, "build.gradle");
56
+ if (fs.existsSync(a)) return path.relative(ctx.projectRoot, a).split(path.sep).join("/");
57
+ const b = path.join(moduleDir, "build.gradle.kts");
58
+ if (fs.existsSync(b)) return path.relative(ctx.projectRoot, b).split(path.sep).join("/");
59
+ return null;
60
+ }
61
+
62
+ export interface BinaryCopy {
63
+ fromAbs: string;
64
+ relTo: string;
65
+ }
66
+
67
+ export type OpHandler = (
68
+ ctx: WorkspaceContext,
69
+ store: TextFileStore,
70
+ args: Record<string, unknown>,
71
+ dryRun: boolean,
72
+ binaryCopies: BinaryCopy[]
73
+ ) => StepResult | Promise<StepResult>;
74
+
75
+ export const opHandlers: Record<string, OpHandler> = {
76
+ /**
77
+ * Reads meetsdk-remote-config.json (or args.configFile) and writes TOPSDK-managed blocks
78
+ * into root `build.gradle` (repositories) and the app module `build.gradle` (resValue + TOPSDK dependencies),
79
+ * matching sdk-integration-agent `gradleEditor.js` behavior.
80
+ */
81
+ "gradle.applyMeetSdkRemoteConfig": async (ctx, store, args, _dry, _bc) => {
82
+ if (!ctx.android?.ok) return fail(["android application module not detected"]);
83
+ const configRel =
84
+ typeof args.configFile === "string" && args.configFile.trim()
85
+ ? args.configFile.trim().replace(/\\/g, "/")
86
+ : MEETSDK_REMOTE_CONFIG_FILENAME;
87
+ const abs = path.join(ctx.projectRoot, configRel);
88
+ if (!fs.existsSync(abs)) return fail([`meet sdk remote config not found: ${configRel}`]);
89
+ let raw: unknown;
90
+ try {
91
+ raw = JSON.parse(fs.readFileSync(abs, "utf8")) as unknown;
92
+ } catch {
93
+ return fail([`failed to parse JSON: ${configRel}`]);
94
+ }
95
+ const parsed = tryParseAsMeetSdkRemoteConfig(raw);
96
+ if (!parsed) return fail([`not a meetsdk-remote-config document: ${configRel}`]);
97
+ const v = validateMeetSdkRemoteConfig(parsed);
98
+ if (!v.ok) {
99
+ return fail([`meetsdk-remote-config validation failed (${configRel}): ${v.missing.join(", ")}`]);
100
+ }
101
+ const resolved = applyMeetSdkDefaultConfig(
102
+ parsed,
103
+ await loadMeetSdkDefaultConfigWithLatestAndroidVersion({
104
+ sdkHomeApiBaseUrl: ctx.sdkHomeApiBaseUrl,
105
+ packageRoot: ctx.packageRoot,
106
+ })
107
+ );
108
+
109
+ const repositories = collectMeetSdkRemoteRepositories(resolved);
110
+ const buildscriptRepositories = collectMeetSdkRemoteBuildscriptRepositories(resolved);
111
+ const buildscriptClasspaths = collectMeetSdkRemoteBuildscriptClasspaths(resolved);
112
+
113
+ const modRel = moduleGradleRel(ctx);
114
+ if (!modRel) return fail(["module build.gradle not found"]);
115
+ if (modRel.endsWith(".kts")) {
116
+ return fail(["gradle.applyMeetSdkRemoteConfig: module build.gradle.kts not supported in MVP"]);
117
+ }
118
+ const modBefore = store.read(modRel);
119
+ const modulePluginStyle = detectModuleGradlePluginStyle(modBefore);
120
+ const usesPluginsDsl = modulePluginStyle === "plugins-dsl";
121
+
122
+ const changed: string[] = [];
123
+ const warnings: string[] = [];
124
+
125
+ const settings = readSettingsGradle(ctx.projectRoot);
126
+ let repositoriesWrittenInSettings = false;
127
+ if (settings && repositories.length > 0) {
128
+ if (settings.rel.endsWith(".kts")) {
129
+ warnings.push("gradle.applyMeetSdkRemoteConfig: settings.gradle.kts repositories edit not supported yet; fallback to root build.gradle");
130
+ } else {
131
+ const settingsBefore = store.read(settings.rel);
132
+ const settingsU = updateSettingsGradleMeetSdkRemote(settingsBefore, repositories);
133
+ if (!settingsU.ok) {
134
+ warnings.push(settingsU.error);
135
+ } else {
136
+ store.write(settings.rel, settingsU.content);
137
+ if (settingsU.changed) changed.push(settings.rel);
138
+ warnings.push(...settingsU.warnings);
139
+ repositoriesWrittenInSettings = true;
140
+ }
141
+ }
142
+ }
143
+
144
+ const root = readRootBuildGradle(ctx.projectRoot);
145
+ if (!root) {
146
+ if (!repositoriesWrittenInSettings) return fail(["root build.gradle not found"]);
147
+ if (buildscriptRepositories.length > 0 || buildscriptClasspaths.length > 0) {
148
+ warnings.push("root build.gradle not found; skipped buildscript repositories/classpaths");
149
+ }
150
+ } else {
151
+ if (root.rel.endsWith(".kts")) {
152
+ return fail(["gradle.applyMeetSdkRemoteConfig: root build.gradle.kts not supported in MVP"]);
153
+ }
154
+ const rootBefore = store.read(root.rel);
155
+ const rootU = updateRootBuildGradleMeetSdkRemote(rootBefore, {
156
+ repositories: repositoriesWrittenInSettings ? [] : repositories,
157
+ buildscriptRepositories: usesPluginsDsl ? [] : buildscriptRepositories,
158
+ buildscriptClasspaths: usesPluginsDsl ? [] : buildscriptClasspaths,
159
+ rootPluginsDsl: usesPluginsDsl ? collectMeetSdkRemotePluginsDslForRoot(resolved) : [],
160
+ stripBuildscriptClasspaths: usesPluginsDsl ? buildscriptClasspaths : [],
161
+ stripPluginsDslIds: usesPluginsDsl ? [] : collectMeetSdkRemotePluginsDslForModule(resolved).map((p) => p.id),
162
+ });
163
+ if (!rootU.ok) return fail([rootU.error]);
164
+ store.write(root.rel, rootU.content);
165
+ if (rootU.changed) changed.push(root.rel);
166
+ warnings.push(...rootU.warnings);
167
+ }
168
+
169
+ const modU = updateModuleBuildGradleMeetSdkRemote(modBefore, resolved, {
170
+ style: modulePluginStyle,
171
+ applyPlugins: usesPluginsDsl ? [] : collectMeetSdkRemoteApplyPlugins(resolved),
172
+ pluginsDsl: usesPluginsDsl ? collectMeetSdkRemotePluginsDslForModule(resolved) : [],
173
+ });
174
+ if (!modU.ok) return fail([modU.error]);
175
+
176
+ store.write(modRel, modU.content);
177
+ if (modU.changed) changed.push(modRel);
178
+ return ok(changed, [...warnings, ...modU.warnings]);
179
+ },
180
+
181
+ "gradle.insertRepositories": (ctx, store, args, _dry, _bc) => {
182
+ if (!ctx.android?.ok) return fail(["android application module not detected"]);
183
+ const urls = args.urls;
184
+ if (!Array.isArray(urls) || !urls.every((u) => typeof u === "string")) {
185
+ return fail(["gradle.insertRepositories requires args.urls: string[]"]);
186
+ }
187
+ const root = readRootBuildGradle(ctx.projectRoot);
188
+ if (!root) return fail(["root build.gradle not found"]);
189
+ if (root.rel.endsWith(".kts")) return fail(["gradle.insertRepositories: build.gradle.kts not supported in MVP"]);
190
+ const rel = root.rel;
191
+ const before = store.read(rel);
192
+ const res = updateRootBuildGradleRepositories(before, urls as string[]);
193
+ if (!res.ok) return fail([res.error]);
194
+ store.write(rel, res.content);
195
+ const changed = res.changed ? [rel] : [];
196
+ return ok(changed, res.warnings);
197
+ },
198
+
199
+ "gradle.addDependency": (ctx, store, args, _dry, _bc) => {
200
+ if (!ctx.android?.ok) return fail(["android application module not detected"]);
201
+ const modRel = moduleGradleRel(ctx);
202
+ if (!modRel) return fail(["module build.gradle not found"]);
203
+ if (modRel.endsWith(".kts")) return fail(["gradle.addDependency: build.gradle.kts not supported in MVP"]);
204
+ let depLines: string[] = [];
205
+ if (Array.isArray(args.lines)) {
206
+ depLines = (args.lines as unknown[]).map((x) => String(x));
207
+ } else if (typeof args.coordinate === "string") {
208
+ const cfg = typeof args.configuration === "string" ? args.configuration : "implementation";
209
+ depLines = [` ${cfg} '${args.coordinate}'`];
210
+ } else {
211
+ return fail(["gradle.addDependency requires args.lines: string[] or args.coordinate: string"]);
212
+ }
213
+ const before = store.read(modRel);
214
+ const res = updateModuleBuildGradleDependencies(before, depLines);
215
+ if (!res.ok) return fail([res.error]);
216
+ store.write(modRel, res.content);
217
+ return ok(res.changed ? [modRel] : [], res.warnings);
218
+ },
219
+
220
+ "android.manifest.insertPermissions": (ctx, store, args, _dry, _bc) => {
221
+ if (!ctx.android?.ok) return fail(["android application module not detected"]);
222
+ const perms = args.permissions;
223
+ if (!Array.isArray(perms) || !perms.every((p) => typeof p === "string")) {
224
+ return fail(["android.manifest.insertPermissions requires args.permissions: string[]"]);
225
+ }
226
+ const rel = path.relative(ctx.projectRoot, ctx.android.manifestPath).split(path.sep).join("/");
227
+ const before = store.read(rel);
228
+ const { content, changed } = insertPermissions(before, perms as string[]);
229
+ store.write(rel, content);
230
+ return ok(changed ? [rel] : []);
231
+ },
232
+
233
+ "ios.integrateTopSdk": async (ctx, store, args, dryRun, binaryCopies) => {
234
+ if (!ctx.ios?.ok) return fail(["iOS project not detected"]);
235
+ const { runIosIntegrateTopSdk } = await import("../ios/integrate.js");
236
+ const targetName = typeof args.targetName === "string" ? args.targetName : undefined;
237
+ const executeAppDelegate = args.executeAppDelegate === false ? false : true;
238
+ return runIosIntegrateTopSdk(ctx, store, binaryCopies, {
239
+ dryRun,
240
+ targetName,
241
+ executeAppDelegate,
242
+ });
243
+ },
244
+
245
+ "files.downloadGoogleServicesJson": async (ctx, _store, args, dryRun, _bc) => {
246
+ if (!ctx.android?.ok) return fail(["android application module not detected"]);
247
+ const configRel =
248
+ typeof args.configFile === "string" && args.configFile.trim()
249
+ ? args.configFile.trim().replace(/\\/g, "/")
250
+ : MEETSDK_REMOTE_CONFIG_FILENAME;
251
+ const abs = path.join(ctx.projectRoot, configRel);
252
+ if (!fs.existsSync(abs)) return fail([`meet sdk remote config not found: ${configRel}`]);
253
+ let raw: unknown;
254
+ try {
255
+ raw = JSON.parse(fs.readFileSync(abs, "utf8")) as unknown;
256
+ } catch {
257
+ return fail([`failed to parse JSON: ${configRel}`]);
258
+ }
259
+ const parsed = tryParseAsMeetSdkRemoteConfig(raw);
260
+ if (!parsed) return fail([`not a meetsdk-remote-config document: ${configRel}`]);
261
+ const resolved = applyMeetSdkDefaultConfig(
262
+ parsed,
263
+ await loadMeetSdkDefaultConfigWithLatestAndroidVersion({
264
+ sdkHomeApiBaseUrl: ctx.sdkHomeApiBaseUrl,
265
+ packageRoot: ctx.packageRoot,
266
+ })
267
+ );
268
+ const firebase = resolved.sdkModules.analytics.firebase;
269
+ if (typeof firebase !== "object" || firebase === null) {
270
+ return ok([], ["files.downloadGoogleServicesJson: analytics.firebase not enabled, skipped"]);
271
+ }
272
+
273
+ const moduleRel = applicationModuleRelPath(ctx.projectRoot, ctx.android);
274
+
275
+ const { downloadGoogleServicesJson } = await import("../android/downloadGoogleServicesJson.js");
276
+ const result = await downloadGoogleServicesJson(ctx.projectRoot, firebase, {
277
+ applicationModuleRelPath: moduleRel,
278
+ dryRun,
279
+ });
280
+ if (!result.ok) return fail([`files.downloadGoogleServicesJson: ${result.error}`]);
281
+ return ok(dryRun ? [] : [result.relPath], [
282
+ dryRun
283
+ ? `would download google-services.json to ${result.relPath}`
284
+ : `downloaded google-services.json to ${result.relPath}`,
285
+ ]);
286
+ },
287
+
288
+ "files.copyBundled": (ctx, _store, args, _dry, binaryCopies) => {
289
+ const from = args.from;
290
+ const to = args.to;
291
+ if (typeof from !== "string" || typeof to !== "string") {
292
+ return fail(["files.copyBundled requires args.from and args.to as strings"]);
293
+ }
294
+ const fromAbs = path.join(ctx.packageRoot, "bundled", from);
295
+ if (!fs.existsSync(fromAbs)) return fail([`bundled source not found: bundled/${from}`]);
296
+ const relTo = to.replace(/\\/g, "/");
297
+ binaryCopies.push({ fromAbs, relTo });
298
+ return ok([relTo]);
299
+ },
300
+ };
301
+
302
+ export function getOpHandler(name: string): OpHandler | undefined {
303
+ return opHandlers[name];
304
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * GET a URL and parse JSON (Node 18+ fetch).
3
+ */
4
+ export async function fetchJsonGet(url: string, init?: RequestInit): Promise<unknown> {
5
+ const res = await fetch(url, {
6
+ redirect: "follow",
7
+ ...init,
8
+ headers: {
9
+ Accept: "application/json",
10
+ ...(init?.headers ?? {}),
11
+ },
12
+ });
13
+ if (!res.ok) {
14
+ throw new Error(`GET ${url} failed: HTTP ${res.status}`);
15
+ }
16
+ const text = await res.text();
17
+ try {
18
+ return JSON.parse(text) as unknown;
19
+ } catch {
20
+ throw new Error("Remote response is not valid JSON");
21
+ }
22
+ }
@@ -0,0 +1,274 @@
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { defaultBundledIosSdkRoot } from "../ios/sdkBundle.js";
5
+ import { syncMeetSdkIosVersionToConfig } from "../config/meetSdkIosConfig.js";
6
+
7
+ export const defaultSdkHomeApiBaseUrl = "https://business-api.meetgames.com";
8
+ export const DEFAULT_IOS_SDK_PLUGINS = ["guest", "facebook", "google", "apple", "apple_pay", "dataAppsFlyer"] as const;
9
+ export const DEFAULT_IOS_SDK_PACKAGE_TYPE = "native";
10
+
11
+ const SDK_HOME_VERSION_PATH = "sdk/home/version";
12
+ const SDK_HOME_DOWNLOAD_URL_PATH = "sdk/home/sdk-download/getDownLoadUrl";
13
+
14
+ type JsonRecord = Record<string, unknown>;
15
+
16
+ export interface SdkHomePlatformVersion {
17
+ ver: string;
18
+ date: string;
19
+ }
20
+
21
+ export interface SdkHomeVersions {
22
+ android: SdkHomePlatformVersion;
23
+ ios: SdkHomePlatformVersion;
24
+ }
25
+
26
+ export interface SdkHomeDownloadUrlRequest {
27
+ baseUrl: string;
28
+ version: string;
29
+ platform?: "ios";
30
+ plugins?: readonly string[] | string;
31
+ packageType?: string;
32
+ }
33
+
34
+ export interface DownloadIosSdkOptions {
35
+ baseUrl?: string;
36
+ version?: string;
37
+ plugins?: readonly string[] | string;
38
+ packageType?: string;
39
+ outputDir?: string;
40
+ signal?: AbortSignal;
41
+ }
42
+
43
+ export interface DownloadIosSdkResult {
44
+ version: string;
45
+ versionDate: string;
46
+ versionUrl: string;
47
+ downloadApiUrl: string;
48
+ sdkZipUrl: string;
49
+ zipPath: string;
50
+ extractDir: string;
51
+ resolvedSdkRoot: string;
52
+ iosConfigPath: string;
53
+ }
54
+
55
+ function isRecord(value: unknown): value is JsonRecord {
56
+ return typeof value === "object" && value !== null && !Array.isArray(value);
57
+ }
58
+
59
+ function joinBaseAndPath(baseUrl: string, subPath: string): URL {
60
+ const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
61
+ return new URL(subPath, base);
62
+ }
63
+
64
+ function pluginsToCsv(plugins: readonly string[] | string | undefined): string {
65
+ if (typeof plugins === "string") return plugins;
66
+ return (plugins ?? DEFAULT_IOS_SDK_PLUGINS).join(",");
67
+ }
68
+
69
+ function pluginsToList(plugins: readonly string[] | string | undefined): string[] {
70
+ return pluginsToCsv(plugins)
71
+ .split(",")
72
+ .map((s) => s.trim())
73
+ .filter(Boolean);
74
+ }
75
+
76
+ export function buildSdkHomeVersionUrl(baseUrl: string): string {
77
+ return joinBaseAndPath(baseUrl, SDK_HOME_VERSION_PATH).toString();
78
+ }
79
+
80
+ export function buildSdkHomeDownloadUrl(params: SdkHomeDownloadUrlRequest): string {
81
+ const u = joinBaseAndPath(params.baseUrl, SDK_HOME_DOWNLOAD_URL_PATH);
82
+ u.searchParams.set("version", params.version);
83
+ u.searchParams.set("platform", params.platform ?? "ios");
84
+ u.searchParams.set("plugins", pluginsToCsv(params.plugins));
85
+ u.searchParams.set("packageType", params.packageType || DEFAULT_IOS_SDK_PACKAGE_TYPE);
86
+ return u.toString();
87
+ }
88
+
89
+ function parseJsonResponseEnvelope(body: unknown, label: string): JsonRecord {
90
+ if (!isRecord(body)) throw new Error(`${label} response is not an object`);
91
+ const code = body.code;
92
+ if (Number(code) !== 200) {
93
+ const message = typeof body.message === "string" ? body.message : JSON.stringify(body);
94
+ throw new Error(`${label} API error (code=${String(code)}): ${message}`);
95
+ }
96
+ return body;
97
+ }
98
+
99
+ async function fetchJson(url: string, signal?: AbortSignal): Promise<unknown> {
100
+ let res: Response;
101
+ try {
102
+ res = await fetch(url, { redirect: "follow", signal, headers: { Accept: "application/json" } });
103
+ } catch (e) {
104
+ const cause = e instanceof Error && "cause" in e && e.cause instanceof Error ? e.cause.message : null;
105
+ const detail = cause && cause !== (e instanceof Error ? e.message : String(e)) ? ` (${cause})` : "";
106
+ throw new Error(`GET ${url} failed: ${e instanceof Error ? e.message : String(e)}${detail}`);
107
+ }
108
+ if (!res.ok) {
109
+ const text = await res.text().catch(() => "");
110
+ const detail = text.trim() ? `: ${text.trim().slice(0, 1000)}` : "";
111
+ throw new Error(`GET ${url} failed: HTTP ${res.status}${detail}`);
112
+ }
113
+ const text = await res.text();
114
+ try {
115
+ return JSON.parse(text) as unknown;
116
+ } catch {
117
+ throw new Error(`GET ${url} did not return valid JSON`);
118
+ }
119
+ }
120
+
121
+ export async function fetchSdkHomeIosVersion(params: {
122
+ baseUrl?: string;
123
+ signal?: AbortSignal;
124
+ } = {}): Promise<{ url: string; ios: SdkHomePlatformVersion }> {
125
+ const { url, versions } = await fetchSdkHomeVersions(params);
126
+ return { url, ios: versions.ios };
127
+ }
128
+
129
+ function parseSdkHomePlatformVersion(value: unknown, platform: "android" | "ios"): SdkHomePlatformVersion {
130
+ if (!isRecord(value) || typeof value.ver !== "string" || typeof value.date !== "string") {
131
+ throw new Error(`sdk-home version response does not contain data.result.${platform}.ver/date`);
132
+ }
133
+ return { ver: value.ver, date: value.date };
134
+ }
135
+
136
+ export async function fetchSdkHomeVersions(params: {
137
+ baseUrl?: string;
138
+ signal?: AbortSignal;
139
+ } = {}): Promise<{ url: string; versions: SdkHomeVersions }> {
140
+ const baseUrl = params.baseUrl || defaultSdkHomeApiBaseUrl;
141
+ const url = buildSdkHomeVersionUrl(baseUrl);
142
+ const body = parseJsonResponseEnvelope(await fetchJson(url, params.signal), "sdk-home version");
143
+ const data = body.data;
144
+ const result = isRecord(data) ? data.result : undefined;
145
+ if (!isRecord(result)) {
146
+ throw new Error("sdk-home version response does not contain data.result");
147
+ }
148
+ return {
149
+ url,
150
+ versions: {
151
+ android: parseSdkHomePlatformVersion(result.android, "android"),
152
+ ios: parseSdkHomePlatformVersion(result.ios, "ios"),
153
+ },
154
+ };
155
+ }
156
+
157
+ export async function fetchSdkHomeIosDownloadUrl(params: SdkHomeDownloadUrlRequest & {
158
+ signal?: AbortSignal;
159
+ }): Promise<{ url: string; sdkZipUrl: string }> {
160
+ const url = buildSdkHomeDownloadUrl(params);
161
+ const body = parseJsonResponseEnvelope(await fetchJson(url, params.signal), "sdk-home getDownLoadUrl");
162
+ if (typeof body.data !== "string" || !body.data.trim()) {
163
+ throw new Error("sdk-home getDownLoadUrl response does not contain a download URL in data");
164
+ }
165
+ return { url, sdkZipUrl: body.data };
166
+ }
167
+
168
+ export function resolveIosSdkZipFileName(version: string): string {
169
+ return `topSDK-ios--V${version}.zip`;
170
+ }
171
+
172
+ export function resolveIosSdkRootFromDirectory(bundledDir: string): string {
173
+ if (!fs.existsSync(bundledDir)) {
174
+ throw new Error(`iOS SDK not found at ${bundledDir} (expected sdk/ and plugins/ inside).`);
175
+ }
176
+ if (fs.existsSync(path.join(bundledDir, "sdk")) && fs.existsSync(path.join(bundledDir, "plugins"))) {
177
+ return bundledDir;
178
+ }
179
+ const children = fs
180
+ .readdirSync(bundledDir, { withFileTypes: true })
181
+ .filter((d) => d.isDirectory() && !d.name.startsWith("."))
182
+ .map((d) => path.join(bundledDir, d.name));
183
+ for (const dir of children) {
184
+ if (fs.existsSync(path.join(dir, "sdk")) && fs.existsSync(path.join(dir, "plugins"))) {
185
+ return dir;
186
+ }
187
+ }
188
+ throw new Error(`iOS SDK layout invalid under ${bundledDir}: need sdk/ + plugins/ at top level or under one version directory.`);
189
+ }
190
+
191
+ export async function downloadBinaryFile(url: string, absPath: string, signal?: AbortSignal): Promise<void> {
192
+ let res: Response;
193
+ try {
194
+ res = await fetch(url, { redirect: "follow", signal });
195
+ } catch (e) {
196
+ const cause = e instanceof Error && "cause" in e && e.cause instanceof Error ? e.cause.message : null;
197
+ const detail = cause && cause !== (e instanceof Error ? e.message : String(e)) ? ` (${cause})` : "";
198
+ throw new Error(`GET ${url} failed: ${e instanceof Error ? e.message : String(e)}${detail}`);
199
+ }
200
+ if (!res.ok) {
201
+ throw new Error(`GET ${url} failed: HTTP ${res.status} ${res.statusText}`);
202
+ }
203
+ const buf = Buffer.from(await res.arrayBuffer());
204
+ fs.mkdirSync(path.dirname(absPath), { recursive: true });
205
+ fs.writeFileSync(absPath, buf);
206
+ }
207
+
208
+ export async function extractZip(zipPath: string, destDir: string): Promise<void> {
209
+ fs.mkdirSync(destDir, { recursive: true });
210
+ await new Promise<void>((resolve, reject) => {
211
+ const child = spawn("unzip", ["-q", "-o", zipPath, "-d", destDir], { stdio: ["ignore", "pipe", "pipe"] });
212
+ let stdout = "";
213
+ let stderr = "";
214
+ child.stdout.on("data", (chunk) => {
215
+ stdout += String(chunk);
216
+ });
217
+ child.stderr.on("data", (chunk) => {
218
+ stderr += String(chunk);
219
+ });
220
+ child.on("error", (e) => reject(new Error(`failed to start unzip: ${e.message}`)));
221
+ child.on("close", (code) => {
222
+ if (code === 0) {
223
+ resolve();
224
+ } else {
225
+ const detail = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
226
+ reject(new Error(`unzip failed with exit code ${code}${detail ? `: ${detail}` : ""}`));
227
+ }
228
+ });
229
+ });
230
+ }
231
+
232
+ export async function downloadIosSdkToBundled(
233
+ packageRoot: string,
234
+ options: DownloadIosSdkOptions = {}
235
+ ): Promise<DownloadIosSdkResult> {
236
+ const baseUrl = options.baseUrl || defaultSdkHomeApiBaseUrl;
237
+ const packageType = options.packageType || DEFAULT_IOS_SDK_PACKAGE_TYPE;
238
+ const plugins = pluginsToList(options.plugins);
239
+ const versionResult = options.version
240
+ ? { url: buildSdkHomeVersionUrl(baseUrl), ios: { ver: options.version, date: "" } }
241
+ : await fetchSdkHomeIosVersion({ baseUrl, signal: options.signal });
242
+ const version = versionResult.ios.ver;
243
+ const downloadResult = await fetchSdkHomeIosDownloadUrl({
244
+ baseUrl,
245
+ version,
246
+ plugins,
247
+ packageType,
248
+ signal: options.signal,
249
+ });
250
+ const extractDir = path.resolve(options.outputDir || defaultBundledIosSdkRoot(packageRoot));
251
+ const zipPath = path.join(extractDir, resolveIosSdkZipFileName(version));
252
+
253
+ await downloadBinaryFile(downloadResult.sdkZipUrl, zipPath, options.signal);
254
+ await extractZip(zipPath, extractDir);
255
+ const iosConfigPath = syncMeetSdkIosVersionToConfig({
256
+ packageRoot,
257
+ version,
258
+ date: versionResult.ios.date,
259
+ packageType,
260
+ plugins,
261
+ });
262
+
263
+ return {
264
+ version,
265
+ versionDate: versionResult.ios.date,
266
+ versionUrl: versionResult.url,
267
+ downloadApiUrl: downloadResult.url,
268
+ sdkZipUrl: downloadResult.sdkZipUrl,
269
+ zipPath,
270
+ extractDir,
271
+ resolvedSdkRoot: resolveIosSdkRootFromDirectory(extractDir),
272
+ iosConfigPath,
273
+ };
274
+ }
@@ -0,0 +1,93 @@
1
+ import { topsdkMd5Base64Sign } from "./topsdkSign.js";
2
+ import { defaultTopSdkBaseUrl, type TopSdkApiEnv } from "./topsdkGetSdkConfig.js";
3
+
4
+ export { defaultTopSdkBaseUrl, type TopSdkApiEnv };
5
+
6
+ const DOWNLOAD_SDK_CONFIG_PATH = "console/openSDK/downloadSDKConfig";
7
+
8
+ function joinBaseAndPath(baseUrl: string, path: string): URL {
9
+ const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
10
+ return new URL(path, base);
11
+ }
12
+
13
+ /**
14
+ * GET `console/openSDK/downloadSDKConfig` — same sign as `getSDKConfig`:
15
+ * sign = base64(md5(appId + appSecret + timestampMillis)), timestamp within 60s.
16
+ */
17
+ export function buildTopSdkDownloadSdkConfigUrl(params: {
18
+ baseUrl: string;
19
+ appId: string;
20
+ appSecret: string;
21
+ channelType: string;
22
+ timestampMillis?: number;
23
+ }): { url: string; timestampMillis: number } {
24
+ const timestampMillis = params.timestampMillis ?? Date.now();
25
+ const sign = topsdkMd5Base64Sign(params.appId, params.appSecret, timestampMillis);
26
+ const u = joinBaseAndPath(params.baseUrl, DOWNLOAD_SDK_CONFIG_PATH);
27
+ u.searchParams.set("appId", params.appId);
28
+ u.searchParams.set("channelType", params.channelType);
29
+ u.searchParams.set("sign", sign);
30
+ u.searchParams.set("timestamp", String(timestampMillis));
31
+ return { url: u.toString(), timestampMillis };
32
+ }
33
+
34
+ function isRecord(v: unknown): v is Record<string, unknown> {
35
+ return typeof v === "object" && v !== null && !Array.isArray(v);
36
+ }
37
+
38
+ /** gp-sdk error envelope when the handler throws before streaming JSON. */
39
+ export function parseTopSdkApiErrorBody(body: unknown): string | null {
40
+ if (!isRecord(body)) return null;
41
+ const code = body.code;
42
+ if (code === undefined || code === null) return null;
43
+ if (Number(code) === 200) return null;
44
+ const message = typeof body.message === "string" ? body.message : typeof body.error === "string" ? body.error : JSON.stringify(body);
45
+ return `TOPSDK API error (code=${code}): ${message}`;
46
+ }
47
+
48
+ export async function fetchTopSdkDownloadSdkConfig(params: {
49
+ baseUrl: string;
50
+ appId: string;
51
+ appSecret: string;
52
+ channelType: string;
53
+ timestampMillis?: number;
54
+ signal?: AbortSignal;
55
+ }): Promise<{ url: string; body: unknown; text: string }> {
56
+ const { url } = buildTopSdkDownloadSdkConfigUrl(params);
57
+ let res: Response;
58
+ try {
59
+ res = await fetch(url, {
60
+ redirect: "follow",
61
+ signal: params.signal,
62
+ headers: { Accept: "application/json" },
63
+ });
64
+ } catch (e) {
65
+ const cause = e instanceof Error && "cause" in e && e.cause instanceof Error ? e.cause.message : null;
66
+ const detail = cause && cause !== (e instanceof Error ? e.message : String(e)) ? ` (${cause})` : "";
67
+ throw new Error(`GET ${url} failed: ${e instanceof Error ? e.message : String(e)}${detail}`);
68
+ }
69
+ if (!res.ok) {
70
+ let detail = "";
71
+ try {
72
+ const text = await res.text();
73
+ if (text.trim()) {
74
+ detail = `: ${text.trim().slice(0, 1000)}`;
75
+ }
76
+ } catch {
77
+ // Keep the original status-only error if the response body cannot be read.
78
+ }
79
+ throw new Error(`GET ${url} failed: HTTP ${res.status}${detail}`);
80
+ }
81
+ const text = await res.text();
82
+ let body: unknown;
83
+ try {
84
+ body = JSON.parse(text) as unknown;
85
+ } catch {
86
+ throw new Error("Remote response is not valid JSON");
87
+ }
88
+ const apiErr = parseTopSdkApiErrorBody(body);
89
+ if (apiErr) {
90
+ throw new Error(apiErr);
91
+ }
92
+ return { url, body, text };
93
+ }