@lydt/geogebra-studio 0.0.0-stage → 0.4.0-rc.2

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 (189) hide show
  1. package/.agents/plugins/marketplace.json +21 -0
  2. package/.codex-plugin/plugin.json +21 -0
  3. package/.mcp.json +18 -0
  4. package/LICENSE +339 -0
  5. package/LICENSE-MIT +21 -0
  6. package/README.md +108 -2
  7. package/README.zh-CN.md +89 -0
  8. package/THIRD_PARTY_NOTICES.md +35 -0
  9. package/dist/cli.d.ts +27 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +234 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/export/files.d.ts +4 -0
  14. package/dist/export/files.d.ts.map +1 -0
  15. package/dist/export/files.js +44 -0
  16. package/dist/export/files.js.map +1 -0
  17. package/dist/export/gikz.d.ts +11 -0
  18. package/dist/export/gikz.d.ts.map +1 -0
  19. package/dist/export/gikz.js +28 -0
  20. package/dist/export/gikz.js.map +1 -0
  21. package/dist/export/html.d.ts +14 -0
  22. package/dist/export/html.d.ts.map +1 -0
  23. package/dist/export/html.js +97 -0
  24. package/dist/export/html.js.map +1 -0
  25. package/dist/export/semantic.d.ts +43 -0
  26. package/dist/export/semantic.d.ts.map +1 -0
  27. package/dist/export/semantic.js +212 -0
  28. package/dist/export/semantic.js.map +1 -0
  29. package/dist/export/viewer-themes.d.ts +76 -0
  30. package/dist/export/viewer-themes.d.ts.map +1 -0
  31. package/dist/export/viewer-themes.js +169 -0
  32. package/dist/export/viewer-themes.js.map +1 -0
  33. package/dist/export/workspace.d.ts +4 -0
  34. package/dist/export/workspace.d.ts.map +1 -0
  35. package/dist/export/workspace.js +25 -0
  36. package/dist/export/workspace.js.map +1 -0
  37. package/dist/gikz-browser.js +770 -0
  38. package/dist/index.d.ts +5 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +84 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/server.d.ts +35 -0
  43. package/dist/server.d.ts.map +1 -0
  44. package/dist/server.js +187 -0
  45. package/dist/server.js.map +1 -0
  46. package/dist/studio/artifact.d.ts +37 -0
  47. package/dist/studio/artifact.d.ts.map +1 -0
  48. package/dist/studio/artifact.js +61 -0
  49. package/dist/studio/artifact.js.map +1 -0
  50. package/dist/studio/browser.d.ts +7 -0
  51. package/dist/studio/browser.d.ts.map +1 -0
  52. package/dist/studio/browser.js +67 -0
  53. package/dist/studio/browser.js.map +1 -0
  54. package/dist/studio/install.d.ts +4 -0
  55. package/dist/studio/install.d.ts.map +1 -0
  56. package/dist/studio/install.js +60 -0
  57. package/dist/studio/install.js.map +1 -0
  58. package/dist/tools/apps-api-tools.d.ts +4 -0
  59. package/dist/tools/apps-api-tools.d.ts.map +1 -0
  60. package/dist/tools/apps-api-tools.js +96 -0
  61. package/dist/tools/apps-api-tools.js.map +1 -0
  62. package/dist/tools/educational-templates.d.ts +33 -0
  63. package/dist/tools/educational-templates.d.ts.map +1 -0
  64. package/dist/tools/educational-templates.js +261 -0
  65. package/dist/tools/educational-templates.js.map +1 -0
  66. package/dist/tools/export-tools.d.ts +3 -0
  67. package/dist/tools/export-tools.d.ts.map +1 -0
  68. package/dist/tools/export-tools.js +118 -0
  69. package/dist/tools/export-tools.js.map +1 -0
  70. package/dist/tools/geogebra-tools.d.ts +15 -0
  71. package/dist/tools/geogebra-tools.d.ts.map +1 -0
  72. package/dist/tools/geogebra-tools.js +3442 -0
  73. package/dist/tools/geogebra-tools.js.map +1 -0
  74. package/dist/tools/index.d.ts +43 -0
  75. package/dist/tools/index.d.ts.map +1 -0
  76. package/dist/tools/index.js +183 -0
  77. package/dist/tools/index.js.map +1 -0
  78. package/dist/tools/performance-tools.d.ts +7 -0
  79. package/dist/tools/performance-tools.d.ts.map +1 -0
  80. package/dist/tools/performance-tools.js +312 -0
  81. package/dist/tools/performance-tools.js.map +1 -0
  82. package/dist/tools/templates/algebra-templates.d.ts +7 -0
  83. package/dist/tools/templates/algebra-templates.d.ts.map +1 -0
  84. package/dist/tools/templates/algebra-templates.js +568 -0
  85. package/dist/tools/templates/algebra-templates.js.map +1 -0
  86. package/dist/tools/templates/geometry-templates.d.ts +7 -0
  87. package/dist/tools/templates/geometry-templates.d.ts.map +1 -0
  88. package/dist/tools/templates/geometry-templates.js +492 -0
  89. package/dist/tools/templates/geometry-templates.js.map +1 -0
  90. package/dist/types/geogebra.d.ts +103 -0
  91. package/dist/types/geogebra.d.ts.map +1 -0
  92. package/dist/types/geogebra.js +34 -0
  93. package/dist/types/geogebra.js.map +1 -0
  94. package/dist/types/mcp.d.ts +125 -0
  95. package/dist/types/mcp.d.ts.map +1 -0
  96. package/dist/types/mcp.js +19 -0
  97. package/dist/types/mcp.js.map +1 -0
  98. package/dist/utils/app-info.d.ts +12 -0
  99. package/dist/utils/app-info.d.ts.map +1 -0
  100. package/dist/utils/app-info.js +30 -0
  101. package/dist/utils/app-info.js.map +1 -0
  102. package/dist/utils/errors.d.ts +37 -0
  103. package/dist/utils/errors.d.ts.map +1 -0
  104. package/dist/utils/errors.js +72 -0
  105. package/dist/utils/errors.js.map +1 -0
  106. package/dist/utils/gemini-compatibility.d.ts +39 -0
  107. package/dist/utils/gemini-compatibility.d.ts.map +1 -0
  108. package/dist/utils/gemini-compatibility.js +154 -0
  109. package/dist/utils/gemini-compatibility.js.map +1 -0
  110. package/dist/utils/geogebra-instance.d.ts +252 -0
  111. package/dist/utils/geogebra-instance.d.ts.map +1 -0
  112. package/dist/utils/geogebra-instance.js +1560 -0
  113. package/dist/utils/geogebra-instance.js.map +1 -0
  114. package/dist/utils/geogebra-mock.d.ts +172 -0
  115. package/dist/utils/geogebra-mock.d.ts.map +1 -0
  116. package/dist/utils/geogebra-mock.js +732 -0
  117. package/dist/utils/geogebra-mock.js.map +1 -0
  118. package/dist/utils/logger.d.ts +4 -0
  119. package/dist/utils/logger.d.ts.map +1 -0
  120. package/dist/utils/logger.js +46 -0
  121. package/dist/utils/logger.js.map +1 -0
  122. package/dist/utils/performance/index.d.ts +76 -0
  123. package/dist/utils/performance/index.d.ts.map +1 -0
  124. package/dist/utils/performance/index.js +163 -0
  125. package/dist/utils/performance/index.js.map +1 -0
  126. package/dist/utils/performance/instance-pool.d.ts +78 -0
  127. package/dist/utils/performance/instance-pool.d.ts.map +1 -0
  128. package/dist/utils/performance/instance-pool.js +268 -0
  129. package/dist/utils/performance/instance-pool.js.map +1 -0
  130. package/dist/utils/validation.d.ts +92 -0
  131. package/dist/utils/validation.d.ts.map +1 -0
  132. package/dist/utils/validation.js +557 -0
  133. package/dist/utils/validation.js.map +1 -0
  134. package/dist/widget.html +110 -0
  135. package/docs/APPS-API-BRIDGE.md +28 -0
  136. package/docs/MODERNIZATION.md +15 -0
  137. package/docs/TANGENT-REGRESSION.md +34 -0
  138. package/docs/VALIDATION.md +26 -0
  139. package/docs/VISUAL-STANDARD.md +26 -0
  140. package/docs/images/native-construction.png +0 -0
  141. package/examples/tangent-construction.json +26 -0
  142. package/gikz/LICENSE +339 -0
  143. package/gikz/PROVENANCE.md +8 -0
  144. package/gikz/browser.cjs +2 -0
  145. package/gikz/cli.cjs +195 -0
  146. package/gikz/core.cjs +806 -0
  147. package/mcp.json +20 -0
  148. package/package.json +107 -4
  149. package/plugin.json +35 -0
  150. package/runtime/package.json +4 -0
  151. package/scripts/build-plugin.cjs +15 -0
  152. package/scripts/build-widget.cjs +7 -0
  153. package/skills/geogebra-construct/SKILL.md +40 -0
  154. package/skills/geogebra-setup/SKILL.md +37 -0
  155. package/skills/geometry-export/SKILL.md +31 -0
  156. package/src/cli.ts +212 -0
  157. package/src/export/files.ts +26 -0
  158. package/src/export/gikz.ts +22 -0
  159. package/src/export/html.ts +108 -0
  160. package/src/export/semantic.ts +180 -0
  161. package/src/export/viewer-themes.ts +165 -0
  162. package/src/export/workspace.ts +15 -0
  163. package/src/index.ts +71 -0
  164. package/src/server.ts +168 -0
  165. package/src/studio/artifact.ts +50 -0
  166. package/src/studio/browser.ts +38 -0
  167. package/src/studio/install.ts +43 -0
  168. package/src/tools/apps-api-tools.ts +96 -0
  169. package/src/tools/educational-templates.ts +299 -0
  170. package/src/tools/export-tools.ts +110 -0
  171. package/src/tools/geogebra-tools.ts +3836 -0
  172. package/src/tools/index.ts +192 -0
  173. package/src/tools/performance-tools.ts +322 -0
  174. package/src/tools/templates/algebra-templates.ts +619 -0
  175. package/src/tools/templates/geometry-templates.ts +550 -0
  176. package/src/types/geogebra.ts +140 -0
  177. package/src/types/global.d.ts +31 -0
  178. package/src/types/mcp.ts +141 -0
  179. package/src/utils/app-info.ts +35 -0
  180. package/src/utils/errors.ts +97 -0
  181. package/src/utils/gemini-compatibility.ts +193 -0
  182. package/src/utils/geogebra-instance.ts +1712 -0
  183. package/src/utils/geogebra-mock.ts +867 -0
  184. package/src/utils/logger.ts +60 -0
  185. package/src/utils/performance/index.ts +211 -0
  186. package/src/utils/performance/instance-pool.ts +323 -0
  187. package/src/utils/validation.ts +647 -0
  188. package/tsconfig.json +35 -0
  189. package/widget/main.ts +12 -0
