@pie-players/pie-section-player 0.3.16 → 0.3.19

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.
@@ -0,0 +1,460 @@
1
+ var k = Object.defineProperty;
2
+ var A = (r, t, e) => t in r ? k(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
3
+ var a = (r, t, e) => A(r, typeof t != "symbol" ? t + "" : t, e);
4
+ const b = (r) => {
5
+ const e = (r.providerOptions && typeof r.providerOptions == "object" ? r.providerOptions : {}).__pieTelemetry;
6
+ return typeof e == "function" ? e : void 0;
7
+ }, R = {
8
+ pie: 3e3,
9
+ custom: 3e3
10
+ }, v = (r) => r.replace(/\/+$/, ""), I = (r) => {
11
+ const t = v(r.apiEndpoint), e = (r.provider || "").toLowerCase();
12
+ return e === "polly" || e === "google" ? `${t}/${e}/voices` : `${t}/voices`;
13
+ }, w = (r) => r.transportMode === "custom" ? "custom" : r.transportMode === "pie" ? "pie" : r.provider === "custom" ? "custom" : "pie", S = (r, t) => r.endpointMode ? r.endpointMode : t === "custom" ? "rootPost" : "synthesizePath", M = (r, t) => r.endpointValidationMode ? r.endpointValidationMode : t === "custom" ? "none" : "voices", E = (r) => {
14
+ const t = r.providerOptions || {};
15
+ if (typeof t.speedRate == "string")
16
+ return t.speedRate;
17
+ const e = Number(r.rate ?? 1);
18
+ return !Number.isFinite(e) || e <= 0.95 ? "slow" : e >= 1.5 ? "fast" : "medium";
19
+ }, P = (r) => {
20
+ const t = [];
21
+ let e = 0;
22
+ const o = r.split(`
23
+ `).map((i) => i.trim()).filter(Boolean);
24
+ for (const i of o)
25
+ try {
26
+ const s = JSON.parse(i), d = typeof s.type == "string" ? s.type : "word", n = typeof s.time == "number" && Number.isFinite(s.time) ? s.time : 0, c = typeof s.value == "string" ? s.value : "", l = typeof s.start == "number" && Number.isFinite(s.start) ? s.start : null, u = typeof s.end == "number" && Number.isFinite(s.end) ? s.end : null, m = l ?? e, y = u ?? m + Math.max(1, c.length || String(s.value || "").length);
27
+ e = Math.max(y + 1, e), t.push({
28
+ time: n,
29
+ type: d,
30
+ start: m,
31
+ end: y,
32
+ value: c
33
+ });
34
+ } catch {
35
+ }
36
+ return t;
37
+ }, O = {
38
+ id: "pie",
39
+ resolveSynthesisUrl: (r) => {
40
+ const t = S(r, "pie"), e = v(r.apiEndpoint);
41
+ return t === "rootPost" ? e : `${e}/synthesize`;
42
+ },
43
+ buildRequestBody: (r, t) => {
44
+ const e = t.providerOptions || {}, o = typeof t.engine == "string" ? t.engine : typeof e.engine == "string" ? e.engine : void 0, i = typeof e.sampleRate == "number" && Number.isFinite(e.sampleRate) ? e.sampleRate : void 0, s = e.format === "mp3" || e.format === "ogg" || e.format === "pcm" ? e.format : void 0, d = Array.isArray(e.speechMarkTypes) ? e.speechMarkTypes.filter((n) => n === "word" || n === "sentence" || n === "ssml") : void 0;
45
+ return {
46
+ text: r,
47
+ provider: t.provider || "polly",
48
+ voice: t.voice,
49
+ language: t.language,
50
+ rate: t.rate,
51
+ engine: o,
52
+ sampleRate: i,
53
+ format: s,
54
+ speechMarkTypes: d,
55
+ includeSpeechMarks: !0
56
+ };
57
+ },
58
+ parseResponse: async (r) => {
59
+ const t = await r.json();
60
+ return {
61
+ audio: {
62
+ kind: "base64",
63
+ data: t.audio,
64
+ contentType: t.contentType
65
+ },
66
+ speechMarks: Array.isArray(t.speechMarks) ? t.speechMarks : []
67
+ };
68
+ }
69
+ }, C = {
70
+ id: "custom",
71
+ resolveSynthesisUrl: (r) => {
72
+ const t = S(r, "custom"), e = v(r.apiEndpoint);
73
+ return t === "synthesizePath" ? `${e}/synthesize` : e;
74
+ },
75
+ buildRequestBody: (r, t) => {
76
+ const e = t.providerOptions || {}, o = typeof e.lang_id == "string" ? e.lang_id : t.language || "en-US", i = typeof e.cache == "boolean" ? e.cache : !0;
77
+ return {
78
+ text: r,
79
+ speedRate: E(t),
80
+ lang_id: o,
81
+ cache: i
82
+ };
83
+ },
84
+ parseResponse: async (r, t, e, o) => {
85
+ const i = await r.json(), s = {};
86
+ if (t.includeAuthOnAssetFetch)
87
+ for (const [n, c] of Object.entries(e))
88
+ n.toLowerCase() === "authorization" && (s[n] = c);
89
+ let d = [];
90
+ if (typeof i.word == "string" && i.word.length > 0) {
91
+ const n = await fetch(i.word, {
92
+ headers: s,
93
+ signal: o
94
+ });
95
+ if (n.ok) {
96
+ const c = await n.text();
97
+ d = P(c);
98
+ }
99
+ }
100
+ return {
101
+ audio: {
102
+ kind: "url",
103
+ url: i.audioContent
104
+ },
105
+ speechMarks: d
106
+ };
107
+ }
108
+ }, B = {
109
+ pie: O,
110
+ custom: C
111
+ };
112
+ class z {
113
+ constructor(t, e) {
114
+ a(this, "config");
115
+ a(this, "adapter");
116
+ a(this, "currentAudio", null);
117
+ a(this, "pausedState", !1);
118
+ a(this, "wordTimings", []);
119
+ a(this, "highlightInterval", null);
120
+ a(this, "intentionallyStopped", !1);
121
+ a(this, "activeSynthesisController", null);
122
+ a(this, "synthesisRunId", 0);
123
+ a(this, "telemetryReporter");
124
+ a(this, "onWordBoundary");
125
+ this.config = t, this.adapter = e, this.telemetryReporter = b(t);
126
+ }
127
+ async emitTelemetry(t, e) {
128
+ try {
129
+ await this.telemetryReporter?.(t, e);
130
+ } catch (o) {
131
+ console.warn("[ServerTTSProvider] telemetry callback failed:", o);
132
+ }
133
+ }
134
+ async speak(t) {
135
+ this.stop(), this.intentionallyStopped = !1;
136
+ const e = ++this.synthesisRunId, o = new AbortController();
137
+ this.activeSynthesisController = o;
138
+ const { audioUrl: i, wordTimings: s } = await this.synthesizeSpeech(t, o.signal, e);
139
+ if (e !== this.synthesisRunId) {
140
+ URL.revokeObjectURL(i);
141
+ return;
142
+ }
143
+ const d = this.config.rate || 1;
144
+ return this.wordTimings = s.map((n) => ({
145
+ ...n,
146
+ time: n.time / d
147
+ })), new Promise((n, c) => {
148
+ const l = new Audio(i);
149
+ this.currentAudio = l, this.config.rate && (l.playbackRate = Math.max(0.25, Math.min(4, this.config.rate))), this.config.volume !== void 0 && (l.volume = Math.max(0, Math.min(1, this.config.volume))), l.onplay = () => {
150
+ this.pausedState = !1, this.onWordBoundary && this.wordTimings.length > 0 && this.startWordHighlighting();
151
+ }, l.onended = () => {
152
+ this.stopWordHighlighting(), URL.revokeObjectURL(i), this.currentAudio = null, this.wordTimings = [], n();
153
+ }, l.onerror = (u) => {
154
+ this.stopWordHighlighting(), URL.revokeObjectURL(i), this.currentAudio = null, this.wordTimings = [], this.intentionallyStopped ? n() : c(new Error("Failed to play audio from server"));
155
+ }, l.onpause = () => {
156
+ this.stopWordHighlighting(), this.pausedState = !0;
157
+ }, l.play().catch(c);
158
+ });
159
+ }
160
+ /**
161
+ * Call server API to synthesize speech
162
+ */
163
+ async synthesizeSpeech(t, e, o) {
164
+ const i = Date.now();
165
+ await this.emitTelemetry("pie-tool-backend-call-start", {
166
+ toolId: "tts",
167
+ backend: this.config.provider || "server",
168
+ operation: "synthesize-speech"
169
+ });
170
+ const s = {
171
+ "Content-Type": "application/json",
172
+ ...this.config.headers
173
+ };
174
+ this.config.authToken && (s.Authorization = `Bearer ${this.config.authToken}`);
175
+ const d = this.adapter.resolveSynthesisUrl(this.config), n = this.adapter.buildRequestBody(t, this.config), c = await (async () => {
176
+ try {
177
+ return await fetch(d, {
178
+ method: "POST",
179
+ headers: s,
180
+ body: JSON.stringify(n),
181
+ signal: e
182
+ });
183
+ } catch (h) {
184
+ throw await this.emitTelemetry("pie-tool-backend-call-error", {
185
+ toolId: "tts",
186
+ backend: this.config.provider || "server",
187
+ operation: "synthesize-speech",
188
+ duration: Date.now() - i,
189
+ errorType: "TTSBackendNetworkError",
190
+ message: h instanceof Error ? h.message : String(h)
191
+ }), h;
192
+ }
193
+ })();
194
+ if (!c.ok) {
195
+ const h = await c.json().catch(() => ({})), p = h.message || h.error?.message || `Server returned ${c.status}`;
196
+ throw await this.emitTelemetry("pie-tool-backend-call-error", {
197
+ toolId: "tts",
198
+ backend: this.config.provider || "server",
199
+ operation: "synthesize-speech",
200
+ duration: Date.now() - i,
201
+ statusCode: c.status,
202
+ errorType: "TTSBackendRequestError",
203
+ message: p
204
+ }), new Error(p);
205
+ }
206
+ const l = await this.adapter.parseResponse(c, this.config, s, e);
207
+ if (o !== this.synthesisRunId || e.aborted)
208
+ throw new Error("Synthesis superseded by a newer request");
209
+ let u;
210
+ if (l.audio.kind === "base64")
211
+ u = this.base64ToBlob(l.audio.data, l.audio.contentType);
212
+ else {
213
+ const h = l.audio.url, p = Date.now();
214
+ await this.emitTelemetry("pie-tool-backend-call-start", {
215
+ toolId: "tts",
216
+ backend: this.config.provider || "server",
217
+ operation: "fetch-synthesized-audio-asset"
218
+ });
219
+ const T = {};
220
+ this.config.includeAuthOnAssetFetch && this.config.authToken && (T.Authorization = `Bearer ${this.config.authToken}`);
221
+ const g = await (async () => {
222
+ try {
223
+ return await fetch(h, {
224
+ headers: T,
225
+ signal: e
226
+ });
227
+ } catch (f) {
228
+ throw await this.emitTelemetry("pie-tool-backend-call-error", {
229
+ toolId: "tts",
230
+ backend: this.config.provider || "server",
231
+ operation: "fetch-synthesized-audio-asset",
232
+ duration: Date.now() - p,
233
+ errorType: "TTSAssetNetworkError",
234
+ message: f instanceof Error ? f.message : String(f)
235
+ }), f;
236
+ }
237
+ })();
238
+ if (!g.ok)
239
+ throw await this.emitTelemetry("pie-tool-backend-call-error", {
240
+ toolId: "tts",
241
+ backend: this.config.provider || "server",
242
+ operation: "fetch-synthesized-audio-asset",
243
+ duration: Date.now() - p,
244
+ statusCode: g.status,
245
+ errorType: "TTSAssetFetchError",
246
+ message: `Failed to download synthesized audio (${g.status})`
247
+ }), new Error(`Failed to download synthesized audio (${g.status})`);
248
+ u = await g.blob(), await this.emitTelemetry("pie-tool-backend-call-success", {
249
+ toolId: "tts",
250
+ backend: this.config.provider || "server",
251
+ operation: "fetch-synthesized-audio-asset",
252
+ duration: Date.now() - p
253
+ });
254
+ }
255
+ const m = URL.createObjectURL(u), y = this.parseSpeechMarks(l.speechMarks);
256
+ return await this.emitTelemetry("pie-tool-backend-call-success", {
257
+ toolId: "tts",
258
+ backend: this.config.provider || "server",
259
+ operation: "synthesize-speech",
260
+ duration: Date.now() - i
261
+ }), { audioUrl: m, wordTimings: y };
262
+ }
263
+ /**
264
+ * Convert base64 to Blob
265
+ */
266
+ base64ToBlob(t, e) {
267
+ const o = atob(t), i = new Array(o.length);
268
+ for (let d = 0; d < o.length; d++)
269
+ i[d] = o.charCodeAt(d);
270
+ const s = new Uint8Array(i);
271
+ return new Blob([s], { type: e });
272
+ }
273
+ /**
274
+ * Parse speech marks into word timings
275
+ */
276
+ parseSpeechMarks(t) {
277
+ return t.filter((e) => e.type === "word").map((e, o) => ({
278
+ time: e.time,
279
+ wordIndex: o,
280
+ charIndex: e.start,
281
+ length: e.end - e.start
282
+ }));
283
+ }
284
+ /**
285
+ * Start word highlighting synchronized with audio playback
286
+ */
287
+ startWordHighlighting() {
288
+ if (this.stopWordHighlighting(), !this.currentAudio || !this.onWordBoundary || this.wordTimings.length === 0) {
289
+ console.log("[ServerTTSProvider] Cannot start highlighting:", {
290
+ hasAudio: !!this.currentAudio,
291
+ hasCallback: !!this.onWordBoundary,
292
+ wordTimingsCount: this.wordTimings.length
293
+ });
294
+ return;
295
+ }
296
+ console.log("[ServerTTSProvider] Starting word highlighting with", this.wordTimings.length, "word timings"), console.log("[ServerTTSProvider] Playback rate:", this.currentAudio.playbackRate), console.log("[ServerTTSProvider] First 3 timings:", this.wordTimings.slice(0, 3));
297
+ let t = -1;
298
+ this.highlightInterval = window.setInterval(() => {
299
+ if (!this.currentAudio) {
300
+ this.stopWordHighlighting();
301
+ return;
302
+ }
303
+ const e = this.currentAudio.currentTime * 1e3;
304
+ for (let o = 0; o < this.wordTimings.length; o++) {
305
+ const i = this.wordTimings[o];
306
+ if (e >= i.time && o > t) {
307
+ this.onWordBoundary && (console.log("[ServerTTSProvider] Highlighting word at charIndex:", i.charIndex, "length:", i.length, "time:", i.time, "currentTime:", e), this.onWordBoundary("", i.charIndex, i.length)), t = o;
308
+ break;
309
+ }
310
+ }
311
+ }, 50);
312
+ }
313
+ /**
314
+ * Stop word highlighting
315
+ */
316
+ stopWordHighlighting() {
317
+ this.highlightInterval !== null && (clearInterval(this.highlightInterval), this.highlightInterval = null);
318
+ }
319
+ pause() {
320
+ this.currentAudio && !this.pausedState && (this.currentAudio.pause(), this.stopWordHighlighting(), this.pausedState = !0);
321
+ }
322
+ resume() {
323
+ this.currentAudio && this.pausedState && (this.currentAudio.play(), this.pausedState = !1, this.onWordBoundary && this.wordTimings.length > 0 && this.startWordHighlighting());
324
+ }
325
+ stop() {
326
+ this.synthesisRunId += 1, this.activeSynthesisController && (this.activeSynthesisController.abort(), this.activeSynthesisController = null), this.stopWordHighlighting(), this.currentAudio && (this.intentionallyStopped = !0, this.currentAudio.pause(), this.currentAudio.src && URL.revokeObjectURL(this.currentAudio.src), this.currentAudio.src = "", this.currentAudio = null), this.pausedState = !1, this.wordTimings = [];
327
+ }
328
+ isPlaying() {
329
+ return this.currentAudio !== null && !this.pausedState;
330
+ }
331
+ isPaused() {
332
+ return this.pausedState;
333
+ }
334
+ /**
335
+ * Update settings dynamically (rate, pitch, voice)
336
+ * Note: Voice changes require resynthesis, so voice updates are stored but
337
+ * take effect on the next speak() call. Rate can be applied to current playback.
338
+ */
339
+ updateSettings(t) {
340
+ t.rate !== void 0 && (this.config.rate = t.rate, this.currentAudio && (this.currentAudio.playbackRate = Math.max(0.25, Math.min(4, t.rate)))), t.pitch !== void 0 && (this.config.pitch = t.pitch), t.voice !== void 0 && (this.config.voice = t.voice);
341
+ }
342
+ }
343
+ class W {
344
+ constructor() {
345
+ a(this, "providerId", "server-tts");
346
+ a(this, "providerName", "Server TTS");
347
+ a(this, "version", "1.0.0");
348
+ a(this, "config", null);
349
+ a(this, "adapter", null);
350
+ a(this, "telemetryReporter");
351
+ }
352
+ async emitTelemetry(t, e) {
353
+ try {
354
+ await this.telemetryReporter?.(t, e);
355
+ } catch (o) {
356
+ console.warn("[ServerTTSProvider] telemetry callback failed:", o);
357
+ }
358
+ }
359
+ /**
360
+ * Initialize the server TTS provider.
361
+ *
362
+ * This is designed to be fast by default (no API calls).
363
+ * Set validateEndpoint: true in config to test API availability during initialization.
364
+ *
365
+ * @performance Default: <10ms, With validation: 100-500ms
366
+ */
367
+ async initialize(t) {
368
+ const e = t;
369
+ if (!e.apiEndpoint)
370
+ throw new Error("apiEndpoint is required for ServerTTSProvider");
371
+ this.config = e, this.telemetryReporter = b(e);
372
+ const o = w(e);
373
+ if (this.adapter = B[o], e.validateEndpoint) {
374
+ const i = Date.now();
375
+ if (await this.emitTelemetry("pie-tool-backend-call-start", {
376
+ toolId: "tts",
377
+ backend: e.provider || "server",
378
+ operation: "validate-endpoint"
379
+ }), !await this.testAPIAvailability())
380
+ throw await this.emitTelemetry("pie-tool-backend-call-error", {
381
+ toolId: "tts",
382
+ backend: e.provider || "server",
383
+ operation: "validate-endpoint",
384
+ duration: Date.now() - i,
385
+ errorType: "TTSEndpointValidationError",
386
+ message: `Server TTS API not available at ${e.apiEndpoint}`
387
+ }), new Error(`Server TTS API not available at ${e.apiEndpoint}`);
388
+ await this.emitTelemetry("pie-tool-backend-call-success", {
389
+ toolId: "tts",
390
+ backend: e.provider || "server",
391
+ operation: "validate-endpoint",
392
+ duration: Date.now() - i
393
+ });
394
+ }
395
+ return new z(e, this.adapter);
396
+ }
397
+ /**
398
+ * Test if API endpoint is available (with timeout).
399
+ *
400
+ * @performance 100-500ms depending on network
401
+ */
402
+ async testAPIAvailability() {
403
+ if (!this.config || !this.adapter)
404
+ return !1;
405
+ try {
406
+ const t = { ...this.config.headers };
407
+ this.config.authToken && (t.Authorization = `Bearer ${this.config.authToken}`);
408
+ const e = new AbortController(), o = setTimeout(() => e.abort(), 5e3), i = M(this.config, this.adapter.id);
409
+ if (i === "none")
410
+ return clearTimeout(o), !0;
411
+ const s = i === "voices" ? I(this.config) : this.adapter.resolveSynthesisUrl(this.config), d = i === "voices" ? "GET" : "OPTIONS";
412
+ try {
413
+ const n = await fetch(s, {
414
+ method: d,
415
+ headers: t,
416
+ signal: e.signal
417
+ });
418
+ return clearTimeout(o), n.ok || n.status === 405;
419
+ } catch {
420
+ return clearTimeout(o), !1;
421
+ }
422
+ } catch {
423
+ return !1;
424
+ }
425
+ }
426
+ supportsFeature(t) {
427
+ switch (t) {
428
+ case "pause":
429
+ case "resume":
430
+ case "wordBoundary":
431
+ case "voiceSelection":
432
+ case "rateControl":
433
+ return !0;
434
+ case "pitchControl":
435
+ return !1;
436
+ default:
437
+ return !1;
438
+ }
439
+ }
440
+ getCapabilities() {
441
+ const t = this.config ? w(this.config) : "pie";
442
+ return {
443
+ supportsPause: !0,
444
+ supportsResume: !0,
445
+ supportsWordBoundary: !0,
446
+ // ✅ Via speech marks from server
447
+ supportsVoiceSelection: !0,
448
+ supportsRateControl: !0,
449
+ supportsPitchControl: !1,
450
+ // Depends on server provider
451
+ maxTextLength: R[t]
452
+ };
453
+ }
454
+ destroy() {
455
+ this.config = null, this.adapter = null, this.telemetryReporter = void 0;
456
+ }
457
+ }
458
+ export {
459
+ W as ServerTTSProvider
460
+ };
@@ -1,11 +1,18 @@
1
- var n = Object.defineProperty, l = (i, e, t) => e in i ? n(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t, o = (i, e, t) => l(i, typeof e != "symbol" ? e + "" : e, t);
1
+ var l = Object.defineProperty, n = (r, e, o) => e in r ? l(r, e, { enumerable: !0, configurable: !0, writable: !0, value: o }) : r[e] = o, s = (r, e, o) => n(r, typeof e != "symbol" ? e + "" : e, o);
2
2
  class p {
3
3
  constructor() {
4
- o(this, "providerId", "desmos"), o(this, "providerName", "Desmos"), o(this, "supportedTypes", [
4
+ s(this, "providerId", "desmos"), s(this, "providerName", "Desmos"), s(this, "supportedTypes", [
5
5
  "basic",
6
6
  "scientific",
7
7
  "graphing"
8
- ]), o(this, "version", "1.10"), o(this, "initialized", !1), o(this, "apiKey"), o(this, "proxyEndpoint"), o(this, "isDevelopment", !1);
8
+ ]), s(this, "version", "1.10"), s(this, "initialized", !1), s(this, "apiKey"), s(this, "proxyEndpoint"), s(this, "isDevelopment", !1), s(this, "onTelemetry");
9
+ }
10
+ async emitTelemetry(e, o) {
11
+ try {
12
+ await this.onTelemetry?.(e, o);
13
+ } catch (t) {
14
+ console.warn("[DesmosProvider] telemetry callback failed:", t);
15
+ }
9
16
  }
10
17
  /**
11
18
  * Get the configured API key
@@ -19,13 +26,13 @@ class p {
19
26
  * @private
20
27
  */
21
28
  async loadDesmosScript() {
22
- return new Promise((e, t) => {
23
- const s = document.createElement("script"), r = this.apiKey ? `https://www.desmos.com/api/v1.10/calculator.js?apiKey=${this.apiKey}` : "https://www.desmos.com/api/v1.10/calculator.js";
24
- s.src = r, s.async = !0, s.onload = () => {
25
- window.Desmos ? (console.log("[DesmosProvider] Desmos API loaded successfully"), e()) : t(new Error("Desmos API loaded but window.Desmos is undefined"));
26
- }, s.onerror = () => {
27
- t(new Error("Failed to load Desmos API from CDN"));
28
- }, document.head.appendChild(s);
29
+ return new Promise((e, o) => {
30
+ const t = document.createElement("script"), i = this.apiKey ? `https://www.desmos.com/api/v1.10/calculator.js?apiKey=${this.apiKey}` : "https://www.desmos.com/api/v1.10/calculator.js";
31
+ t.src = i, t.async = !0, t.onload = () => {
32
+ window.Desmos ? (console.log("[DesmosProvider] Desmos API loaded successfully"), e()) : o(new Error("Desmos API loaded but window.Desmos is undefined"));
33
+ }, t.onerror = () => {
34
+ o(new Error("Failed to load Desmos API from CDN"));
35
+ }, document.head.appendChild(t);
29
36
  });
30
37
  }
31
38
  /**
@@ -34,34 +41,78 @@ class p {
34
41
  */
35
42
  async initialize(e) {
36
43
  if (!this.initialized) {
37
- if (typeof window > "u")
44
+ if (this.onTelemetry = e?.onTelemetry, typeof window > "u")
38
45
  throw new Error("Desmos calculators can only be initialized in the browser");
39
46
  if (this.isDevelopment = process.env.NODE_ENV === "development" || typeof process > "u" || !process.env.NODE_ENV, e?.proxyEndpoint) {
40
47
  this.proxyEndpoint = e.proxyEndpoint;
48
+ const o = Date.now();
49
+ await this.emitTelemetry("pie-tool-backend-call-start", {
50
+ toolId: "calculator",
51
+ backend: "desmos",
52
+ operation: "proxy-auth-fetch"
53
+ });
41
54
  try {
42
55
  const t = await fetch(e.proxyEndpoint);
43
56
  if (!t.ok)
44
57
  throw new Error(`Proxy endpoint returned ${t.status}`);
45
- const s = await t.json();
46
- this.apiKey = s.apiKey, console.log("[DesmosProvider] Initialized with server-side proxy (SECURE)");
58
+ const i = await t.json();
59
+ this.apiKey = i.apiKey, await this.emitTelemetry("pie-tool-backend-call-success", {
60
+ toolId: "calculator",
61
+ backend: "desmos",
62
+ operation: "proxy-auth-fetch",
63
+ duration: Date.now() - o
64
+ }), console.log("[DesmosProvider] Initialized with server-side proxy (SECURE)");
47
65
  } catch (t) {
48
- throw new Error(`[DesmosProvider] Failed to fetch API key from proxy: ${t}`);
66
+ throw await this.emitTelemetry("pie-tool-backend-call-error", {
67
+ toolId: "calculator",
68
+ backend: "desmos",
69
+ operation: "proxy-auth-fetch",
70
+ duration: Date.now() - o,
71
+ errorType: "CalculatorProxyAuthError",
72
+ message: t instanceof Error ? t.message : String(t)
73
+ }), new Error(`[DesmosProvider] Failed to fetch API key from proxy: ${t}`);
49
74
  }
50
75
  } else e?.apiKey ? (this.apiKey = e.apiKey, this.isDevelopment ? console.log("[DesmosProvider] Initialized with direct API key (DEVELOPMENT MODE)") : console.error(`⚠️ [DesmosProvider] SECURITY WARNING: API key exposed in client-side code!
51
76
  This is insecure for production. Use proxyEndpoint instead.
52
77
  See: https://pie-players.dev/docs/calculator-desmos#security`)) : console.warn(`[DesmosProvider] No API key or proxy endpoint provided.
53
78
  Production usage requires authentication. Obtain API key from https://www.desmos.com/api
54
79
  Recommended: Use proxyEndpoint for production, apiKey for development only.`);
55
- window.Desmos || (console.log("[DesmosProvider] Loading Desmos API library..."), await this.loadDesmosScript()), this.initialized = !0;
80
+ if (!window.Desmos) {
81
+ console.log("[DesmosProvider] Loading Desmos API library...");
82
+ const o = Date.now();
83
+ await this.emitTelemetry("pie-tool-library-load-start", {
84
+ toolId: "calculator",
85
+ backend: "desmos",
86
+ operation: "desmos-script-load"
87
+ });
88
+ try {
89
+ await this.loadDesmosScript(), await this.emitTelemetry("pie-tool-library-load-success", {
90
+ toolId: "calculator",
91
+ backend: "desmos",
92
+ operation: "desmos-script-load",
93
+ duration: Date.now() - o
94
+ });
95
+ } catch (t) {
96
+ throw await this.emitTelemetry("pie-tool-library-load-error", {
97
+ toolId: "calculator",
98
+ backend: "desmos",
99
+ operation: "desmos-script-load",
100
+ duration: Date.now() - o,
101
+ errorType: "ToolLibraryLoadError",
102
+ message: t instanceof Error ? t.message : String(t)
103
+ }), t;
104
+ }
105
+ }
106
+ this.initialized = !0;
56
107
  }
57
108
  }
58
109
  /**
59
110
  * Create a calculator instance
60
111
  */
61
- async createCalculator(e, t, s) {
112
+ async createCalculator(e, o, t) {
62
113
  if (this.initialized || await this.initialize(), !this.supportsType(e))
63
114
  throw new Error(`Desmos does not support calculator type: ${e}`);
64
- return new c(this, e, t, s, this.apiKey);
115
+ return new c(this, e, o, t, this.apiKey);
65
116
  }
66
117
  /**
67
118
  * Check if type is supported
@@ -73,7 +124,7 @@ Recommended: Use proxyEndpoint for production, apiKey for development only.`);
73
124
  * Cleanup
74
125
  */
75
126
  destroy() {
76
- this.initialized = !1;
127
+ this.initialized = !1, this.onTelemetry = void 0;
77
128
  }
78
129
  /**
79
130
  * Get provider capabilities
@@ -91,17 +142,17 @@ Recommended: Use proxyEndpoint for production, apiKey for development only.`);
91
142
  }
92
143
  }
93
144
  class c {
94
- constructor(e, t, s, r, a) {
95
- if (o(this, "provider"), o(this, "type"), o(this, "Desmos"), o(this, "calculator"), o(this, "container"), this.provider = e, this.type = t, this.container = s, this.Desmos = window.Desmos, !this.Desmos)
145
+ constructor(e, o, t, i, a) {
146
+ if (s(this, "provider"), s(this, "type"), s(this, "Desmos"), s(this, "calculator"), s(this, "container"), this.provider = e, this.type = o, this.container = t, this.Desmos = window.Desmos, !this.Desmos)
96
147
  throw new Error("Desmos API not available");
97
- this._initializeCalculator(r, a);
148
+ this._initializeCalculator(i, a);
98
149
  }
99
- _initializeCalculator(e, t) {
100
- const s = {
150
+ _initializeCalculator(e, o) {
151
+ const t = {
101
152
  ...e?.desmos || {},
102
- apiKey: t || e?.desmos?.apiKey
153
+ apiKey: o || e?.desmos?.apiKey
103
154
  };
104
- switch (e?.restrictedMode && Object.assign(s, {
155
+ switch (e?.restrictedMode && Object.assign(t, {
105
156
  expressionsTopbar: !1,
106
157
  settingsMenu: !1,
107
158
  zoomButtons: !1,
@@ -109,13 +160,13 @@ class c {
109
160
  links: !1
110
161
  }), this.type) {
111
162
  case "graphing":
112
- this.calculator = this.Desmos.GraphingCalculator(this.container, s);
163
+ this.calculator = this.Desmos.GraphingCalculator(this.container, t);
113
164
  break;
114
165
  case "scientific":
115
- this.calculator = this.Desmos.ScientificCalculator(this.container, s);
166
+ this.calculator = this.Desmos.ScientificCalculator(this.container, t);
116
167
  break;
117
168
  case "basic":
118
- this.calculator = this.Desmos.FourFunctionCalculator(this.container, s);
169
+ this.calculator = this.Desmos.FourFunctionCalculator(this.container, t);
119
170
  break;
120
171
  default:
121
172
  throw new Error(`Unsupported calculator type: ${this.type}`);
@@ -132,23 +183,23 @@ class c {
132
183
  setValue(e) {
133
184
  if (this.type === "graphing" && this.calculator.setState)
134
185
  try {
135
- const t = JSON.parse(e);
136
- this.calculator.setState(t);
137
- } catch (t) {
138
- console.error("[DesmosCalculator] Failed to set state:", t);
186
+ const o = JSON.parse(e);
187
+ this.calculator.setState(o);
188
+ } catch (o) {
189
+ console.error("[DesmosCalculator] Failed to set state:", o);
139
190
  }
140
191
  }
141
192
  clear() {
142
193
  this.calculator.setBlank && this.calculator.setBlank();
143
194
  }
144
195
  async evaluate(e) {
145
- return this.type === "graphing" ? new Promise((t) => {
146
- const s = `eval_${Date.now()}`;
147
- this.calculator.setExpression({ id: s, latex: e }), setTimeout(() => {
148
- const r = this.calculator.HelperExpression({
196
+ return this.type === "graphing" ? new Promise((o) => {
197
+ const t = `eval_${Date.now()}`;
198
+ this.calculator.setExpression({ id: t, latex: e }), setTimeout(() => {
199
+ const i = this.calculator.HelperExpression({
149
200
  latex: e
150
201
  }).numericValue || e;
151
- this.calculator.removeExpression({ id: s }), t(String(r));
202
+ this.calculator.removeExpression({ id: t }), o(String(i));
152
203
  }, 100);
153
204
  }) : e;
154
205
  }