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/README.md +158 -13
- package/dist/index.js +814 -397
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +814 -397
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +227 -1
- package/dist/server.d.ts +227 -1
- package/dist/server.js +203 -0
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +197 -0
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
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
|