@@ -0,0 +1,1560 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.GeoGebraInstance = void 0;
40
+ // @ts-nocheck
41
+ const puppeteer_core_1 = __importDefault(require("puppeteer-core"));
42
+ const browser_1 = require("../studio/browser");
43
+ const html_1 = require("../export/html");
44
+ const workspace_1 = require("../export/workspace");
45
+ const geogebra_1 = require("../types/geogebra");
46
+ const logger_1 = __importDefault(require("./logger"));
47
+ const crypto_1 = require("crypto");
48
+ /**
49
+ * GeoGebra Instance - Manages a single GeoGebra instance via Puppeteer
50
+ */
51
+ /**
52
+ * Escapes a string for safe use in JavaScript code.
53
+ * Replaces backslashes and single quotes with their escaped equivalents.
54
+ */
55
+ function escapeForJavaScript(input) {
56
+ return input.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
57
+ }
58
+ class GeoGebraInstance {
59
+ browser;
60
+ page;
61
+ isInitialized = false;
62
+ lastActivity = new Date();
63
+ id;
64
+ config;
65
+ constructor(config = {}) {
66
+ this.id = (0, crypto_1.randomUUID)();
67
+ this.config = {
68
+ appName: 'classic', // Changed from 'graphing' to 'classic' for full functionality
69
+ width: 800,
70
+ height: 600,
71
+ showMenuBar: false,
72
+ showToolBar: false,
73
+ showAlgebraInput: false,
74
+ showResetIcon: false,
75
+ enableRightClick: true,
76
+ language: 'en',
77
+ ...config
78
+ };
79
+ logger_1.default.info(`Created GeoGebra instance ${this.id}`, { config: this.config });
80
+ }
81
+ /**
82
+ * Initialize the GeoGebra instance
83
+ */
84
+ async initialize(headless = true, browserArgs = []) {
85
+ try {
86
+ logger_1.default.info(`Initializing GeoGebra instance ${this.id}`, { headless });
87
+ // Launch browser
88
+ this.browser = await puppeteer_core_1.default.launch({
89
+ headless,
90
+ executablePath: (0, browser_1.resolveBrowser)(),
91
+ args: [
92
+ '--no-sandbox',
93
+ '--disable-setuid-sandbox',
94
+ '--disable-dev-shm-usage',
95
+ '--disable-accelerated-2d-canvas',
96
+ '--no-first-run',
97
+ '--no-zygote',
98
+ '--disable-gpu',
99
+ ...browserArgs
100
+ ]
101
+ });
102
+ // Create new page
103
+ this.page = await this.browser.newPage();
104
+ // Set viewport
105
+ await this.page.setViewport({
106
+ width: this.config.width || 800,
107
+ height: this.config.height || 600
108
+ });
109
+ // Load GeoGebra
110
+ await this.loadGeoGebra();
111
+ // Wait for GeoGebra to be ready
112
+ await this.waitForReady();
113
+ this.isInitialized = true;
114
+ this.updateActivity();
115
+ logger_1.default.info(`GeoGebra instance ${this.id} initialized successfully`);
116
+ }
117
+ catch (error) {
118
+ logger_1.default.error(`Failed to initialize GeoGebra instance ${this.id}`, error);
119
+ await this.cleanup();
120
+ throw new geogebra_1.GeoGebraConnectionError(`Failed to initialize GeoGebra: ${error instanceof Error ? error.message : String(error)}`);
121
+ }
122
+ }
123
+ /**
124
+ * Load GeoGebra applet in the browser
125
+ */
126
+ async loadGeoGebra() {
127
+ if (!this.page) {
128
+ throw new geogebra_1.GeoGebraConnectionError('Page not initialized');
129
+ }
130
+ const appletHTML = this.generateAppletHTML();
131
+ await this.page.setContent(appletHTML);
132
+ // Wait for GeoGebra to load
133
+ await this.page.waitForFunction('window.ggbReady || window.ggbError', { timeout: 60000 });
134
+ const bootError = await this.page.evaluate(() => window.ggbError);
135
+ if (bootError)
136
+ throw new geogebra_1.GeoGebraConnectionError(bootError);
137
+ }
138
+ /**
139
+ * Generate HTML content with GeoGebra applet
140
+ */
141
+ generateAppletHTML() {
142
+ return (0, html_1.constructionHtml)({ width: this.config.width, height: this.config.height, ggbBase64: this.config.ggbBase64 });
143
+ }
144
+ /**
145
+ * Wait for GeoGebra to be ready
146
+ */
147
+ async waitForReady(timeout = 30000) {
148
+ if (!this.page) {
149
+ throw new geogebra_1.GeoGebraConnectionError('Page not initialized');
150
+ }
151
+ try {
152
+ // Wait for both ggbReady flag and ggbApplet to be available
153
+ await this.page.waitForFunction('window.ggbReady === true && window.ggbApplet && typeof window.ggbApplet.evalCommand === "function"', { timeout });
154
+ // Additional verification that the applet is functional
155
+ const isWorking = await this.page.evaluate(() => {
156
+ try {
157
+ return typeof window.ggbApplet.evalCommand === 'function' &&
158
+ typeof window.ggbApplet.exists === 'function';
159
+ }
160
+ catch (e) {
161
+ return false;
162
+ }
163
+ });
164
+ if (!isWorking) {
165
+ throw new Error('GeoGebra applet methods not available');
166
+ }
167
+ logger_1.default.debug(`GeoGebra instance ${this.id} is ready and functional`);
168
+ }
169
+ catch (error) {
170
+ // Log the current state for debugging
171
+ try {
172
+ const debugInfo = await this.page.evaluate(() => ({
173
+ ggbReady: window.ggbReady,
174
+ ggbAppletExists: !!window.ggbApplet,
175
+ ggbAppletType: typeof window.ggbApplet,
176
+ evalCommandExists: window.ggbApplet && typeof window.ggbApplet.evalCommand,
177
+ pageLocation: window.location.href,
178
+ pageTitle: document.title
179
+ }));
180
+ logger_1.default.error(`GeoGebra initialization failed. Debug info:`, debugInfo);
181
+ }
182
+ catch (debugError) {
183
+ logger_1.default.error(`Failed to get debug info during initialization failure`, debugError);
184
+ }
185
+ throw new geogebra_1.GeoGebraConnectionError('GeoGebra failed to initialize within timeout');
186
+ }
187
+ }
188
+ /**
189
+ * Execute a GeoGebra command
190
+ */
191
+ async evalCommand(command) {
192
+ this.ensureInitialized();
193
+ this.updateActivity();
194
+ try {
195
+ logger_1.default.debug(`Executing command on instance ${this.id}: ${command}`);
196
+ // First verify that the applet is still available
197
+ const appletCheck = await this.page.evaluate(() => {
198
+ return {
199
+ appletExists: !!window.ggbApplet,
200
+ evalCommandExists: !!window.ggbApplet && typeof window.ggbApplet.evalCommand === 'function'
201
+ };
202
+ });
203
+ if (!appletCheck.appletExists || !appletCheck.evalCommandExists) {
204
+ throw new Error(`GeoGebra applet not available: ${JSON.stringify(appletCheck)}`);
205
+ }
206
+ const result = await this.page.evaluate((cmd) => {
207
+ try {
208
+ const success = window.ggbApplet.evalCommand(cmd);
209
+ return {
210
+ success: success,
211
+ error: success ? undefined : 'Command execution failed'
212
+ };
213
+ }
214
+ catch (error) {
215
+ return {
216
+ success: false,
217
+ error: error.message || 'Unknown error'
218
+ };
219
+ }
220
+ }, command);
221
+ if (!result.success) {
222
+ throw new geogebra_1.GeoGebraCommandError(result.error || 'Command failed', command);
223
+ }
224
+ logger_1.default.debug(`Command executed successfully on instance ${this.id}: ${command}`);
225
+ return result;
226
+ }
227
+ catch (error) {
228
+ logger_1.default.error(`Command execution failed on instance ${this.id}`, { command, error });
229
+ if (error instanceof geogebra_1.GeoGebraCommandError) {
230
+ throw error;
231
+ }
232
+ throw new geogebra_1.GeoGebraCommandError(`Failed to execute command: ${error instanceof Error ? error.message : String(error)}`, command);
233
+ }
234
+ }
235
+ /**
236
+ * Execute command and get labels of created objects
237
+ */
238
+ async evalCommandGetLabels(command) {
239
+ this.ensureInitialized();
240
+ this.updateActivity();
241
+ try {
242
+ const labels = await this.page.evaluate((cmd) => {
243
+ const result = window.ggbApplet.evalCommandGetLabels(cmd);
244
+ return result ? result.split(',').filter((label) => label.trim()) : [];
245
+ }, command);
246
+ logger_1.default.debug(`Command executed on instance ${this.id}, labels: ${labels.join(', ')}`);
247
+ return labels;
248
+ }
249
+ catch (error) {
250
+ logger_1.default.error(`Failed to execute command with labels on instance ${this.id}`, { command, error });
251
+ throw new geogebra_1.GeoGebraCommandError(`Failed to execute command: ${error instanceof Error ? error.message : String(error)}`, command);
252
+ }
253
+ }
254
+ /** Invoke a documented GeoGebra Apps API method against the live applet. */
255
+ async callAppsApi(method, args = [], options = {}) {
256
+ this.ensureInitialized();
257
+ this.updateActivity();
258
+ if (!/^[A-Za-z][A-Za-z0-9]*$/.test(method) || ['constructor', '__proto__', 'prototype', 'toString', 'valueOf'].includes(method)) {
259
+ throw new Error('Invalid GeoGebra Apps API method name');
260
+ }
261
+ return this.page.evaluate(async ({ method, args, waitForCallbackIndex, callbackTimeoutMs, callbacks }) => {
262
+ const api = window.ggbApplet;
263
+ const target = api?.[method];
264
+ if (typeof target !== 'function')
265
+ throw new Error(`GeoGebra Apps API method is unavailable: ${method}`);
266
+ const invocationArgs = [...args];
267
+ const callbackStore = (window.__geogebraMcpCallbacks ??= { functions: {}, events: {} });
268
+ for (const descriptor of callbacks ?? []) {
269
+ if (!Number.isInteger(descriptor.argumentIndex) || descriptor.argumentIndex < 0 || descriptor.argumentIndex > 20 || !/^[A-Za-z0-9_-]{1,80}$/.test(descriptor.id)) {
270
+ throw new Error('Invalid persistent callback descriptor');
271
+ }
272
+ callbackStore.events[descriptor.id] ??= [];
273
+ callbackStore.functions[descriptor.id] ??= (...values) => {
274
+ const queue = callbackStore.events[descriptor.id];
275
+ queue.push(values);
276
+ if (queue.length > 100)
277
+ queue.splice(0, queue.length - 100);
278
+ };
279
+ invocationArgs[descriptor.argumentIndex] = callbackStore.functions[descriptor.id];
280
+ }
281
+ if (waitForCallbackIndex !== undefined) {
282
+ const index = waitForCallbackIndex;
283
+ if (!Number.isInteger(index) || index < 0 || index > 20)
284
+ throw new Error('Invalid callback argument index');
285
+ return await new Promise((resolve, reject) => {
286
+ const timer = setTimeout(() => reject(new Error(`GeoGebra API callback timed out: ${method}`)), callbackTimeoutMs);
287
+ invocationArgs[index] = (value) => { clearTimeout(timer); resolve(value); };
288
+ try {
289
+ const immediate = target.apply(api, invocationArgs);
290
+ if (immediate && typeof immediate.then === 'function')
291
+ immediate.then(resolve, reject);
292
+ }
293
+ catch (error) {
294
+ clearTimeout(timer);
295
+ reject(error);
296
+ }
297
+ });
298
+ }
299
+ const result = target.apply(api, invocationArgs);
300
+ return result && typeof result.then === 'function' ? await result : result;
301
+ }, { method, args, ...options, callbackTimeoutMs: options.callbackTimeoutMs ?? 15000 });
302
+ }
303
+ async readAppsApiCallbackEvents(id, clear = true) {
304
+ this.ensureInitialized();
305
+ if (!/^[A-Za-z0-9_-]{1,80}$/.test(id))
306
+ throw new Error('Invalid callback id');
307
+ return this.page.evaluate(({ id, clear }) => {
308
+ const store = window.__geogebraMcpCallbacks;
309
+ const events = store?.events?.[id] ?? [];
310
+ const copy = events.slice();
311
+ if (clear && store?.events?.[id])
312
+ store.events[id] = [];
313
+ return copy;
314
+ }, { id, clear });
315
+ }
316
+ /** Enumerate callable methods exposed by the currently loaded official applet API. */
317
+ async listAppsApiMethods() {
318
+ this.ensureInitialized();
319
+ return this.page.evaluate(() => {
320
+ const api = window.ggbApplet;
321
+ const names = new Set();
322
+ for (let object = api; object && object !== Object.prototype; object = Object.getPrototypeOf(object)) {
323
+ for (const name of Object.getOwnPropertyNames(object)) {
324
+ if (/^[A-Za-z][A-Za-z0-9]*$/.test(name) && typeof api[name] === 'function' && !['constructor', 'toString', 'valueOf'].includes(name))
325
+ names.add(name);
326
+ }
327
+ }
328
+ return [...names].sort();
329
+ });
330
+ }
331
+ /**
332
+ * Delete an object
333
+ */
334
+ async deleteObject(objName) {
335
+ this.ensureInitialized();
336
+ this.updateActivity();
337
+ try {
338
+ await this.page.evaluate(`window.ggbApplet.deleteObject('${escapeForJavaScript(objName)}');`);
339
+ return true;
340
+ }
341
+ catch (error) {
342
+ logger_1.default.error(`Failed to delete object ${objName} on instance ${this.id}`, error);
343
+ return false;
344
+ }
345
+ }
346
+ /**
347
+ * Check if object exists
348
+ */
349
+ async exists(objName) {
350
+ this.ensureInitialized();
351
+ this.updateActivity();
352
+ try {
353
+ const exists = await this.page.evaluate((name) => {
354
+ return window.ggbApplet.exists(name);
355
+ }, objName);
356
+ return exists;
357
+ }
358
+ catch (error) {
359
+ logger_1.default.error(`Failed to check existence of object ${objName} on instance ${this.id}`, error);
360
+ return false;
361
+ }
362
+ }
363
+ /**
364
+ * Check if object is defined
365
+ */
366
+ async isDefined(objName) {
367
+ this.ensureInitialized();
368
+ this.updateActivity();
369
+ try {
370
+ const defined = await this.page.evaluate((name) => {
371
+ return window.ggbApplet.isDefined(name);
372
+ }, objName);
373
+ return defined;
374
+ }
375
+ catch (error) {
376
+ logger_1.default.error(`Failed to check if object ${objName} is defined on instance ${this.id}`, error);
377
+ return false;
378
+ }
379
+ }
380
+ /**
381
+ * Get all object names
382
+ */
383
+ async getAllObjectNames(type) {
384
+ this.ensureInitialized();
385
+ this.updateActivity();
386
+ try {
387
+ const names = await this.page.evaluate((objType) => {
388
+ return window.ggbApplet.getAllObjectNames(objType);
389
+ }, type);
390
+ return names || [];
391
+ }
392
+ catch (error) {
393
+ logger_1.default.error(`Failed to get object names on instance ${this.id}`, error);
394
+ return [];
395
+ }
396
+ }
397
+ /**
398
+ * Set each object's caption to LaTeX name form ($name$) and show caption label mode.
399
+ */
400
+ async applyLatexCaptionDefaults(objectNames, showLabel = true) {
401
+ this.ensureInitialized();
402
+ this.updateActivity();
403
+ if (objectNames.length === 0) {
404
+ return;
405
+ }
406
+ const uniqueNames = Array.from(new Set(objectNames.filter(name => name && name.trim())));
407
+ if (uniqueNames.length === 0) {
408
+ return;
409
+ }
410
+ try {
411
+ const result = await this.page.evaluate((names, shouldShowLabel) => {
412
+ const applet = window.ggbApplet;
413
+ const failed = [];
414
+ for (const name of names) {
415
+ try {
416
+ if (!applet.exists(name)) {
417
+ failed.push(name);
418
+ continue;
419
+ }
420
+ applet.setCaption(name, `$${name}$`);
421
+ applet.setLabelStyle(name, 3);
422
+ if (shouldShowLabel) {
423
+ applet.setLabelVisible(name, true);
424
+ }
425
+ }
426
+ catch (_error) {
427
+ failed.push(name);
428
+ }
429
+ }
430
+ return { failed };
431
+ }, uniqueNames, showLabel);
432
+ if (Array.isArray(result?.failed) && result.failed.length > 0) {
433
+ logger_1.default.warn(`Failed to apply LaTeX caption defaults for ${result.failed.length} object(s)`, {
434
+ instanceId: this.id,
435
+ failedObjects: result.failed
436
+ });
437
+ }
438
+ }
439
+ catch (error) {
440
+ logger_1.default.error(`Failed to apply LaTeX caption defaults on instance ${this.id}`, {
441
+ objectNames: uniqueNames,
442
+ error
443
+ });
444
+ throw error;
445
+ }
446
+ }
447
+ /**
448
+ * Apply default visual style settings to objects.
449
+ * - Point-like objects: point size
450
+ * - Line-like objects: line thickness
451
+ */
452
+ async applyDefaultStyleSettings(objectNames, pointSize, lineThickness) {
453
+ this.ensureInitialized();
454
+ this.updateActivity();
455
+ if (objectNames.length === 0) {
456
+ return;
457
+ }
458
+ const uniqueNames = Array.from(new Set(objectNames.filter(name => name && name.trim())));
459
+ if (uniqueNames.length === 0) {
460
+ return;
461
+ }
462
+ try {
463
+ const result = await this.page.evaluate((names, pointSizeValue, lineThicknessValue) => {
464
+ const applet = window.ggbApplet;
465
+ const failed = [];
466
+ const pointTypes = new Set(['point']);
467
+ const lineTypes = new Set([
468
+ 'line',
469
+ 'segment',
470
+ 'ray',
471
+ 'vector',
472
+ 'circle',
473
+ 'conic',
474
+ 'polygon',
475
+ 'function',
476
+ 'curve'
477
+ ]);
478
+ for (const name of names) {
479
+ try {
480
+ if (!applet.exists(name)) {
481
+ failed.push(name);
482
+ continue;
483
+ }
484
+ const rawType = String(applet.getObjectType(name) || '').toLowerCase();
485
+ if (pointTypes.has(rawType)) {
486
+ applet.setPointSize(name, pointSizeValue);
487
+ }
488
+ else if (lineTypes.has(rawType)) {
489
+ applet.setLineThickness(name, lineThicknessValue);
490
+ }
491
+ }
492
+ catch (_error) {
493
+ failed.push(name);
494
+ }
495
+ }
496
+ return { failed };
497
+ }, uniqueNames, pointSize, lineThickness);
498
+ if (Array.isArray(result?.failed) && result.failed.length > 0) {
499
+ logger_1.default.warn(`Failed to apply default style settings for ${result.failed.length} object(s)`, {
500
+ instanceId: this.id,
501
+ failedObjects: result.failed
502
+ });
503
+ }
504
+ }
505
+ catch (error) {
506
+ logger_1.default.error(`Failed to apply default style settings on instance ${this.id}`, {
507
+ objectNames: uniqueNames,
508
+ pointSize,
509
+ lineThickness,
510
+ error
511
+ });
512
+ throw error;
513
+ }
514
+ }
515
+ /**
516
+ * Get object information
517
+ */
518
+ async getObjectInfo(objName) {
519
+ this.ensureInitialized();
520
+ this.updateActivity();
521
+ try {
522
+ const info = await this.page.evaluate((name) => {
523
+ if (!window.ggbApplet.exists(name)) {
524
+ return null;
525
+ }
526
+ const type = window.ggbApplet.getObjectType(name);
527
+ // Base information that all objects have
528
+ const baseInfo = {
529
+ name,
530
+ type,
531
+ visible: window.ggbApplet.getVisible(name),
532
+ defined: window.ggbApplet.isDefined(name),
533
+ color: window.ggbApplet.getColor(name)
534
+ };
535
+ // Try to get value and valueString safely
536
+ let value = null;
537
+ let valueString = null;
538
+ try {
539
+ value = window.ggbApplet.getValue(name);
540
+ }
541
+ catch (e) {
542
+ // Some objects don't have a numeric value
543
+ }
544
+ try {
545
+ valueString = window.ggbApplet.getValueString(name);
546
+ }
547
+ catch (e) {
548
+ // Some objects don't have a value string
549
+ }
550
+ // Only try to get coordinates for objects that support them
551
+ // Points, segments, lines, circles typically have coordinates
552
+ // Functions, curves, implicit curves typically don't
553
+ const coordinateTypes = ['point', 'segment', 'line', 'circle', 'polygon', 'vector'];
554
+ let coordinates = {};
555
+ if (coordinateTypes.includes(type.toLowerCase())) {
556
+ try {
557
+ coordinates = {
558
+ x: window.ggbApplet.getXcoord(name),
559
+ y: window.ggbApplet.getYcoord(name),
560
+ z: window.ggbApplet.getZcoord(name)
561
+ };
562
+ }
563
+ catch (e) {
564
+ // If coordinate access fails, just omit coordinates
565
+ }
566
+ }
567
+ return {
568
+ ...baseInfo,
569
+ value,
570
+ valueString,
571
+ ...coordinates
572
+ };
573
+ }, objName);
574
+ return info;
575
+ }
576
+ catch (error) {
577
+ logger_1.default.error(`Failed to get object info for ${objName} on instance ${this.id}`, error);
578
+ return null;
579
+ }
580
+ }
581
+ /**
582
+ * Get X coordinate of object
583
+ */
584
+ async getXcoord(objName) {
585
+ this.ensureInitialized();
586
+ this.updateActivity();
587
+ try {
588
+ const x = await this.page.evaluate((name) => {
589
+ return window.ggbApplet.getXcoord(name);
590
+ }, objName);
591
+ return x || 0;
592
+ }
593
+ catch (error) {
594
+ logger_1.default.error(`Failed to get X coordinate of ${objName} on instance ${this.id}`, error);
595
+ return 0;
596
+ }
597
+ }
598
+ /**
599
+ * Get Y coordinate of object
600
+ */
601
+ async getYcoord(objName) {
602
+ this.ensureInitialized();
603
+ this.updateActivity();
604
+ try {
605
+ const y = await this.page.evaluate((name) => {
606
+ return window.ggbApplet.getYcoord(name);
607
+ }, objName);
608
+ return y || 0;
609
+ }
610
+ catch (error) {
611
+ logger_1.default.error(`Failed to get Y coordinate of ${objName} on instance ${this.id}`, error);
612
+ return 0;
613
+ }
614
+ }
615
+ /**
616
+ * Get Z coordinate of object
617
+ */
618
+ async getZcoord(objName) {
619
+ this.ensureInitialized();
620
+ this.updateActivity();
621
+ try {
622
+ const z = await this.page.evaluate((name) => {
623
+ return window.ggbApplet.getZcoord(name);
624
+ }, objName);
625
+ return z || 0;
626
+ }
627
+ catch (error) {
628
+ logger_1.default.error(`Failed to get Z coordinate of ${objName} on instance ${this.id}`, error);
629
+ return 0;
630
+ }
631
+ }
632
+ /**
633
+ * Get value of object
634
+ */
635
+ async getValue(objName) {
636
+ this.ensureInitialized();
637
+ this.updateActivity();
638
+ try {
639
+ const value = await this.page.evaluate((name) => {
640
+ return window.ggbApplet.getValue(name);
641
+ }, objName);
642
+ return value || 0;
643
+ }
644
+ catch (error) {
645
+ logger_1.default.error(`Failed to get value of ${objName} on instance ${this.id}`, error);
646
+ return 0;
647
+ }
648
+ }
649
+ /**
650
+ * Get value string of object
651
+ */
652
+ async getValueString(objName) {
653
+ this.ensureInitialized();
654
+ this.updateActivity();
655
+ try {
656
+ const valueStr = await this.page.evaluate((name) => {
657
+ return window.ggbApplet.getValueString(name);
658
+ }, objName);
659
+ return valueStr || '';
660
+ }
661
+ catch (error) {
662
+ logger_1.default.error(`Failed to get value string of ${objName} on instance ${this.id}`, error);
663
+ return '';
664
+ }
665
+ }
666
+ /**
667
+ * Clear construction
668
+ */
669
+ async newConstruction() {
670
+ this.ensureInitialized();
671
+ this.updateActivity();
672
+ try {
673
+ await this.page.evaluate(() => {
674
+ window.ggbApplet.newConstruction();
675
+ });
676
+ logger_1.default.debug(`Construction cleared on instance ${this.id}`);
677
+ }
678
+ catch (error) {
679
+ logger_1.default.error(`Failed to clear construction on instance ${this.id}`, error);
680
+ throw new geogebra_1.GeoGebraError(`Failed to clear construction: ${error instanceof Error ? error.message : String(error)}`);
681
+ }
682
+ }
683
+ /**
684
+ * Reset construction
685
+ */
686
+ async reset() {
687
+ this.ensureInitialized();
688
+ this.updateActivity();
689
+ try {
690
+ await this.page.evaluate(() => {
691
+ window.ggbApplet.reset();
692
+ });
693
+ logger_1.default.debug(`Construction reset on instance ${this.id}`);
694
+ }
695
+ catch (error) {
696
+ logger_1.default.error(`Failed to reset construction on instance ${this.id}`, error);
697
+ throw new geogebra_1.GeoGebraError(`Failed to reset construction: ${error instanceof Error ? error.message : String(error)}`);
698
+ }
699
+ }
700
+ /**
701
+ * Refresh views
702
+ */
703
+ async refreshViews() {
704
+ this.ensureInitialized();
705
+ this.updateActivity();
706
+ try {
707
+ await this.page.evaluate(() => {
708
+ window.ggbApplet.refreshViews();
709
+ });
710
+ logger_1.default.debug(`Views refreshed on instance ${this.id}`);
711
+ }
712
+ catch (error) {
713
+ logger_1.default.error(`Failed to refresh views on instance ${this.id}`, error);
714
+ throw new geogebra_1.GeoGebraError(`Failed to refresh views: ${error instanceof Error ? error.message : String(error)}`);
715
+ }
716
+ }
717
+ /**
718
+ * Set coordinate system bounds
719
+ */
720
+ async setCoordSystem(xmin, xmax, ymin, ymax) {
721
+ this.ensureInitialized();
722
+ this.updateActivity();
723
+ try {
724
+ await this.page.evaluate((xmin, xmax, ymin, ymax) => {
725
+ window.ggbApplet.setCoordSystem(xmin, xmax, ymin, ymax);
726
+ }, xmin, xmax, ymin, ymax);
727
+ logger_1.default.debug(`Coordinate system set on instance ${this.id}: x[${xmin}, ${xmax}], y[${ymin}, ${ymax}]`);
728
+ }
729
+ catch (error) {
730
+ logger_1.default.error(`Failed to set coordinate system on instance ${this.id}`, error);
731
+ throw new geogebra_1.GeoGebraError(`Failed to set coordinate system: ${error instanceof Error ? error.message : String(error)}`);
732
+ }
733
+ }
734
+ /**
735
+ * Set axes visibility
736
+ */
737
+ async setAxesVisible(xAxis, yAxis) {
738
+ this.ensureInitialized();
739
+ this.updateActivity();
740
+ try {
741
+ await this.page.evaluate((xAxis, yAxis) => {
742
+ window.ggbApplet.setAxesVisible(xAxis, yAxis);
743
+ }, xAxis, yAxis);
744
+ logger_1.default.debug(`Axes visibility set on instance ${this.id}: x=${xAxis}, y=${yAxis}`);
745
+ }
746
+ catch (error) {
747
+ logger_1.default.error(`Failed to set axes visibility on instance ${this.id}`, error);
748
+ throw new geogebra_1.GeoGebraError(`Failed to set axes visibility: ${error instanceof Error ? error.message : String(error)}`);
749
+ }
750
+ }
751
+ /**
752
+ * Set grid visibility
753
+ */
754
+ async setGridVisible(visible) {
755
+ this.ensureInitialized();
756
+ this.updateActivity();
757
+ try {
758
+ await this.page.evaluate((visible) => {
759
+ window.ggbApplet.setGridVisible(visible);
760
+ }, visible);
761
+ logger_1.default.debug(`Grid visibility set on instance ${this.id}: ${visible}`);
762
+ }
763
+ catch (error) {
764
+ logger_1.default.error(`Failed to set grid visibility on instance ${this.id}`, error);
765
+ throw new geogebra_1.GeoGebraError(`Failed to set grid visibility: ${error instanceof Error ? error.message : String(error)}`);
766
+ }
767
+ }
768
+ /**
769
+ * Check if GeoGebra is ready
770
+ */
771
+ async isReady() {
772
+ if (!this.page)
773
+ return false;
774
+ try {
775
+ const ready = await this.page.evaluate(() => {
776
+ return window.ggbReady === true && window.ggbApplet;
777
+ });
778
+ return ready;
779
+ }
780
+ catch (error) {
781
+ logger_1.default.error(`Failed to check ready state on instance ${this.id}`, error);
782
+ return false;
783
+ }
784
+ }
785
+ /**
786
+ * Export construction as PNG (base64) with enhanced parameters
787
+ * GEB-17: Enhanced with validation, retry logic, and better error handling
788
+ */
789
+ async exportPNG(scale = 1, transparent = false, dpi = 72, width, height) {
790
+ this.ensureInitialized();
791
+ this.updateActivity();
792
+ // GEB-17: Validate export readiness before attempting
793
+ const readiness = await this.validateExportReadiness('png');
794
+ if (!readiness.ready) {
795
+ throw new geogebra_1.GeoGebraError(`PNG export not ready: ${readiness.issues.join(', ')}. Recommendations: ${readiness.recommendations.join(', ')}`);
796
+ }
797
+ return this.retryOperation(async () => {
798
+ const pngBase64 = await this.page.evaluate((scale, transparent, dpi, width, height) => {
799
+ try {
800
+ // GEB-17: Enhanced applet availability check
801
+ const applet = window.ggbApplet;
802
+ if (!applet) {
803
+ throw new Error('GeoGebra applet not available');
804
+ }
805
+ if (typeof applet.getPNGBase64 !== 'function') {
806
+ throw new Error('getPNGBase64 method not available on applet');
807
+ }
808
+ // Calculate effective scale if width/height provided
809
+ let effectiveScale = scale;
810
+ if (width !== undefined && height !== undefined) {
811
+ // Get current dimensions with better error handling
812
+ let graphics;
813
+ try {
814
+ graphics = applet.getGraphicsOptions ? applet.getGraphicsOptions() : { width: 800, height: 600 };
815
+ }
816
+ catch (e) {
817
+ // Fallback to default dimensions if getGraphicsOptions fails
818
+ graphics = { width: 800, height: 600 };
819
+ }
820
+ const currentWidth = graphics.width || 800;
821
+ const currentHeight = graphics.height || 600;
822
+ const scaleX = width / currentWidth;
823
+ const scaleY = height / currentHeight;
824
+ effectiveScale = Math.min(scaleX, scaleY);
825
+ }
826
+ // GEB-17: Enhanced PNG export with better error handling and validation
827
+ let result = null;
828
+ let lastError = null;
829
+ // Try primary method with full parameters
830
+ try {
831
+ result = applet.getPNGBase64(effectiveScale, transparent, dpi);
832
+ if (result && typeof result === 'string' && result.length > 0) {
833
+ return result; // Success with primary method
834
+ }
835
+ }
836
+ catch (e1) {
837
+ lastError = e1;
838
+ // Continue to fallback methods
839
+ }
840
+ // Try without dpi parameter
841
+ try {
842
+ result = applet.getPNGBase64(effectiveScale, transparent);
843
+ if (result && typeof result === 'string' && result.length > 0) {
844
+ return result; // Success with fallback method
845
+ }
846
+ }
847
+ catch (e2) {
848
+ lastError = e2;
849
+ // Continue to next fallback
850
+ }
851
+ // Try simplest approach with just scale
852
+ try {
853
+ result = applet.getPNGBase64(effectiveScale);
854
+ if (result && typeof result === 'string' && result.length > 0) {
855
+ return result; // Success with simple method
856
+ }
857
+ }
858
+ catch (e3) {
859
+ lastError = e3;
860
+ // Continue to final fallback
861
+ }
862
+ // Try with default scale as last resort
863
+ try {
864
+ result = applet.getPNGBase64(1);
865
+ if (result && typeof result === 'string' && result.length > 0) {
866
+ return result; // Success with default scale
867
+ }
868
+ }
869
+ catch (e4) {
870
+ lastError = e4;
871
+ }
872
+ // If we get here, all methods failed
873
+ throw new Error(`All PNG export methods failed. Last error: ${lastError ? lastError.message : 'Unknown error'}. Result was: ${typeof result} with length ${result ? result.length : 'undefined'}`);
874
+ }
875
+ catch (error) {
876
+ throw new Error(`PNG export failed: ${error.message || error}`);
877
+ }
878
+ }, scale, transparent, dpi, width, height);
879
+ // GEB-17: Enhanced validation of the result
880
+ if (!pngBase64) {
881
+ throw new Error('PNG export returned null or undefined');
882
+ }
883
+ if (typeof pngBase64 !== 'string') {
884
+ throw new Error(`PNG export returned invalid type: ${typeof pngBase64}`);
885
+ }
886
+ if (pngBase64.length === 0) {
887
+ throw new Error('PNG export returned empty string');
888
+ }
889
+ // Enhanced base64 validation
890
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(pngBase64)) {
891
+ throw new Error(`Result is not valid base64. Length: ${pngBase64.length}, starts with: ${pngBase64.substring(0, 50)}`);
892
+ }
893
+ // Additional check for minimum reasonable size (base64 should be substantial for a real image)
894
+ if (pngBase64.length < 100) {
895
+ throw new Error(`PNG result suspiciously small (${pngBase64.length} characters): ${pngBase64}`);
896
+ }
897
+ logger_1.default.debug(`PNG exported from instance ${this.id} with scale ${scale}, transparent ${transparent}, dpi ${dpi}, dimensions ${width}x${height}, result length: ${pngBase64.length}`);
898
+ return pngBase64;
899
+ }, 3, 1000, 'PNG export');
900
+ }
901
+ /**
902
+ * Export construction as SVG
903
+ * GEB-17: Enhanced with better validation and error handling, no more placeholder responses
904
+ */
905
+ /** Raw PGF is internal input to the mandatory Gikz pipeline. */
906
+ async exportNativeTikZ() { return this.nativeExport('tikz'); }
907
+ async exportSVG() { return this.nativeExport('svg'); }
908
+ async exportPDF() { return this.nativeExport('pdf'); }
909
+ async exportGGB() { return this.nativeExport('ggb'); }
910
+ async nativeExport(format) {
911
+ this.ensureInitialized();
912
+ this.updateActivity();
913
+ const result = await this.page.evaluate((kind) => new Promise((resolve, reject) => {
914
+ const api = window.ggbApplet;
915
+ const timer = setTimeout(() => reject(new Error(kind + ' export timed out')), 20000);
916
+ const done = (value) => {
917
+ clearTimeout(timer);
918
+ if (!value || typeof value !== 'string')
919
+ reject(new Error(kind + ' export returned no data'));
920
+ else
921
+ resolve(value);
922
+ };
923
+ try {
924
+ if (kind === 'ggb')
925
+ api.getBase64(done);
926
+ else if (kind === 'svg')
927
+ api.exportSVG(done);
928
+ else if (kind === 'tikz')
929
+ api.exportPGF(done);
930
+ else
931
+ api.exportPDF(1, done);
932
+ }
933
+ catch (e) {
934
+ clearTimeout(timer);
935
+ reject(e);
936
+ }
937
+ }), format);
938
+ if (format === 'tikz') {
939
+ if (!result.includes('\\begin{tikzpicture}'))
940
+ throw new geogebra_1.GeoGebraError('Invalid native TikZ export');
941
+ return result;
942
+ }
943
+ if (format === 'svg') {
944
+ if (!result.includes('<svg'))
945
+ throw new geogebra_1.GeoGebraError('Invalid SVG export');
946
+ return result;
947
+ }
948
+ const base64 = result.replace(/^data:[^,]+,/, '');
949
+ const bytes = Buffer.from(base64, 'base64');
950
+ if (format === 'ggb' && bytes.subarray(0, 2).toString() !== 'PK')
951
+ throw new geogebra_1.GeoGebraError('GGB export is not a ZIP archive');
952
+ if (format === 'pdf' && bytes.subarray(0, 5).toString() !== '%PDF-')
953
+ throw new geogebra_1.GeoGebraError('Invalid native PDF export');
954
+ return base64;
955
+ }
956
+ /** Load a generated viewer and wait for its native applet and responsive layout. */
957
+ async renderHtml(html) {
958
+ this.ensureInitialized();
959
+ // A fresh document prevents lexical globals and old applet callbacks leaking between renders.
960
+ await this.page.goto("about:blank");
961
+ await this.page.setContent(html);
962
+ await this.page.waitForFunction('window.ggbReady || window.ggbError', { timeout: 60000 });
963
+ const error = await this.page.evaluate(() => window.ggbError);
964
+ if (error)
965
+ throw new geogebra_1.GeoGebraConnectionError(error);
966
+ await this.page.waitForFunction(() => {
967
+ const view = JSON.parse(window.ggbApplet.getViewProperties(1));
968
+ return view.width > 0 && view.height > 0 && Math.abs(view.invXscale / view.invYscale - 1) < 1e-6;
969
+ }, { timeout: 10000 });
970
+ }
971
+ async capturePreview() {
972
+ this.ensureInitialized();
973
+ // Native LaTeX loads fonts after appletOnLoad; capture the completed paint.
974
+ // GeoGebra maintains background requests, so network idle is not a readiness condition.
975
+ await this.page.evaluate(() => new Promise(resolve => setTimeout(resolve, 2000)));
976
+ await this.page.evaluate(async () => { await document.fonts.ready; await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))); });
977
+ return this.page.screenshot({ type: 'png' });
978
+ }
979
+ async getConstructionXML() {
980
+ this.ensureInitialized();
981
+ return this.page.evaluate(() => String(window.ggbApplet.getXML()));
982
+ }
983
+ /**
984
+ * Validate that a file path is within the workspace directory to prevent path traversal attacks
985
+ * @param filePath - The file path to validate
986
+ * @param workspaceDir - The workspace directory
987
+ * @returns The validated absolute path
988
+ * @throws Error if path is outside workspace
989
+ */
990
+ validateFilePath(filePath, workspaceDir) {
991
+ const path = require('path');
992
+ const fullPath = path.resolve(workspaceDir, filePath);
993
+ const normalizedWorkspace = path.resolve(workspaceDir);
994
+ // Check if the resolved path is within the workspace
995
+ if (!fullPath.startsWith(normalizedWorkspace + path.sep) && fullPath !== normalizedWorkspace) {
996
+ throw new Error(`Path traversal detected: ${filePath} resolves outside workspace`);
997
+ }
998
+ return fullPath;
999
+ }
1000
+ /**
1001
+ * Calculate optimal view coordinates to fit all visible objects
1002
+ * @param padding - Padding percentage around objects (default: 0.2 for 20% margin)
1003
+ * @param minRange - Minimum range for each axis (default: 2)
1004
+ * @returns View coordinates {xmin, xmax, ymin, ymax} or null if no visible points
1005
+ */
1006
+ async calculateAutoZoomCoordinates(padding = 0.2, minRange = 2) {
1007
+ this.ensureInitialized();
1008
+ this.updateActivity();
1009
+ const objectNames = await this.getAllObjectNames();
1010
+ const allObjects = [];
1011
+ for (const name of objectNames) {
1012
+ const info = await this.getObjectInfo(name);
1013
+ if (info) {
1014
+ allObjects.push(info);
1015
+ }
1016
+ }
1017
+ const visiblePoints = allObjects.filter((obj) => obj.type === 'point' && obj.visible && obj.defined &&
1018
+ typeof obj.x === 'number' && typeof obj.y === 'number');
1019
+ if (visiblePoints.length === 0) {
1020
+ return null;
1021
+ }
1022
+ let minX = Infinity;
1023
+ let maxX = -Infinity;
1024
+ let minY = Infinity;
1025
+ let maxY = -Infinity;
1026
+ for (const point of visiblePoints) {
1027
+ minX = Math.min(minX, point.x);
1028
+ maxX = Math.max(maxX, point.x);
1029
+ minY = Math.min(minY, point.y);
1030
+ maxY = Math.max(maxY, point.y);
1031
+ }
1032
+ let rangeX = maxX - minX;
1033
+ let rangeY = maxY - minY;
1034
+ // Ensure minimum range
1035
+ if (rangeX < minRange) {
1036
+ const center = (minX + maxX) / 2;
1037
+ minX = center - minRange / 2;
1038
+ maxX = center + minRange / 2;
1039
+ rangeX = minRange;
1040
+ }
1041
+ if (rangeY < minRange) {
1042
+ const center = (minY + maxY) / 2;
1043
+ minY = center - minRange / 2;
1044
+ maxY = center + minRange / 2;
1045
+ rangeY = minRange;
1046
+ }
1047
+ // Apply padding
1048
+ const padX = rangeX * padding;
1049
+ const padY = rangeY * padding;
1050
+ return {
1051
+ xmin: minX - padX,
1052
+ xmax: maxX + padX,
1053
+ ymin: minY - padY,
1054
+ ymax: maxY + padY
1055
+ };
1056
+ }
1057
+ /**
1058
+ * Apply auto-zoom to fit all visible objects
1059
+ * @param padding - Padding percentage around objects (default: 0.2 for 20% margin)
1060
+ * @param minRange - Minimum range for each axis (default: 2)
1061
+ * @returns true if auto-zoom was applied, false if no visible points
1062
+ */
1063
+ async applyAutoZoom(padding = 0.2, minRange = 2) {
1064
+ this.ensureInitialized();
1065
+ if (!Number.isFinite(padding) || padding < 0 || padding > 2 || !Number.isFinite(minRange) || minRange <= 0)
1066
+ throw new geogebra_1.GeoGebraError('Invalid auto zoom padding or minimum range');
1067
+ return this.page.evaluate(async (pad, minimum) => {
1068
+ const api = window.ggbApplet;
1069
+ if (!api.getAllObjectNames().length)
1070
+ return false;
1071
+ // Native fitting includes conic extents. Preserve equal coordinate scales.
1072
+ api.showAllObjects();
1073
+ // GeoGebra animates fitting; read the bounds after that animation settles.
1074
+ await new Promise(resolve => setTimeout(resolve, 600));
1075
+ const view = JSON.parse(api.getViewProperties(1));
1076
+ const spanX = Math.max(view.width * view.invXscale * (1 + 2 * pad), minimum);
1077
+ const spanY = Math.max(view.height * view.invYscale * (1 + 2 * pad), minimum);
1078
+ const scale = Math.max(spanX / view.width, spanY / view.height);
1079
+ const cx = view.xMin + view.width * view.invXscale / 2;
1080
+ const cy = view.yMin + view.height * view.invYscale / 2;
1081
+ api.setCoordSystem(cx - view.width * scale / 2, cx + view.width * scale / 2, cy - view.height * scale / 2, cy + view.height * scale / 2);
1082
+ await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
1083
+ return true;
1084
+ }, padding, minRange);
1085
+ }
1086
+ /**
1087
+ * Load a GeoGebra construction from a .ggb file
1088
+ * @param ggbBase64OrPath - Either base64-encoded .ggb content or file path
1089
+ */
1090
+ async loadGGB(ggbBase64OrPath) {
1091
+ this.ensureInitialized();
1092
+ this.updateActivity();
1093
+ return this.retryOperation(async () => {
1094
+ let ggbBase64 = ggbBase64OrPath;
1095
+ // Check if it's a file path (contains path separators or ends with .ggb)
1096
+ if (!(/^[A-Za-z0-9+/]+={0,2}$/.test(ggbBase64OrPath) && Buffer.from(ggbBase64OrPath, 'base64').subarray(0, 2).toString() === 'PK')) {
1097
+ // It's a file path, read the file
1098
+ const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
1099
+ const path = await Promise.resolve().then(() => __importStar(require('path')));
1100
+ // Resolve path relative to workspace or current directory
1101
+ const workspaceDir = (0, workspace_1.currentWorkspace)();
1102
+ const fullPath = this.validateFilePath(ggbBase64OrPath, workspaceDir);
1103
+ try {
1104
+ await fs.access(fullPath);
1105
+ }
1106
+ catch (error) {
1107
+ throw new Error(`GGB file not found: ${fullPath}`);
1108
+ }
1109
+ // Read file and convert to base64 (async)
1110
+ const buffer = await fs.readFile(fullPath);
1111
+ ggbBase64 = buffer.toString('base64');
1112
+ }
1113
+ // Validate base64 format
1114
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(ggbBase64)) {
1115
+ throw new Error('Invalid base64 format for GGB data');
1116
+ }
1117
+ await this.page.evaluate((base64Data) => new Promise((resolve, reject) => {
1118
+ const timer = setTimeout(() => reject(new Error('GGB load timed out')), 20000);
1119
+ try {
1120
+ window.ggbApplet.setBase64(base64Data, () => { clearTimeout(timer); resolve(); });
1121
+ }
1122
+ catch (error) {
1123
+ clearTimeout(timer);
1124
+ reject(error);
1125
+ }
1126
+ }), ggbBase64);
1127
+ logger_1.default.debug(`GGB file loaded into instance ${this.id}`);
1128
+ }, 3, 1000, 'GGB load');
1129
+ }
1130
+ /**
1131
+ * Export animation as GIF or video frames
1132
+ * GEB-17: Enhanced with comprehensive applet availability checks
1133
+ */
1134
+ async exportAnimation(options = {}) {
1135
+ this.ensureInitialized();
1136
+ this.updateActivity();
1137
+ // GEB-17: Validate export readiness before attempting
1138
+ const readiness = await this.validateExportReadiness('animation');
1139
+ if (!readiness.ready) {
1140
+ throw new geogebra_1.GeoGebraError(`Animation export not ready: ${readiness.issues.join(', ')}. Recommendations: ${readiness.recommendations.join(', ')}`);
1141
+ }
1142
+ const { duration = 5000, // 5 seconds
1143
+ frameRate = 10, // 10 fps
1144
+ format = 'frames', width, height } = options;
1145
+ return this.retryOperation(async () => {
1146
+ // GEB-17: Enhanced applet availability and animation readiness check
1147
+ const animationCheck = await this.page.evaluate(() => {
1148
+ const applet = window.ggbApplet;
1149
+ if (!applet) {
1150
+ return { ready: false, error: 'GeoGebra applet not available' };
1151
+ }
1152
+ if (typeof applet.startAnimation !== 'function' || typeof applet.stopAnimation !== 'function') {
1153
+ return { ready: false, error: 'Animation methods not available on applet' };
1154
+ }
1155
+ if (typeof applet.isAnimationRunning !== 'function') {
1156
+ return { ready: false, error: 'Animation status method not available' };
1157
+ }
1158
+ // Check if there are any objects that can be animated
1159
+ try {
1160
+ const allObjects = applet.getAllObjectNames();
1161
+ if (!allObjects || allObjects.length === 0) {
1162
+ return { ready: false, error: 'No objects available for animation' };
1163
+ }
1164
+ }
1165
+ catch (e) {
1166
+ return { ready: false, error: 'Cannot access object list for animation' };
1167
+ }
1168
+ return { ready: true, error: null };
1169
+ });
1170
+ if (!animationCheck.ready) {
1171
+ throw new Error(`Animation export failed: ${animationCheck.error}`);
1172
+ }
1173
+ const frameInterval = 1000 / frameRate;
1174
+ const totalFrames = Math.ceil(duration / frameInterval);
1175
+ const frames = [];
1176
+ logger_1.default.debug(`Starting animation capture for instance ${this.id}: ${totalFrames} frames over ${duration}ms`);
1177
+ try {
1178
+ // Start animation
1179
+ await this.page.evaluate(() => {
1180
+ window.ggbApplet.startAnimation();
1181
+ });
1182
+ // Capture frames
1183
+ for (let i = 0; i < totalFrames; i++) {
1184
+ // Wait for frame interval
1185
+ await new Promise(resolve => setTimeout(resolve, frameInterval));
1186
+ // Capture frame as PNG
1187
+ try {
1188
+ const frameData = await this.exportPNG(1, false, 72, width, height);
1189
+ frames.push(frameData);
1190
+ logger_1.default.debug(`Captured frame ${i + 1}/${totalFrames} for instance ${this.id}`);
1191
+ }
1192
+ catch (frameError) {
1193
+ logger_1.default.warn(`Failed to capture frame ${i + 1}/${totalFrames} for instance ${this.id}:`, frameError);
1194
+ // Continue with next frame rather than failing completely
1195
+ }
1196
+ }
1197
+ // Stop animation
1198
+ await this.page.evaluate(() => {
1199
+ window.ggbApplet.stopAnimation();
1200
+ });
1201
+ if (frames.length === 0) {
1202
+ throw new Error('No frames were successfully captured');
1203
+ }
1204
+ logger_1.default.debug(`Animation capture completed for instance ${this.id}: ${frames.length} frames captured`);
1205
+ if (format === 'frames') {
1206
+ return frames;
1207
+ }
1208
+ else {
1209
+ // For GIF format, we would need additional processing
1210
+ // For now, return the frames and let the caller handle GIF creation
1211
+ logger_1.default.warn('GIF format not yet implemented, returning frames');
1212
+ return frames;
1213
+ }
1214
+ }
1215
+ catch (error) {
1216
+ // Ensure animation is stopped even if capture fails
1217
+ try {
1218
+ await this.page.evaluate(() => {
1219
+ window.ggbApplet.stopAnimation();
1220
+ });
1221
+ }
1222
+ catch (stopError) {
1223
+ logger_1.default.error(`Failed to stop animation after capture error:`, stopError);
1224
+ }
1225
+ throw error;
1226
+ }
1227
+ }, 2, 3000, 'Animation export'); // Fewer retries, longer delay for animation
1228
+ }
1229
+ /**
1230
+ * Cleanup resources
1231
+ */
1232
+ async cleanup() {
1233
+ logger_1.default.debug(`Cleaning up GeoGebra instance ${this.id}`);
1234
+ try {
1235
+ if (this.page && !this.page.isClosed()) {
1236
+ await this.page.close();
1237
+ }
1238
+ }
1239
+ catch (error) {
1240
+ logger_1.default.error(`Error closing page for instance ${this.id}`, error);
1241
+ }
1242
+ try {
1243
+ if (this.browser) {
1244
+ await this.browser.close();
1245
+ }
1246
+ }
1247
+ catch (error) {
1248
+ logger_1.default.error(`Error closing browser for instance ${this.id}`, error);
1249
+ }
1250
+ this.page = undefined;
1251
+ this.browser = undefined;
1252
+ this.isInitialized = false;
1253
+ logger_1.default.debug(`GeoGebra instance ${this.id} cleaned up successfully`);
1254
+ }
1255
+ /**
1256
+ * Get instance state
1257
+ */
1258
+ getState() {
1259
+ return {
1260
+ id: this.id,
1261
+ isReady: this.isInitialized,
1262
+ lastActivity: this.lastActivity,
1263
+ config: this.config
1264
+ };
1265
+ }
1266
+ /**
1267
+ * GEB-17: Enhanced diagnostic method to check applet health
1268
+ */
1269
+ async checkAppletHealth() {
1270
+ this.ensureInitialized();
1271
+ try {
1272
+ const healthCheck = await this.page.evaluate(() => {
1273
+ const issues = [];
1274
+ const applet = window.ggbApplet;
1275
+ // Check basic applet availability
1276
+ const hasApplet = !!applet;
1277
+ if (!hasApplet) {
1278
+ issues.push('GeoGebra applet not found');
1279
+ }
1280
+ // Check core methods
1281
+ const hasEvalCommand = hasApplet && typeof applet.evalCommand === 'function';
1282
+ if (!hasEvalCommand) {
1283
+ issues.push('evalCommand method not available');
1284
+ }
1285
+ // Check export methods
1286
+ const hasPNGExport = hasApplet && typeof applet.getPNGBase64 === 'function';
1287
+ const hasSVGExport = hasApplet && (typeof applet.exportSVG === 'function' || typeof applet.getSVG === 'function');
1288
+ if (!hasPNGExport) {
1289
+ issues.push('PNG export method (getPNGBase64) not available');
1290
+ }
1291
+ if (!hasSVGExport) {
1292
+ issues.push('SVG export methods not available');
1293
+ }
1294
+ // Check animation methods
1295
+ const hasAnimationMethods = hasApplet &&
1296
+ typeof applet.startAnimation === 'function' &&
1297
+ typeof applet.stopAnimation === 'function';
1298
+ if (!hasAnimationMethods) {
1299
+ issues.push('Animation methods not available');
1300
+ }
1301
+ // Test a simple command to verify functionality
1302
+ let commandWorks = false;
1303
+ if (hasEvalCommand) {
1304
+ try {
1305
+ // Try a harmless command
1306
+ const result = typeof applet.evalCommand === 'function' && typeof applet.getAllObjectNames === 'function';
1307
+ commandWorks = Boolean(result);
1308
+ }
1309
+ catch (e) {
1310
+ issues.push('Command execution test failed: ' + e.message);
1311
+ }
1312
+ }
1313
+ return {
1314
+ issues,
1315
+ capabilities: {
1316
+ hasApplet,
1317
+ hasEvalCommand: hasEvalCommand && commandWorks,
1318
+ hasExportMethods: hasPNGExport && hasSVGExport,
1319
+ hasAnimationMethods
1320
+ },
1321
+ exportAvailability: {
1322
+ png: hasPNGExport,
1323
+ svg: hasSVGExport,
1324
+ pdf: hasApplet && typeof applet.exportPDF === 'function'
1325
+ }
1326
+ };
1327
+ });
1328
+ const isHealthy = healthCheck.issues.length === 0;
1329
+ return {
1330
+ isHealthy,
1331
+ ...healthCheck
1332
+ };
1333
+ }
1334
+ catch (error) {
1335
+ return {
1336
+ isHealthy: false,
1337
+ issues: [`Health check failed: ${error instanceof Error ? error.message : String(error)}`],
1338
+ capabilities: {
1339
+ hasApplet: false,
1340
+ hasEvalCommand: false,
1341
+ hasExportMethods: false,
1342
+ hasAnimationMethods: false
1343
+ },
1344
+ exportAvailability: {
1345
+ png: false,
1346
+ svg: false,
1347
+ pdf: false
1348
+ }
1349
+ };
1350
+ }
1351
+ }
1352
+ /**
1353
+ * GEB-17: Enhanced method to validate export readiness before attempting export
1354
+ */
1355
+ async validateExportReadiness(exportType) {
1356
+ this.ensureInitialized();
1357
+ const health = await this.checkAppletHealth();
1358
+ const issues = [];
1359
+ const recommendations = [];
1360
+ if (!health.isHealthy) {
1361
+ issues.push(...health.issues);
1362
+ recommendations.push('Reinitialize the GeoGebra instance');
1363
+ }
1364
+ // Type-specific checks
1365
+ switch (exportType) {
1366
+ case 'png':
1367
+ if (!health.exportAvailability.png) {
1368
+ issues.push('PNG export method not available');
1369
+ recommendations.push('Check GeoGebra applet initialization');
1370
+ }
1371
+ break;
1372
+ case 'svg':
1373
+ if (!health.exportAvailability.svg) {
1374
+ issues.push('SVG export method not available');
1375
+ recommendations.push('Check GeoGebra applet initialization');
1376
+ }
1377
+ break;
1378
+ case 'pdf':
1379
+ // PDF uses browser PDF generation, check page availability
1380
+ if (!this.page || this.page.isClosed()) {
1381
+ issues.push('Browser page not available for PDF generation');
1382
+ recommendations.push('Reinitialize the browser instance');
1383
+ }
1384
+ break;
1385
+ case 'animation':
1386
+ if (!health.capabilities.hasAnimationMethods) {
1387
+ issues.push('Animation methods not available');
1388
+ recommendations.push('Check GeoGebra applet initialization');
1389
+ }
1390
+ // Check if there are objects to animate
1391
+ try {
1392
+ const objects = await this.getAllObjectNames();
1393
+ if (objects.length === 0) {
1394
+ issues.push('No objects available for animation');
1395
+ recommendations.push('Create geometric objects before exporting animation');
1396
+ }
1397
+ }
1398
+ catch (e) {
1399
+ issues.push('Cannot check for objects to animate');
1400
+ }
1401
+ break;
1402
+ }
1403
+ return {
1404
+ ready: issues.length === 0,
1405
+ issues,
1406
+ recommendations
1407
+ };
1408
+ }
1409
+ /**
1410
+ * GEB-17: Retry mechanism for failed operations
1411
+ */
1412
+ async retryOperation(operation, maxRetries = 3, delay = 1000, operationName = 'operation') {
1413
+ let lastError;
1414
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1415
+ try {
1416
+ logger_1.default.debug(`Attempting ${operationName} (attempt ${attempt}/${maxRetries})`);
1417
+ return await operation();
1418
+ }
1419
+ catch (error) {
1420
+ lastError = error;
1421
+ logger_1.default.warn(`${operationName} attempt ${attempt} failed:`, error);
1422
+ if (attempt < maxRetries) {
1423
+ // Wait before retrying, with exponential backoff
1424
+ const waitTime = delay * Math.pow(2, attempt - 1);
1425
+ logger_1.default.debug(`Waiting ${waitTime}ms before retry`);
1426
+ await new Promise(resolve => setTimeout(resolve, waitTime));
1427
+ }
1428
+ }
1429
+ }
1430
+ throw new Error(`${operationName} failed after ${maxRetries} attempts: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
1431
+ }
1432
+ /**
1433
+ * Execute code in the browser context for debugging
1434
+ */
1435
+ async debugEvaluate(code) {
1436
+ this.ensureInitialized();
1437
+ if (!this.page) {
1438
+ throw new geogebra_1.GeoGebraConnectionError('Page not available');
1439
+ }
1440
+ return await this.page.evaluate(code);
1441
+ }
1442
+ /**
1443
+ * Update last activity timestamp
1444
+ */
1445
+ updateActivity() {
1446
+ this.lastActivity = new Date();
1447
+ }
1448
+ /**
1449
+ * Ensure instance is initialized
1450
+ */
1451
+ ensureInitialized() {
1452
+ if (!this.isInitialized) {
1453
+ throw new geogebra_1.GeoGebraConnectionError('GeoGebra instance not initialized');
1454
+ }
1455
+ }
1456
+ /**
1457
+ * Animation Methods
1458
+ */
1459
+ async setAnimating(objName, animate) {
1460
+ this.ensureInitialized();
1461
+ this.updateActivity();
1462
+ try {
1463
+ await this.page.evaluate((objName, animate) => {
1464
+ window.ggbApplet.setAnimating(objName, animate);
1465
+ }, objName, animate);
1466
+ logger_1.default.debug(`Set animating for ${objName} on instance ${this.id}: ${animate}`);
1467
+ }
1468
+ catch (error) {
1469
+ logger_1.default.error(`Failed to set animating for ${objName} on instance ${this.id}`, error);
1470
+ throw new geogebra_1.GeoGebraError(`Failed to set animating: ${error instanceof Error ? error.message : String(error)}`);
1471
+ }
1472
+ }
1473
+ async setAnimationSpeed(objName, speed) {
1474
+ this.ensureInitialized();
1475
+ this.updateActivity();
1476
+ try {
1477
+ await this.page.evaluate((objName, speed) => {
1478
+ window.ggbApplet.setAnimationSpeed(objName, speed);
1479
+ }, objName, speed);
1480
+ logger_1.default.debug(`Set animation speed for ${objName} on instance ${this.id}: ${speed}`);
1481
+ }
1482
+ catch (error) {
1483
+ logger_1.default.error(`Failed to set animation speed for ${objName} on instance ${this.id}`, error);
1484
+ throw new geogebra_1.GeoGebraError(`Failed to set animation speed: ${error instanceof Error ? error.message : String(error)}`);
1485
+ }
1486
+ }
1487
+ async startAnimation() {
1488
+ this.ensureInitialized();
1489
+ this.updateActivity();
1490
+ try {
1491
+ await this.page.evaluate(() => {
1492
+ window.ggbApplet.startAnimation();
1493
+ });
1494
+ logger_1.default.debug(`Started animation on instance ${this.id}`);
1495
+ }
1496
+ catch (error) {
1497
+ logger_1.default.error(`Failed to start animation on instance ${this.id}`, error);
1498
+ throw new geogebra_1.GeoGebraError(`Failed to start animation: ${error instanceof Error ? error.message : String(error)}`);
1499
+ }
1500
+ }
1501
+ async stopAnimation() {
1502
+ this.ensureInitialized();
1503
+ this.updateActivity();
1504
+ try {
1505
+ await this.page.evaluate(() => {
1506
+ window.ggbApplet.stopAnimation();
1507
+ });
1508
+ logger_1.default.debug(`Stopped animation on instance ${this.id}`);
1509
+ }
1510
+ catch (error) {
1511
+ logger_1.default.error(`Failed to stop animation on instance ${this.id}`, error);
1512
+ throw new geogebra_1.GeoGebraError(`Failed to stop animation: ${error instanceof Error ? error.message : String(error)}`);
1513
+ }
1514
+ }
1515
+ async isAnimationRunning() {
1516
+ this.ensureInitialized();
1517
+ this.updateActivity();
1518
+ try {
1519
+ const isRunning = await this.page.evaluate(() => {
1520
+ return window.ggbApplet.isAnimationRunning();
1521
+ });
1522
+ logger_1.default.debug(`Animation running status on instance ${this.id}: ${isRunning}`);
1523
+ return isRunning;
1524
+ }
1525
+ catch (error) {
1526
+ logger_1.default.error(`Failed to check animation status on instance ${this.id}`, error);
1527
+ throw new geogebra_1.GeoGebraError(`Failed to check animation status: ${error instanceof Error ? error.message : String(error)}`);
1528
+ }
1529
+ }
1530
+ async setTrace(objName, flag) {
1531
+ this.ensureInitialized();
1532
+ this.updateActivity();
1533
+ try {
1534
+ await this.page.evaluate((objName, flag) => {
1535
+ window.ggbApplet.setTrace(objName, flag);
1536
+ }, objName, flag);
1537
+ logger_1.default.debug(`Set trace for ${objName} on instance ${this.id}: ${flag}`);
1538
+ }
1539
+ catch (error) {
1540
+ logger_1.default.error(`Failed to set trace for ${objName} on instance ${this.id}`, error);
1541
+ throw new geogebra_1.GeoGebraError(`Failed to set trace: ${error instanceof Error ? error.message : String(error)}`);
1542
+ }
1543
+ }
1544
+ async setValue(objName, value) {
1545
+ this.ensureInitialized();
1546
+ this.updateActivity();
1547
+ try {
1548
+ await this.page.evaluate((objName, value) => {
1549
+ window.ggbApplet.setValue(objName, value);
1550
+ }, objName, value);
1551
+ logger_1.default.debug(`Set value for ${objName} on instance ${this.id}: ${value}`);
1552
+ }
1553
+ catch (error) {
1554
+ logger_1.default.error(`Failed to set value for ${objName} on instance ${this.id}`, error);
1555
+ throw new geogebra_1.GeoGebraError(`Failed to set value: ${error instanceof Error ? error.message : String(error)}`);
1556
+ }
1557
+ }
1558
+ }
1559
+ exports.GeoGebraInstance = GeoGebraInstance;
1560
+ //# sourceMappingURL=geogebra-instance.js.map