glitchgrab 1.48.0 → 1.49.0

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.
package/dist/server.d.mts CHANGED
@@ -127,5 +127,231 @@ interface SignSessionOptions {
127
127
  * loses the AI features.
128
128
  */
129
129
  declare function signSession(options: SignSessionOptions): string;
130
+ /** One guide as the list returns it — no body. Dates are ISO strings: this came over JSON. */
131
+ interface GuideSummary {
132
+ slug: string;
133
+ title: string;
134
+ /** One line saying what the guide answers. */
135
+ summary: string;
136
+ category: string | null;
137
+ /** The order set in Glitchgrab. The list already arrives sorted by it. */
138
+ orderIndex: number;
139
+ /** Always `"PUBLISHED"` with a `gg_` token; only a `ggw_` write key sees drafts. */
140
+ status: "DRAFT" | "PUBLISHED";
141
+ /** When someone last re-checked the steps against the product. */
142
+ lastVerifiedAt: string | null;
143
+ updatedAt: string;
144
+ /**
145
+ * Where this guide lives on your site: its own link, else
146
+ * `<guides base URL>/<slug>`, else null when the repo sets neither.
147
+ */
148
+ link: string | null;
149
+ }
150
+ /** One guide with its body. */
151
+ interface Guide extends GuideSummary {
152
+ /** The markdown as written. */
153
+ content: string;
154
+ /**
155
+ * `content` rendered by Glitchgrab: raw HTML in the markdown arrives as text,
156
+ * only allowlisted attributes survive, and links keep only http(s), mailto
157
+ * and relative targets. Headings carry `id="guide-<heading text>"`, for a
158
+ * table of contents or a link to a single step.
159
+ *
160
+ * Safe to render as-is. On a domain that holds your users' sessions, a second
161
+ * sanitize pass of your own is cheap defence in depth — this SDK ships no
162
+ * sanitizer so it stays dependency-free.
163
+ */
164
+ html: string;
165
+ }
166
+ /** Options for {@link listGuides} and {@link getGuide}. */
167
+ interface GuideReadOptions {
168
+ /**
169
+ * Repo token (`gg_…`). Falls back to {@link configureServerReporter}'s token,
170
+ * then `GLITCHGRAB_TOKEN`, then `NEXT_PUBLIC_GLITCHGRAB_TOKEN` — the token is
171
+ * public, so the one your provider already uses is fine.
172
+ *
173
+ * Never pass the `ggw_` guides write key for a public page: it reads drafts too.
174
+ */
175
+ token?: string;
176
+ /** Override the API host. Falls back to `GLITCHGRAB_BASE_URL`, then `https://glitchgrab.dev`. */
177
+ baseUrl?: string;
178
+ /**
179
+ * Seconds Next.js may cache the response. Default 300; `0` reads fresh on
180
+ * every request. The route is `force-dynamic`, so without this nothing is
181
+ * cached at all. Ignored outside Next.js.
182
+ */
183
+ revalidate?: number;
184
+ /** Give up and return empty after this many ms. Default 5000 — a slow Glitchgrab must not hang your render. */
185
+ timeoutMs?: number;
186
+ }
187
+ /**
188
+ * Every published guide for the token's repo, in the order set in Glitchgrab.
189
+ * No bodies — this is the index page.
190
+ *
191
+ * ```ts
192
+ * // app/guides/page.tsx
193
+ * import { listGuides } from "glitchgrab/server";
194
+ *
195
+ * export default async function GuidesPage() {
196
+ * const guides = await listGuides();
197
+ * return guides.map((g) => <a key={g.slug} href={`/guides/${g.slug}`}>{g.title}</a>);
198
+ * }
199
+ * ```
200
+ *
201
+ * Returns `[]` on any failure. Never throws: a Glitchgrab outage leaves the
202
+ * page empty instead of turning it into a 500.
203
+ */
204
+ declare function listGuides(options?: GuideReadOptions): Promise<GuideSummary[]>;
205
+ /**
206
+ * One published guide with its markdown and rendered HTML.
207
+ *
208
+ * Returns `null` when there is no such guide and on any failure — call
209
+ * `notFound()` on it. Never throws.
210
+ */
211
+ declare function getGuide(slug: string, options?: GuideReadOptions): Promise<Guide | null>;
212
+ /** Someone on the call, as the recorder saw them. */
213
+ interface CallParticipant {
214
+ name: string | null;
215
+ email: string | null;
216
+ }
217
+ /** One shared call as the list returns it — no transcript, no links. */
218
+ interface CallListItem {
219
+ id: string;
220
+ title: string | null;
221
+ startsAt: string | null;
222
+ endsAt: string | null;
223
+ durationSec: number | null;
224
+ /** `"bot"` (joined the Meet) or `"extension"` (recorded from someone's Chrome). */
225
+ recorder: string | null;
226
+ /** Match a call to your customer by these emails. */
227
+ participants: CallParticipant[];
228
+ hasRecording: boolean;
229
+ /** `IDLE`, `RUNNING`, `DONE` or `FAILED`. */
230
+ transcriptStatus: string;
231
+ /** One line on what the call was about, once Glitchgrab has read it. */
232
+ overview: string | null;
233
+ createdAt: string;
234
+ }
235
+ interface CallAudio {
236
+ /**
237
+ * `call` — a bot recording's single mixed track. An extension recording has
238
+ * two instead: `client` (everyone else on the call) and `recorder` (the
239
+ * person who recorded it).
240
+ */
241
+ track: "call" | "client" | "recorder";
242
+ /** Plays for {@link Call.linksExpireInSeconds}. */
243
+ url: string;
244
+ }
245
+ interface CallMoment {
246
+ /** Milliseconds into the call — seek the audio here. */
247
+ tMs: number | null;
248
+ speaker: string | null;
249
+ text: string;
250
+ }
251
+ /** One shared call in full. */
252
+ interface Call extends CallListItem {
253
+ meetUrl: string | null;
254
+ audio: CallAudio[];
255
+ /** Audio and screenshot links stop working after this many seconds. Read the call per render; never store them. */
256
+ linksExpireInSeconds: number;
257
+ transcript: string | null;
258
+ summary: {
259
+ overview: string;
260
+ moments: CallMoment[];
261
+ } | null;
262
+ /** Screenshots grabbed during the call, a page at a time. `tMs` is on the transcript's clock. */
263
+ frames: {
264
+ total: number;
265
+ offset: number;
266
+ limit: number;
267
+ items: {
268
+ tMs: number;
269
+ url: string;
270
+ }[];
271
+ };
272
+ }
273
+ /** Options for {@link listCalls}, {@link getCall} and {@link deleteCall}. */
274
+ interface CallsOptions {
275
+ /**
276
+ * The project's calls key (`ggc_…`). Falls back to `GLITCHGRAB_CALLS_KEY`.
277
+ * Server only — never a `NEXT_PUBLIC_` variable: it plays and deletes calls.
278
+ */
279
+ key?: string;
280
+ /** Override the API host. Falls back to `GLITCHGRAB_BASE_URL`, then `https://glitchgrab.dev`. */
281
+ baseUrl?: string;
282
+ /** Give up after this many ms. Default 5000. */
283
+ timeoutMs?: number;
284
+ }
285
+ interface ListCallsOptions extends CallsOptions {
286
+ /** Default 20, max 100. */
287
+ limit?: number;
288
+ /** ISO date: only calls recorded before it. Pass the last call's `createdAt` for the next page. */
289
+ before?: string;
290
+ }
291
+ interface GetCallOptions extends CallsOptions {
292
+ /** First screenshot to return. Default 0. */
293
+ framesOffset?: number;
294
+ /** Screenshots per page. Default 50, max 200, `0` for none. */
295
+ framesLimit?: number;
296
+ }
297
+ type DeleteCallResult = {
298
+ deleted: true;
299
+ } | {
300
+ deleted: false;
301
+ error: string;
302
+ };
303
+ /**
304
+ * Calls shared with the client on the key's project, newest first. No links —
305
+ * open one with {@link getCall}.
306
+ *
307
+ * ```ts
308
+ * // app/admin/calls/page.tsx — server component
309
+ * import { listCalls } from "glitchgrab/server";
310
+ *
311
+ * export default async function CallsPage() {
312
+ * const calls = await listCalls(); // GLITCHGRAB_CALLS_KEY=ggc_…
313
+ * return calls.map((c) => <a key={c.id} href={`/admin/calls/${c.id}`}>{c.title}</a>);
314
+ * }
315
+ * ```
316
+ *
317
+ * Returns `[]` on any failure. Never throws.
318
+ */
319
+ declare function listCalls(options?: ListCallsOptions): Promise<CallListItem[]>;
320
+ /**
321
+ * One shared call: audio links, transcript, summary and a page of screenshots.
322
+ * Links expire after `linksExpireInSeconds` — read the call when the page
323
+ * renders and never store them.
324
+ *
325
+ * Returns `null` when the call does not exist, is not shared, and on any
326
+ * failure — call `notFound()` on it. Never throws.
327
+ */
328
+ declare function getCall(id: string, options?: GetCallOptions): Promise<Call | null>;
329
+ /**
330
+ * Permanently delete one shared call — the recording, the screenshots, the
331
+ * transcript. Cannot be undone.
332
+ *
333
+ * Resolves `{ deleted: false, error }` instead of throwing: the reason is worth
334
+ * showing ("The bot is still in this call — delete it after the call ends").
335
+ */
336
+ declare function deleteCall(id: string, options?: CallsOptions): Promise<DeleteCallResult>;
337
+ /**
338
+ * `JSON.stringify` that is safe inside `<script type="application/ld+json">`.
339
+ *
340
+ * Guide titles and summaries are written outside your codebase, and
341
+ * `JSON.stringify` leaves `<` alone — a title containing `</script>` closes the
342
+ * tag and whatever follows runs as HTML. The JSON unicode escape for `<` is the same
343
+ * character to a JSON parser and inert to the HTML one. `>`, `&` and the two
344
+ * JavaScript line separators are escaped for the same reason.
345
+ *
346
+ * ```tsx
347
+ * <script
348
+ * type="application/ld+json"
349
+ * dangerouslySetInnerHTML={{ __html: escapeJsonForScript({ "@type": "HowTo", name: guide.title }) }}
350
+ * />
351
+ * ```
352
+ *
353
+ * Never throws: a value JSON cannot represent (circular, `undefined`) becomes `null`.
354
+ */
355
+ declare function escapeJsonForScript(value: unknown): string;
130
356
 
131
- export { type CaptureServerErrorsOptions, ReportResult, ReportSeverity, ReportType, type ServerReportOptions, type ServerReporterConfig, type ServerReporterIdentity, type SignSessionOptions, captureServerErrors, configureServerReporter, reportServerError, resetServerReporter, signSession };
357
+ export { type Call, type CallAudio, type CallListItem, type CallMoment, type CallParticipant, type CallsOptions, type CaptureServerErrorsOptions, type DeleteCallResult, type GetCallOptions, type Guide, type GuideReadOptions, type GuideSummary, type ListCallsOptions, ReportResult, ReportSeverity, ReportType, type ServerReportOptions, type ServerReporterConfig, type ServerReporterIdentity, type SignSessionOptions, captureServerErrors, configureServerReporter, deleteCall, escapeJsonForScript, getCall, getGuide, listCalls, listGuides, reportServerError, resetServerReporter, signSession };
package/dist/server.d.ts CHANGED
@@ -127,5 +127,231 @@ interface SignSessionOptions {
127
127
  * loses the AI features.
128
128
  */
129
129
  declare function signSession(options: SignSessionOptions): string;
130
+ /** One guide as the list returns it — no body. Dates are ISO strings: this came over JSON. */
131
+ interface GuideSummary {
132
+ slug: string;
133
+ title: string;
134
+ /** One line saying what the guide answers. */
135
+ summary: string;
136
+ category: string | null;
137
+ /** The order set in Glitchgrab. The list already arrives sorted by it. */
138
+ orderIndex: number;
139
+ /** Always `"PUBLISHED"` with a `gg_` token; only a `ggw_` write key sees drafts. */
140
+ status: "DRAFT" | "PUBLISHED";
141
+ /** When someone last re-checked the steps against the product. */
142
+ lastVerifiedAt: string | null;
143
+ updatedAt: string;
144
+ /**
145
+ * Where this guide lives on your site: its own link, else
146
+ * `<guides base URL>/<slug>`, else null when the repo sets neither.
147
+ */
148
+ link: string | null;
149
+ }
150
+ /** One guide with its body. */
151
+ interface Guide extends GuideSummary {
152
+ /** The markdown as written. */
153
+ content: string;
154
+ /**
155
+ * `content` rendered by Glitchgrab: raw HTML in the markdown arrives as text,
156
+ * only allowlisted attributes survive, and links keep only http(s), mailto
157
+ * and relative targets. Headings carry `id="guide-<heading text>"`, for a
158
+ * table of contents or a link to a single step.
159
+ *
160
+ * Safe to render as-is. On a domain that holds your users' sessions, a second
161
+ * sanitize pass of your own is cheap defence in depth — this SDK ships no
162
+ * sanitizer so it stays dependency-free.
163
+ */
164
+ html: string;
165
+ }
166
+ /** Options for {@link listGuides} and {@link getGuide}. */
167
+ interface GuideReadOptions {
168
+ /**
169
+ * Repo token (`gg_…`). Falls back to {@link configureServerReporter}'s token,
170
+ * then `GLITCHGRAB_TOKEN`, then `NEXT_PUBLIC_GLITCHGRAB_TOKEN` — the token is
171
+ * public, so the one your provider already uses is fine.
172
+ *
173
+ * Never pass the `ggw_` guides write key for a public page: it reads drafts too.
174
+ */
175
+ token?: string;
176
+ /** Override the API host. Falls back to `GLITCHGRAB_BASE_URL`, then `https://glitchgrab.dev`. */
177
+ baseUrl?: string;
178
+ /**
179
+ * Seconds Next.js may cache the response. Default 300; `0` reads fresh on
180
+ * every request. The route is `force-dynamic`, so without this nothing is
181
+ * cached at all. Ignored outside Next.js.
182
+ */
183
+ revalidate?: number;
184
+ /** Give up and return empty after this many ms. Default 5000 — a slow Glitchgrab must not hang your render. */
185
+ timeoutMs?: number;
186
+ }
187
+ /**
188
+ * Every published guide for the token's repo, in the order set in Glitchgrab.
189
+ * No bodies — this is the index page.
190
+ *
191
+ * ```ts
192
+ * // app/guides/page.tsx
193
+ * import { listGuides } from "glitchgrab/server";
194
+ *
195
+ * export default async function GuidesPage() {
196
+ * const guides = await listGuides();
197
+ * return guides.map((g) => <a key={g.slug} href={`/guides/${g.slug}`}>{g.title}</a>);
198
+ * }
199
+ * ```
200
+ *
201
+ * Returns `[]` on any failure. Never throws: a Glitchgrab outage leaves the
202
+ * page empty instead of turning it into a 500.
203
+ */
204
+ declare function listGuides(options?: GuideReadOptions): Promise<GuideSummary[]>;
205
+ /**
206
+ * One published guide with its markdown and rendered HTML.
207
+ *
208
+ * Returns `null` when there is no such guide and on any failure — call
209
+ * `notFound()` on it. Never throws.
210
+ */
211
+ declare function getGuide(slug: string, options?: GuideReadOptions): Promise<Guide | null>;
212
+ /** Someone on the call, as the recorder saw them. */
213
+ interface CallParticipant {
214
+ name: string | null;
215
+ email: string | null;
216
+ }
217
+ /** One shared call as the list returns it — no transcript, no links. */
218
+ interface CallListItem {
219
+ id: string;
220
+ title: string | null;
221
+ startsAt: string | null;
222
+ endsAt: string | null;
223
+ durationSec: number | null;
224
+ /** `"bot"` (joined the Meet) or `"extension"` (recorded from someone's Chrome). */
225
+ recorder: string | null;
226
+ /** Match a call to your customer by these emails. */
227
+ participants: CallParticipant[];
228
+ hasRecording: boolean;
229
+ /** `IDLE`, `RUNNING`, `DONE` or `FAILED`. */
230
+ transcriptStatus: string;
231
+ /** One line on what the call was about, once Glitchgrab has read it. */
232
+ overview: string | null;
233
+ createdAt: string;
234
+ }
235
+ interface CallAudio {
236
+ /**
237
+ * `call` — a bot recording's single mixed track. An extension recording has
238
+ * two instead: `client` (everyone else on the call) and `recorder` (the
239
+ * person who recorded it).
240
+ */
241
+ track: "call" | "client" | "recorder";
242
+ /** Plays for {@link Call.linksExpireInSeconds}. */
243
+ url: string;
244
+ }
245
+ interface CallMoment {
246
+ /** Milliseconds into the call — seek the audio here. */
247
+ tMs: number | null;
248
+ speaker: string | null;
249
+ text: string;
250
+ }
251
+ /** One shared call in full. */
252
+ interface Call extends CallListItem {
253
+ meetUrl: string | null;
254
+ audio: CallAudio[];
255
+ /** Audio and screenshot links stop working after this many seconds. Read the call per render; never store them. */
256
+ linksExpireInSeconds: number;
257
+ transcript: string | null;
258
+ summary: {
259
+ overview: string;
260
+ moments: CallMoment[];
261
+ } | null;
262
+ /** Screenshots grabbed during the call, a page at a time. `tMs` is on the transcript's clock. */
263
+ frames: {
264
+ total: number;
265
+ offset: number;
266
+ limit: number;
267
+ items: {
268
+ tMs: number;
269
+ url: string;
270
+ }[];
271
+ };
272
+ }
273
+ /** Options for {@link listCalls}, {@link getCall} and {@link deleteCall}. */
274
+ interface CallsOptions {
275
+ /**
276
+ * The project's calls key (`ggc_…`). Falls back to `GLITCHGRAB_CALLS_KEY`.
277
+ * Server only — never a `NEXT_PUBLIC_` variable: it plays and deletes calls.
278
+ */
279
+ key?: string;
280
+ /** Override the API host. Falls back to `GLITCHGRAB_BASE_URL`, then `https://glitchgrab.dev`. */
281
+ baseUrl?: string;
282
+ /** Give up after this many ms. Default 5000. */
283
+ timeoutMs?: number;
284
+ }
285
+ interface ListCallsOptions extends CallsOptions {
286
+ /** Default 20, max 100. */
287
+ limit?: number;
288
+ /** ISO date: only calls recorded before it. Pass the last call's `createdAt` for the next page. */
289
+ before?: string;
290
+ }
291
+ interface GetCallOptions extends CallsOptions {
292
+ /** First screenshot to return. Default 0. */
293
+ framesOffset?: number;
294
+ /** Screenshots per page. Default 50, max 200, `0` for none. */
295
+ framesLimit?: number;
296
+ }
297
+ type DeleteCallResult = {
298
+ deleted: true;
299
+ } | {
300
+ deleted: false;
301
+ error: string;
302
+ };
303
+ /**
304
+ * Calls shared with the client on the key's project, newest first. No links —
305
+ * open one with {@link getCall}.
306
+ *
307
+ * ```ts
308
+ * // app/admin/calls/page.tsx — server component
309
+ * import { listCalls } from "glitchgrab/server";
310
+ *
311
+ * export default async function CallsPage() {
312
+ * const calls = await listCalls(); // GLITCHGRAB_CALLS_KEY=ggc_…
313
+ * return calls.map((c) => <a key={c.id} href={`/admin/calls/${c.id}`}>{c.title}</a>);
314
+ * }
315
+ * ```
316
+ *
317
+ * Returns `[]` on any failure. Never throws.
318
+ */
319
+ declare function listCalls(options?: ListCallsOptions): Promise<CallListItem[]>;
320
+ /**
321
+ * One shared call: audio links, transcript, summary and a page of screenshots.
322
+ * Links expire after `linksExpireInSeconds` — read the call when the page
323
+ * renders and never store them.
324
+ *
325
+ * Returns `null` when the call does not exist, is not shared, and on any
326
+ * failure — call `notFound()` on it. Never throws.
327
+ */
328
+ declare function getCall(id: string, options?: GetCallOptions): Promise<Call | null>;
329
+ /**
330
+ * Permanently delete one shared call — the recording, the screenshots, the
331
+ * transcript. Cannot be undone.
332
+ *
333
+ * Resolves `{ deleted: false, error }` instead of throwing: the reason is worth
334
+ * showing ("The bot is still in this call — delete it after the call ends").
335
+ */
336
+ declare function deleteCall(id: string, options?: CallsOptions): Promise<DeleteCallResult>;
337
+ /**
338
+ * `JSON.stringify` that is safe inside `<script type="application/ld+json">`.
339
+ *
340
+ * Guide titles and summaries are written outside your codebase, and
341
+ * `JSON.stringify` leaves `<` alone — a title containing `</script>` closes the
342
+ * tag and whatever follows runs as HTML. The JSON unicode escape for `<` is the same
343
+ * character to a JSON parser and inert to the HTML one. `>`, `&` and the two
344
+ * JavaScript line separators are escaped for the same reason.
345
+ *
346
+ * ```tsx
347
+ * <script
348
+ * type="application/ld+json"
349
+ * dangerouslySetInnerHTML={{ __html: escapeJsonForScript({ "@type": "HowTo", name: guide.title }) }}
350
+ * />
351
+ * ```
352
+ *
353
+ * Never throws: a value JSON cannot represent (circular, `undefined`) becomes `null`.
354
+ */
355
+ declare function escapeJsonForScript(value: unknown): string;
130
356
 
131
- export { type CaptureServerErrorsOptions, ReportResult, ReportSeverity, ReportType, type ServerReportOptions, type ServerReporterConfig, type ServerReporterIdentity, type SignSessionOptions, captureServerErrors, configureServerReporter, reportServerError, resetServerReporter, signSession };
357
+ export { type Call, type CallAudio, type CallListItem, type CallMoment, type CallParticipant, type CallsOptions, type CaptureServerErrorsOptions, type DeleteCallResult, type GetCallOptions, type Guide, type GuideReadOptions, type GuideSummary, type ListCallsOptions, ReportResult, ReportSeverity, ReportType, type ServerReportOptions, type ServerReporterConfig, type ServerReporterIdentity, type SignSessionOptions, captureServerErrors, configureServerReporter, deleteCall, escapeJsonForScript, getCall, getGuide, listCalls, listGuides, reportServerError, resetServerReporter, signSession };
package/dist/server.js CHANGED
@@ -22,6 +22,12 @@ var server_exports = {};
22
22
  __export(server_exports, {
23
23
  captureServerErrors: () => captureServerErrors,
24
24
  configureServerReporter: () => configureServerReporter,
25
+ deleteCall: () => deleteCall,
26
+ escapeJsonForScript: () => escapeJsonForScript,
27
+ getCall: () => getCall,
28
+ getGuide: () => getGuide,
29
+ listCalls: () => listCalls,
30
+ listGuides: () => listGuides,
25
31
  reportServerError: () => reportServerError,
26
32
  resetServerReporter: () => resetServerReporter,
27
33
  signSession: () => signSession
@@ -247,10 +253,207 @@ function signSession(options) {
247
253
  const sig = (0, import_node_crypto.createHmac)("sha256", secret).update(`v1.${exp}.${userId}`).digest("base64url");
248
254
  return `v1.${exp}.${sig}`;
249
255
  }
256
+ var GUIDES_REVALIDATE_SECONDS = 300;
257
+ var SDK_API_TIMEOUT_MS = 5e3;
258
+ async function callSdkApi(path, token, options, init) {
259
+ var _a, _b, _c, _d, _e, _f;
260
+ const baseUrl = ((_c = (_b = (_a = options.baseUrl) != null ? _a : defaults.baseUrl) != null ? _b : readEnv("GLITCHGRAB_BASE_URL")) != null ? _c : DEFAULT_BASE_URL).replace(/\/+$/, "");
261
+ const controller = typeof AbortController === "function" ? new AbortController() : void 0;
262
+ const timer = controller ? setTimeout(() => controller.abort(), (_d = options.timeoutMs) != null ? _d : SDK_API_TIMEOUT_MS) : void 0;
263
+ try {
264
+ const request = {
265
+ method: (_e = init.method) != null ? _e : "GET",
266
+ headers: { Authorization: `Bearer ${token}` },
267
+ ...init.cache === "no-store" ? { cache: "no-store" } : { next: { revalidate: init.cache.revalidate } },
268
+ ...controller ? { signal: controller.signal } : {}
269
+ };
270
+ const response = await fetch(`${baseUrl}${path}`, request);
271
+ const body = await response.json().catch(() => null);
272
+ if (!response.ok || (body == null ? void 0 : body.success) !== true) {
273
+ return { data: null, error: typeof (body == null ? void 0 : body.error) === "string" ? body.error : `HTTP ${response.status}` };
274
+ }
275
+ return { data: (_f = body.data) != null ? _f : null, error: null };
276
+ } catch (e) {
277
+ return { data: null, error: "Could not reach Glitchgrab" };
278
+ } finally {
279
+ if (timer) clearTimeout(timer);
280
+ }
281
+ }
282
+ async function readGuidesApi(query, options) {
283
+ var _a, _b, _c;
284
+ const token = (_c = (_b = (_a = options.token) != null ? _a : defaults.token) != null ? _b : readEnv("GLITCHGRAB_TOKEN")) != null ? _c : readEnv("NEXT_PUBLIC_GLITCHGRAB_TOKEN");
285
+ if (!token) return null;
286
+ const revalidate = typeof options.revalidate === "number" && Number.isFinite(options.revalidate) && options.revalidate >= 0 ? options.revalidate : GUIDES_REVALIDATE_SECONDS;
287
+ const result = await callSdkApi(`/api/v1/sdk/guides${query}`, token, options, { cache: { revalidate } });
288
+ return result.data;
289
+ }
290
+ function readGuideSummary(value) {
291
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
292
+ const guide = value;
293
+ if (typeof guide.slug !== "string" || typeof guide.title !== "string" || typeof guide.summary !== "string") {
294
+ return null;
295
+ }
296
+ return {
297
+ slug: guide.slug,
298
+ title: guide.title,
299
+ summary: guide.summary,
300
+ category: typeof guide.category === "string" ? guide.category : null,
301
+ orderIndex: typeof guide.orderIndex === "number" ? guide.orderIndex : 0,
302
+ status: guide.status === "DRAFT" ? "DRAFT" : "PUBLISHED",
303
+ lastVerifiedAt: typeof guide.lastVerifiedAt === "string" ? guide.lastVerifiedAt : null,
304
+ updatedAt: typeof guide.updatedAt === "string" ? guide.updatedAt : "",
305
+ link: typeof guide.link === "string" ? guide.link : null
306
+ };
307
+ }
308
+ async function listGuides(options = {}) {
309
+ try {
310
+ const data = await readGuidesApi("", options);
311
+ if (!Array.isArray(data)) return [];
312
+ return data.map(readGuideSummary).filter((guide) => guide !== null);
313
+ } catch (e) {
314
+ return [];
315
+ }
316
+ }
317
+ async function getGuide(slug, options = {}) {
318
+ try {
319
+ const wanted = typeof slug === "string" ? slug.trim() : "";
320
+ if (!wanted) return null;
321
+ const data = await readGuidesApi(`?slug=${encodeURIComponent(wanted)}`, options);
322
+ const summary = readGuideSummary(data);
323
+ const body = data;
324
+ if (!summary || typeof (body == null ? void 0 : body.html) !== "string") return null;
325
+ return {
326
+ ...summary,
327
+ content: typeof body.content === "string" ? body.content : "",
328
+ html: body.html
329
+ };
330
+ } catch (e) {
331
+ return null;
332
+ }
333
+ }
334
+ var CALLS_KEY_PREFIX = "ggc_";
335
+ function callsKey(options) {
336
+ var _a;
337
+ const key = (_a = options.key) != null ? _a : readEnv("GLITCHGRAB_CALLS_KEY");
338
+ return typeof key === "string" && key.startsWith(CALLS_KEY_PREFIX) ? key : null;
339
+ }
340
+ function isRecord(value) {
341
+ return !!value && typeof value === "object" && !Array.isArray(value);
342
+ }
343
+ function stringOrNull(value) {
344
+ return typeof value === "string" ? value : null;
345
+ }
346
+ function numberOrNull(value) {
347
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
348
+ }
349
+ function readCallListItem(value) {
350
+ if (!isRecord(value) || typeof value.id !== "string") return null;
351
+ return {
352
+ id: value.id,
353
+ title: stringOrNull(value.title),
354
+ startsAt: stringOrNull(value.startsAt),
355
+ endsAt: stringOrNull(value.endsAt),
356
+ durationSec: numberOrNull(value.durationSec),
357
+ recorder: stringOrNull(value.recorder),
358
+ participants: Array.isArray(value.participants) ? value.participants.filter(isRecord).map((p) => ({ name: stringOrNull(p.name), email: stringOrNull(p.email) })) : [],
359
+ hasRecording: value.hasRecording === true,
360
+ transcriptStatus: typeof value.transcriptStatus === "string" ? value.transcriptStatus : "IDLE",
361
+ overview: stringOrNull(value.overview),
362
+ createdAt: typeof value.createdAt === "string" ? value.createdAt : ""
363
+ };
364
+ }
365
+ var AUDIO_TRACKS = /* @__PURE__ */ new Set(["call", "client", "recorder"]);
366
+ function readCall(value) {
367
+ var _a, _b, _c, _d;
368
+ const base = readCallListItem(value);
369
+ if (!base || !isRecord(value)) return null;
370
+ const rawSummary = value.summary;
371
+ const summary = isRecord(rawSummary) && typeof rawSummary.overview === "string" ? {
372
+ overview: rawSummary.overview,
373
+ moments: Array.isArray(rawSummary.moments) ? rawSummary.moments.filter(isRecord).filter((m) => typeof m.text === "string").map((m) => ({ tMs: numberOrNull(m.tMs), speaker: stringOrNull(m.speaker), text: m.text })) : []
374
+ } : null;
375
+ const frames = isRecord(value.frames) ? value.frames : {};
376
+ return {
377
+ ...base,
378
+ meetUrl: stringOrNull(value.meetUrl),
379
+ audio: Array.isArray(value.audio) ? value.audio.filter(isRecord).filter((a) => typeof a.url === "string" && typeof a.track === "string" && AUDIO_TRACKS.has(a.track)).map((a) => ({ track: a.track, url: a.url })) : [],
380
+ linksExpireInSeconds: (_a = numberOrNull(value.linksExpireInSeconds)) != null ? _a : 0,
381
+ transcript: stringOrNull(value.transcript),
382
+ summary,
383
+ frames: {
384
+ total: (_b = numberOrNull(frames.total)) != null ? _b : 0,
385
+ offset: (_c = numberOrNull(frames.offset)) != null ? _c : 0,
386
+ limit: (_d = numberOrNull(frames.limit)) != null ? _d : 0,
387
+ items: Array.isArray(frames.items) ? frames.items.filter(isRecord).filter((f) => typeof f.url === "string" && typeof f.tMs === "number").map((f) => ({ tMs: f.tMs, url: f.url })) : []
388
+ }
389
+ };
390
+ }
391
+ function setNumber(query, name, value) {
392
+ if (typeof value === "number" && Number.isFinite(value)) query.set(name, String(Math.floor(value)));
393
+ }
394
+ async function listCalls(options = {}) {
395
+ try {
396
+ const key = callsKey(options);
397
+ if (!key) return [];
398
+ const query = new URLSearchParams();
399
+ setNumber(query, "limit", options.limit);
400
+ if (typeof options.before === "string" && options.before) query.set("before", options.before);
401
+ const qs = query.toString();
402
+ const { data } = await callSdkApi(`/api/v1/sdk/calls${qs ? `?${qs}` : ""}`, key, options, { cache: "no-store" });
403
+ if (!Array.isArray(data)) return [];
404
+ return data.map(readCallListItem).filter((call) => call !== null);
405
+ } catch (e) {
406
+ return [];
407
+ }
408
+ }
409
+ async function getCall(id, options = {}) {
410
+ try {
411
+ const wanted = typeof id === "string" ? id.trim() : "";
412
+ const key = callsKey(options);
413
+ if (!wanted || !key) return null;
414
+ const query = new URLSearchParams({ id: wanted });
415
+ setNumber(query, "framesOffset", options.framesOffset);
416
+ setNumber(query, "framesLimit", options.framesLimit);
417
+ const { data } = await callSdkApi(`/api/v1/sdk/calls?${query.toString()}`, key, options, { cache: "no-store" });
418
+ return readCall(data);
419
+ } catch (e) {
420
+ return null;
421
+ }
422
+ }
423
+ async function deleteCall(id, options = {}) {
424
+ try {
425
+ const wanted = typeof id === "string" ? id.trim() : "";
426
+ if (!wanted) return { deleted: false, error: "No call id" };
427
+ const key = callsKey(options);
428
+ if (!key) return { deleted: false, error: "No calls key \u2014 set GLITCHGRAB_CALLS_KEY (ggc_\u2026)" };
429
+ const { error } = await callSdkApi(`/api/v1/sdk/calls?id=${encodeURIComponent(wanted)}`, key, options, {
430
+ method: "DELETE",
431
+ cache: "no-store"
432
+ });
433
+ return error === null ? { deleted: true } : { deleted: false, error };
434
+ } catch (e) {
435
+ return { deleted: false, error: "Could not delete the call" };
436
+ }
437
+ }
438
+ function escapeJsonForScript(value) {
439
+ try {
440
+ const json = JSON.stringify(value);
441
+ if (json === void 0) return "null";
442
+ return json.replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
443
+ } catch (e) {
444
+ return "null";
445
+ }
446
+ }
250
447
  // Annotate the CommonJS export names for ESM import in node:
251
448
  0 && (module.exports = {
252
449
  captureServerErrors,
253
450
  configureServerReporter,
451
+ deleteCall,
452
+ escapeJsonForScript,
453
+ getCall,
454
+ getGuide,
455
+ listCalls,
456
+ listGuides,
254
457
  reportServerError,
255
458
  resetServerReporter,
256
459
  signSession