@glasstrace/sdk 0.4.2 → 0.7.1

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/index.d.cts CHANGED
@@ -67,6 +67,7 @@ type GlasstraceEnvVars = z.infer<typeof GlasstraceEnvVarsSchema>;
67
67
  * - SDK health diagnostics (embedded in init requests)
68
68
  * - Discovery endpoint (GET /__glasstrace/config)
69
69
  * - Source map upload (POST /v1/source-maps)
70
+ * - Presigned source map upload (POST /v1/source-maps/presign, POST /v1/source-maps/manifest)
70
71
  */
71
72
 
72
73
  /** Test file import relationships, embedded in SDK init request. */
@@ -112,6 +113,11 @@ declare const SdkInitResponseSchema: z.ZodObject<{
112
113
  maxTraceSizeBytes: z.ZodNumber;
113
114
  maxConcurrentSessions: z.ZodNumber;
114
115
  }, z.core.$strip>;
116
+ claimResult: z.ZodOptional<z.ZodObject<{
117
+ newApiKey: z.core.$ZodBranded<z.ZodString, "DevApiKey", "out">;
118
+ accountId: z.ZodString;
119
+ graceExpiresAt: z.ZodNumber;
120
+ }, z.core.$strip>>;
115
121
  }, z.core.$strip>;
116
122
  type SdkInitResponse = z.infer<typeof SdkInitResponseSchema>;
117
123
  /** Response from POST /v1/source-maps. */
@@ -122,6 +128,15 @@ declare const SourceMapUploadResponseSchema: z.ZodObject<{
122
128
  totalSizeBytes: z.ZodNumber;
123
129
  }, z.core.$strip>;
124
130
  type SourceMapUploadResponse = z.infer<typeof SourceMapUploadResponseSchema>;
131
+ /** Response confirming source map manifest activation. */
132
+ declare const SourceMapManifestResponseSchema: z.ZodObject<{
133
+ success: z.ZodLiteral<true>;
134
+ buildHash: z.core.$ZodBranded<z.ZodString, "BuildHash", "out">;
135
+ fileCount: z.ZodNumber;
136
+ totalSizeBytes: z.ZodNumber;
137
+ activatedAt: z.ZodNumber;
138
+ }, z.core.$strip>;
139
+ type SourceMapManifestResponse = z.infer<typeof SourceMapManifestResponseSchema>;
125
140
 
126
141
  /**
127
142
  * Internal SDK error class with a typed diagnostic code.
@@ -270,11 +285,24 @@ declare function sendInitRequest(config: ResolvedConfig, anonKey: AnonApiKey | n
270
285
  message: string;
271
286
  timestamp: number;
272
287
  }>, signal?: AbortSignal): Promise<SdkInitResponse>;
288
+ /**
289
+ * Result returned by {@link performInit} when the backend reports an
290
+ * account claim transition. `null` means no claim was present.
291
+ */
292
+ interface InitClaimResult {
293
+ claimResult: NonNullable<SdkInitResponse["claimResult"]>;
294
+ }
273
295
  /**
274
296
  * Orchestrates the full init flow: send request, update config, cache result.
275
297
  * This function MUST NOT throw.
298
+ *
299
+ * Returns the claim result when the backend reports an account claim
300
+ * transition, or `null` when no claim result is available (including
301
+ * when init is skipped due to rate-limit backoff, missing API key,
302
+ * or request failure). Callers that do not need claim information
303
+ * can safely ignore the return value.
276
304
  */
277
- declare function performInit(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string): Promise<void>;
305
+ declare function performInit(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string): Promise<InitClaimResult | null>;
278
306
  /**
279
307
  * Returns the current capture config from the three-tier fallback chain:
280
308
  * 1. In-memory config from latest init response
@@ -457,6 +485,43 @@ declare function computeBuildHash(maps?: SourceMapEntry[]): Promise<string>;
457
485
  * and file entries. Validates the response against SourceMapUploadResponseSchema.
458
486
  */
459
487
  declare function uploadSourceMaps(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[]): Promise<SourceMapUploadResponse>;
488
+ /** Builds at or above this byte size route to the presigned upload flow. */
489
+ declare const PRESIGNED_THRESHOLD_BYTES = 4500000;
490
+ /** Signature for the blob upload function, injectable for testing. */
491
+ type BlobUploader = (clientToken: string, pathname: string, content: string) => Promise<{
492
+ url: string;
493
+ size: number;
494
+ }>;
495
+ /**
496
+ * Orchestrates the 3-phase presigned upload flow.
497
+ *
498
+ * 1. Requests presigned tokens for all source map files
499
+ * 2. Uploads each file to blob storage with a concurrency limit of 5
500
+ * 3. Submits the manifest to finalize the upload
501
+ *
502
+ * Accepts an optional `blobUploader` for test injection; defaults to
503
+ * {@link uploadToBlob}.
504
+ */
505
+ declare function uploadSourceMapsPresigned(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[], blobUploader?: BlobUploader): Promise<SourceMapManifestResponse>;
506
+ /**
507
+ * Options for {@link uploadSourceMapsAuto}, primarily used for test injection.
508
+ */
509
+ interface AutoUploadOptions {
510
+ /** Override blob availability check (for testing). */
511
+ checkBlobAvailable?: () => Promise<boolean>;
512
+ /** Override blob uploader (for testing). */
513
+ blobUploader?: BlobUploader;
514
+ }
515
+ /**
516
+ * Automatically routes source map uploads based on total build size.
517
+ *
518
+ * - Below {@link PRESIGNED_THRESHOLD_BYTES}: uses the legacy single-request
519
+ * {@link uploadSourceMaps} endpoint.
520
+ * - At or above the threshold: checks if `@vercel/blob` is available and
521
+ * uses the presigned 3-phase flow. Falls back to legacy with a warning
522
+ * if the package is not installed.
523
+ */
524
+ declare function uploadSourceMapsAuto(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[], options?: AutoUploadOptions): Promise<SourceMapUploadResponse | SourceMapManifestResponse>;
460
525
 
461
526
  /**
462
527
  * Manual error capture API.
@@ -519,4 +584,4 @@ declare function extractImports(fileContent: string): string[];
519
584
  */
520
585
  declare function buildImportGraph(projectRoot: string): Promise<ImportGraphPayload>;
521
586
 
522
- export { type FetchTarget, GlasstraceExporter, type GlasstraceExporterOptions, GlasstraceSpanProcessor, type ResolvedConfig, SdkError, SessionManager, type SourceMapEntry, buildImportGraph, captureError, classifyFetchTarget, collectSourceMaps, computeBuildHash, createDiscoveryHandler, deriveSessionId, discoverTestFiles, extractImports, getActiveConfig, getDateString, getDiscoveryHandler, getOrCreateAnonKey, getOrigin, isAnonymousMode, isProductionDisabled, loadCachedConfig, performInit, readAnonKey, readEnvVars, registerGlasstrace, resolveConfig, saveCachedConfig, sendInitRequest, uploadSourceMaps, withGlasstraceConfig };
587
+ export { type AutoUploadOptions, type BlobUploader, type FetchTarget, GlasstraceExporter, type GlasstraceExporterOptions, GlasstraceSpanProcessor, type InitClaimResult, PRESIGNED_THRESHOLD_BYTES, type ResolvedConfig, SdkError, SessionManager, type SourceMapEntry, buildImportGraph, captureError, classifyFetchTarget, collectSourceMaps, computeBuildHash, createDiscoveryHandler, deriveSessionId, discoverTestFiles, extractImports, getActiveConfig, getDateString, getDiscoveryHandler, getOrCreateAnonKey, getOrigin, isAnonymousMode, isProductionDisabled, loadCachedConfig, performInit, readAnonKey, readEnvVars, registerGlasstrace, resolveConfig, saveCachedConfig, sendInitRequest, uploadSourceMaps, uploadSourceMapsAuto, uploadSourceMapsPresigned, withGlasstraceConfig };
package/dist/index.d.ts CHANGED
@@ -67,6 +67,7 @@ type GlasstraceEnvVars = z.infer<typeof GlasstraceEnvVarsSchema>;
67
67
  * - SDK health diagnostics (embedded in init requests)
68
68
  * - Discovery endpoint (GET /__glasstrace/config)
69
69
  * - Source map upload (POST /v1/source-maps)
70
+ * - Presigned source map upload (POST /v1/source-maps/presign, POST /v1/source-maps/manifest)
70
71
  */
71
72
 
72
73
  /** Test file import relationships, embedded in SDK init request. */
@@ -112,6 +113,11 @@ declare const SdkInitResponseSchema: z.ZodObject<{
112
113
  maxTraceSizeBytes: z.ZodNumber;
113
114
  maxConcurrentSessions: z.ZodNumber;
114
115
  }, z.core.$strip>;
116
+ claimResult: z.ZodOptional<z.ZodObject<{
117
+ newApiKey: z.core.$ZodBranded<z.ZodString, "DevApiKey", "out">;
118
+ accountId: z.ZodString;
119
+ graceExpiresAt: z.ZodNumber;
120
+ }, z.core.$strip>>;
115
121
  }, z.core.$strip>;
116
122
  type SdkInitResponse = z.infer<typeof SdkInitResponseSchema>;
117
123
  /** Response from POST /v1/source-maps. */
@@ -122,6 +128,15 @@ declare const SourceMapUploadResponseSchema: z.ZodObject<{
122
128
  totalSizeBytes: z.ZodNumber;
123
129
  }, z.core.$strip>;
124
130
  type SourceMapUploadResponse = z.infer<typeof SourceMapUploadResponseSchema>;
131
+ /** Response confirming source map manifest activation. */
132
+ declare const SourceMapManifestResponseSchema: z.ZodObject<{
133
+ success: z.ZodLiteral<true>;
134
+ buildHash: z.core.$ZodBranded<z.ZodString, "BuildHash", "out">;
135
+ fileCount: z.ZodNumber;
136
+ totalSizeBytes: z.ZodNumber;
137
+ activatedAt: z.ZodNumber;
138
+ }, z.core.$strip>;
139
+ type SourceMapManifestResponse = z.infer<typeof SourceMapManifestResponseSchema>;
125
140
 
126
141
  /**
127
142
  * Internal SDK error class with a typed diagnostic code.
@@ -270,11 +285,24 @@ declare function sendInitRequest(config: ResolvedConfig, anonKey: AnonApiKey | n
270
285
  message: string;
271
286
  timestamp: number;
272
287
  }>, signal?: AbortSignal): Promise<SdkInitResponse>;
288
+ /**
289
+ * Result returned by {@link performInit} when the backend reports an
290
+ * account claim transition. `null` means no claim was present.
291
+ */
292
+ interface InitClaimResult {
293
+ claimResult: NonNullable<SdkInitResponse["claimResult"]>;
294
+ }
273
295
  /**
274
296
  * Orchestrates the full init flow: send request, update config, cache result.
275
297
  * This function MUST NOT throw.
298
+ *
299
+ * Returns the claim result when the backend reports an account claim
300
+ * transition, or `null` when no claim result is available (including
301
+ * when init is skipped due to rate-limit backoff, missing API key,
302
+ * or request failure). Callers that do not need claim information
303
+ * can safely ignore the return value.
276
304
  */
277
- declare function performInit(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string): Promise<void>;
305
+ declare function performInit(config: ResolvedConfig, anonKey: AnonApiKey | null, sdkVersion: string): Promise<InitClaimResult | null>;
278
306
  /**
279
307
  * Returns the current capture config from the three-tier fallback chain:
280
308
  * 1. In-memory config from latest init response
@@ -457,6 +485,43 @@ declare function computeBuildHash(maps?: SourceMapEntry[]): Promise<string>;
457
485
  * and file entries. Validates the response against SourceMapUploadResponseSchema.
458
486
  */
459
487
  declare function uploadSourceMaps(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[]): Promise<SourceMapUploadResponse>;
488
+ /** Builds at or above this byte size route to the presigned upload flow. */
489
+ declare const PRESIGNED_THRESHOLD_BYTES = 4500000;
490
+ /** Signature for the blob upload function, injectable for testing. */
491
+ type BlobUploader = (clientToken: string, pathname: string, content: string) => Promise<{
492
+ url: string;
493
+ size: number;
494
+ }>;
495
+ /**
496
+ * Orchestrates the 3-phase presigned upload flow.
497
+ *
498
+ * 1. Requests presigned tokens for all source map files
499
+ * 2. Uploads each file to blob storage with a concurrency limit of 5
500
+ * 3. Submits the manifest to finalize the upload
501
+ *
502
+ * Accepts an optional `blobUploader` for test injection; defaults to
503
+ * {@link uploadToBlob}.
504
+ */
505
+ declare function uploadSourceMapsPresigned(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[], blobUploader?: BlobUploader): Promise<SourceMapManifestResponse>;
506
+ /**
507
+ * Options for {@link uploadSourceMapsAuto}, primarily used for test injection.
508
+ */
509
+ interface AutoUploadOptions {
510
+ /** Override blob availability check (for testing). */
511
+ checkBlobAvailable?: () => Promise<boolean>;
512
+ /** Override blob uploader (for testing). */
513
+ blobUploader?: BlobUploader;
514
+ }
515
+ /**
516
+ * Automatically routes source map uploads based on total build size.
517
+ *
518
+ * - Below {@link PRESIGNED_THRESHOLD_BYTES}: uses the legacy single-request
519
+ * {@link uploadSourceMaps} endpoint.
520
+ * - At or above the threshold: checks if `@vercel/blob` is available and
521
+ * uses the presigned 3-phase flow. Falls back to legacy with a warning
522
+ * if the package is not installed.
523
+ */
524
+ declare function uploadSourceMapsAuto(apiKey: string, endpoint: string, buildHash: string, maps: SourceMapEntry[], options?: AutoUploadOptions): Promise<SourceMapUploadResponse | SourceMapManifestResponse>;
460
525
 
461
526
  /**
462
527
  * Manual error capture API.
@@ -519,4 +584,4 @@ declare function extractImports(fileContent: string): string[];
519
584
  */
520
585
  declare function buildImportGraph(projectRoot: string): Promise<ImportGraphPayload>;
521
586
 
522
- export { type FetchTarget, GlasstraceExporter, type GlasstraceExporterOptions, GlasstraceSpanProcessor, type ResolvedConfig, SdkError, SessionManager, type SourceMapEntry, buildImportGraph, captureError, classifyFetchTarget, collectSourceMaps, computeBuildHash, createDiscoveryHandler, deriveSessionId, discoverTestFiles, extractImports, getActiveConfig, getDateString, getDiscoveryHandler, getOrCreateAnonKey, getOrigin, isAnonymousMode, isProductionDisabled, loadCachedConfig, performInit, readAnonKey, readEnvVars, registerGlasstrace, resolveConfig, saveCachedConfig, sendInitRequest, uploadSourceMaps, withGlasstraceConfig };
587
+ export { type AutoUploadOptions, type BlobUploader, type FetchTarget, GlasstraceExporter, type GlasstraceExporterOptions, GlasstraceSpanProcessor, type InitClaimResult, PRESIGNED_THRESHOLD_BYTES, type ResolvedConfig, SdkError, SessionManager, type SourceMapEntry, buildImportGraph, captureError, classifyFetchTarget, collectSourceMaps, computeBuildHash, createDiscoveryHandler, deriveSessionId, discoverTestFiles, extractImports, getActiveConfig, getDateString, getDiscoveryHandler, getOrCreateAnonKey, getOrigin, isAnonymousMode, isProductionDisabled, loadCachedConfig, performInit, readAnonKey, readEnvVars, registerGlasstrace, resolveConfig, saveCachedConfig, sendInitRequest, uploadSourceMaps, uploadSourceMapsAuto, uploadSourceMapsPresigned, withGlasstraceConfig };
package/dist/index.js CHANGED
@@ -2,17 +2,19 @@ import {
2
2
  buildImportGraph,
3
3
  discoverTestFiles,
4
4
  extractImports
5
- } from "./chunk-67H2JEI4.js";
5
+ } from "./chunk-SALPGSWK.js";
6
6
  import {
7
7
  DEFAULT_CAPTURE_CONFIG,
8
8
  GLASSTRACE_ATTRIBUTE_NAMES,
9
+ PresignedUploadResponseSchema,
9
10
  SdkCachedConfigSchema,
10
11
  SdkInitResponseSchema,
11
12
  SessionIdSchema,
13
+ SourceMapManifestResponseSchema,
12
14
  SourceMapUploadResponseSchema,
13
15
  getOrCreateAnonKey,
14
16
  readAnonKey
15
- } from "./chunk-TVOYTJ7I.js";
17
+ } from "./chunk-QW6W4CSA.js";
16
18
  import {
17
19
  INVALID_SPAN_CONTEXT,
18
20
  SamplingDecision,
@@ -184,7 +186,7 @@ function classifyFetchTarget(url) {
184
186
 
185
187
  // src/init-client.ts
186
188
  import { readFileSync } from "fs";
187
- import { writeFile, mkdir } from "fs/promises";
189
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
188
190
  import { join } from "path";
189
191
  var GLASSTRACE_DIR = ".glasstrace";
190
192
  var CONFIG_FILE = "config";
@@ -220,12 +222,14 @@ async function saveCachedConfig(response, projectRoot) {
220
222
  const dirPath = join(root, GLASSTRACE_DIR);
221
223
  const configPath = join(dirPath, CONFIG_FILE);
222
224
  try {
223
- await mkdir(dirPath, { recursive: true });
225
+ await mkdir(dirPath, { recursive: true, mode: 448 });
226
+ await chmod(dirPath, 448);
224
227
  const cached = {
225
228
  response,
226
229
  cachedAt: Date.now()
227
230
  };
228
- await writeFile(configPath, JSON.stringify(cached), "utf-8");
231
+ await writeFile(configPath, JSON.stringify(cached), { encoding: "utf-8", mode: 384 });
232
+ await chmod(configPath, 384);
229
233
  } catch (err) {
230
234
  console.warn(
231
235
  `[glasstrace] Failed to cache config to ${configPath}: ${err instanceof Error ? err.message : String(err)}`
@@ -278,16 +282,88 @@ async function sendInitRequest(config, anonKey, sdkVersion, importGraph, healthR
278
282
  const body = await response.json();
279
283
  return SdkInitResponseSchema.parse(body);
280
284
  }
285
+ async function writeClaimedKey(newApiKey, projectRoot) {
286
+ const root = projectRoot ?? process.cwd();
287
+ const envLocalPath = join(root, ".env.local");
288
+ let envLocalWritten = false;
289
+ try {
290
+ let content;
291
+ try {
292
+ content = await readFile(envLocalPath, "utf-8");
293
+ if (/^GLASSTRACE_API_KEY=.*/m.test(content)) {
294
+ content = content.replace(
295
+ /^GLASSTRACE_API_KEY=.*$/gm,
296
+ `GLASSTRACE_API_KEY=${newApiKey}`
297
+ );
298
+ } else {
299
+ if (content.length > 0 && !content.endsWith("\n")) {
300
+ content += "\n";
301
+ }
302
+ content += `GLASSTRACE_API_KEY=${newApiKey}
303
+ `;
304
+ }
305
+ } catch (readErr) {
306
+ const code = readErr instanceof Error ? readErr.code : void 0;
307
+ if (code !== "ENOENT") {
308
+ throw readErr;
309
+ }
310
+ content = `GLASSTRACE_API_KEY=${newApiKey}
311
+ `;
312
+ }
313
+ await writeFile(envLocalPath, content, { encoding: "utf-8", mode: 384 });
314
+ await chmod(envLocalPath, 384);
315
+ envLocalWritten = true;
316
+ } catch {
317
+ }
318
+ if (envLocalWritten) {
319
+ try {
320
+ process.stderr.write(
321
+ "[glasstrace] Account claimed! API key written to .env.local. Restart your dev server to use it.\n"
322
+ );
323
+ } catch {
324
+ }
325
+ return;
326
+ }
327
+ let claimedKeyWritten = false;
328
+ try {
329
+ const dirPath = join(root, GLASSTRACE_DIR);
330
+ await mkdir(dirPath, { recursive: true, mode: 448 });
331
+ await chmod(dirPath, 448);
332
+ const claimedKeyPath = join(dirPath, "claimed-key");
333
+ await writeFile(claimedKeyPath, newApiKey, {
334
+ encoding: "utf-8",
335
+ mode: 384
336
+ });
337
+ await chmod(claimedKeyPath, 384);
338
+ claimedKeyWritten = true;
339
+ } catch {
340
+ }
341
+ if (claimedKeyWritten) {
342
+ try {
343
+ process.stderr.write(
344
+ "[glasstrace] Account claimed! API key written to .glasstrace/claimed-key. Copy it to your .env.local file.\n"
345
+ );
346
+ } catch {
347
+ }
348
+ return;
349
+ }
350
+ try {
351
+ process.stderr.write(
352
+ "[glasstrace] Account claimed but could not write key to disk. Visit your dashboard settings to rotate and retrieve a new API key.\n"
353
+ );
354
+ } catch {
355
+ }
356
+ }
281
357
  async function performInit(config, anonKey, sdkVersion) {
282
358
  if (rateLimitBackoff) {
283
359
  rateLimitBackoff = false;
284
- return;
360
+ return null;
285
361
  }
286
362
  try {
287
363
  const effectiveKey = config.apiKey || anonKey;
288
364
  if (!effectiveKey) {
289
365
  console.warn("[glasstrace] No API key available for init request.");
290
- return;
366
+ return null;
291
367
  }
292
368
  const controller = new AbortController();
293
369
  const timeoutId = setTimeout(() => controller.abort(), INIT_TIMEOUT_MS);
@@ -304,45 +380,55 @@ async function performInit(config, anonKey, sdkVersion) {
304
380
  clearTimeout(timeoutId);
305
381
  currentConfig = result;
306
382
  await saveCachedConfig(result);
383
+ if (result.claimResult) {
384
+ try {
385
+ await writeClaimedKey(result.claimResult.newApiKey);
386
+ } catch {
387
+ }
388
+ return { claimResult: result.claimResult };
389
+ }
390
+ return null;
307
391
  } catch (err) {
308
392
  clearTimeout(timeoutId);
309
393
  if (err instanceof DOMException && err.name === "AbortError") {
310
394
  console.warn("[glasstrace] ingestion_unreachable: Init request timed out.");
311
- return;
395
+ return null;
312
396
  }
313
397
  const status = err.status;
314
398
  if (status === 401) {
315
399
  console.warn(
316
400
  "[glasstrace] ingestion_auth_failed: Check your GLASSTRACE_API_KEY."
317
401
  );
318
- return;
402
+ return null;
319
403
  }
320
404
  if (status === 429) {
321
405
  console.warn("[glasstrace] ingestion_rate_limited: Backing off.");
322
406
  rateLimitBackoff = true;
323
- return;
407
+ return null;
324
408
  }
325
409
  if (typeof status === "number" && status >= 400) {
326
410
  console.warn(
327
411
  `[glasstrace] Init request failed with status ${status}. Using cached config.`
328
412
  );
329
- return;
413
+ return null;
330
414
  }
331
415
  if (err instanceof Error && err.name === "ZodError") {
332
416
  console.warn(
333
417
  "[glasstrace] Init response failed validation (schema version mismatch?). Using cached config."
334
418
  );
335
- return;
419
+ return null;
336
420
  }
337
421
  console.warn(
338
422
  `[glasstrace] ingestion_unreachable: ${err instanceof Error ? err.message : String(err)}`
339
423
  );
424
+ return null;
340
425
  }
341
426
  } catch (err) {
342
427
  console.warn(
343
428
  `[glasstrace] Unexpected init error: ${err instanceof Error ? err.message : String(err)}`
344
429
  );
345
430
  }
431
+ return null;
346
432
  }
347
433
  function getActiveConfig() {
348
434
  if (currentConfig) {
@@ -3452,6 +3538,14 @@ async function installConsoleCapture() {
3452
3538
  }
3453
3539
  };
3454
3540
  }
3541
+ function sdkLog(level, message) {
3542
+ isGlasstraceLog = true;
3543
+ try {
3544
+ console[level](message);
3545
+ } finally {
3546
+ isGlasstraceLog = false;
3547
+ }
3548
+ }
3455
3549
 
3456
3550
  // src/register.ts
3457
3551
  var consoleCaptureInstalled = false;
@@ -3541,7 +3635,11 @@ function registerGlasstrace(options) {
3541
3635
  if (config.verbose) {
3542
3636
  console.info("[glasstrace] Background init firing.");
3543
3637
  }
3544
- await performInit(config, anonKey, "0.4.2");
3638
+ const initResult = await performInit(config, anonKey, "0.7.1");
3639
+ if (initResult?.claimResult) {
3640
+ setResolvedApiKey(initResult.claimResult.newApiKey);
3641
+ notifyApiKeyResolved();
3642
+ }
3545
3643
  maybeInstallConsoleCapture();
3546
3644
  } catch (err) {
3547
3645
  console.warn(
@@ -3561,7 +3659,11 @@ function registerGlasstrace(options) {
3561
3659
  if (config.verbose) {
3562
3660
  console.info("[glasstrace] Background init firing.");
3563
3661
  }
3564
- await performInit(config, anonKey, "0.4.2");
3662
+ const initResult = await performInit(config, anonKey, "0.7.1");
3663
+ if (initResult?.claimResult) {
3664
+ setResolvedApiKey(initResult.claimResult.newApiKey);
3665
+ notifyApiKeyResolved();
3666
+ }
3565
3667
  maybeInstallConsoleCapture();
3566
3668
  } catch (err) {
3567
3669
  console.warn(
@@ -3583,7 +3685,7 @@ function registerGlasstrace(options) {
3583
3685
  if (config.verbose) {
3584
3686
  console.info("[glasstrace] Background init firing.");
3585
3687
  }
3586
- await performInit(config, anonKeyForInit, "0.4.2");
3688
+ await performInit(config, anonKeyForInit, "0.7.1");
3587
3689
  maybeInstallConsoleCapture();
3588
3690
  } catch (err) {
3589
3691
  console.warn(
@@ -3682,10 +3784,7 @@ async function uploadSourceMaps(apiKey, endpoint, buildHash, maps) {
3682
3784
  sourceMap: m.content
3683
3785
  }))
3684
3786
  };
3685
- let baseUrl = endpoint;
3686
- while (baseUrl.endsWith("/")) {
3687
- baseUrl = baseUrl.slice(0, -1);
3688
- }
3787
+ const baseUrl = stripTrailingSlashes(endpoint);
3689
3788
  const response = await fetch(`${baseUrl}/v1/source-maps`, {
3690
3789
  method: "POST",
3691
3790
  headers: {
@@ -3706,6 +3805,155 @@ async function uploadSourceMaps(apiKey, endpoint, buildHash, maps) {
3706
3805
  const json = await response.json();
3707
3806
  return SourceMapUploadResponseSchema.parse(json);
3708
3807
  }
3808
+ var PRESIGNED_THRESHOLD_BYTES = 45e5;
3809
+ function stripTrailingSlashes(url) {
3810
+ let result = url;
3811
+ while (result.endsWith("/")) {
3812
+ result = result.slice(0, -1);
3813
+ }
3814
+ return result;
3815
+ }
3816
+ async function requestPresignedTokens(apiKey, endpoint, buildHash, files) {
3817
+ const baseUrl = stripTrailingSlashes(endpoint);
3818
+ const response = await fetch(`${baseUrl}/v1/source-maps/presign`, {
3819
+ method: "POST",
3820
+ headers: {
3821
+ "Content-Type": "application/json",
3822
+ Authorization: `Bearer ${apiKey}`
3823
+ },
3824
+ body: JSON.stringify({ buildHash, files })
3825
+ });
3826
+ if (!response.ok) {
3827
+ try {
3828
+ await response.text();
3829
+ } catch {
3830
+ }
3831
+ throw new Error(
3832
+ `Presigned token request failed: ${String(response.status)} ${response.statusText}`
3833
+ );
3834
+ }
3835
+ const json = await response.json();
3836
+ return PresignedUploadResponseSchema.parse(json);
3837
+ }
3838
+ async function uploadToBlob(clientToken, pathname, content) {
3839
+ let mod;
3840
+ try {
3841
+ mod = await import("@vercel/blob/client");
3842
+ } catch (err) {
3843
+ const code = err.code;
3844
+ if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
3845
+ throw new Error(
3846
+ "Presigned upload requires @vercel/blob. Install it: npm install @vercel/blob"
3847
+ );
3848
+ }
3849
+ throw err;
3850
+ }
3851
+ const result = await mod.put(pathname, new Blob([content]), {
3852
+ access: "public",
3853
+ token: clientToken
3854
+ });
3855
+ return { url: result.url, size: Buffer.byteLength(content, "utf-8") };
3856
+ }
3857
+ async function submitManifest(apiKey, endpoint, uploadId, buildHash, files) {
3858
+ const baseUrl = stripTrailingSlashes(endpoint);
3859
+ const response = await fetch(`${baseUrl}/v1/source-maps/manifest`, {
3860
+ method: "POST",
3861
+ headers: {
3862
+ "Content-Type": "application/json",
3863
+ Authorization: `Bearer ${apiKey}`
3864
+ },
3865
+ body: JSON.stringify({ uploadId, buildHash, files })
3866
+ });
3867
+ if (!response.ok) {
3868
+ try {
3869
+ await response.text();
3870
+ } catch {
3871
+ }
3872
+ throw new Error(
3873
+ `Source map manifest submission failed: ${String(response.status)} ${response.statusText}`
3874
+ );
3875
+ }
3876
+ const json = await response.json();
3877
+ return SourceMapManifestResponseSchema.parse(json);
3878
+ }
3879
+ async function uploadSourceMapsPresigned(apiKey, endpoint, buildHash, maps, blobUploader = uploadToBlob) {
3880
+ if (maps.length === 0) {
3881
+ throw new Error("No source maps to upload");
3882
+ }
3883
+ const presigned = await requestPresignedTokens(
3884
+ apiKey,
3885
+ endpoint,
3886
+ buildHash,
3887
+ maps.map((m) => ({
3888
+ filePath: m.filePath,
3889
+ sizeBytes: Buffer.byteLength(m.content, "utf-8")
3890
+ }))
3891
+ );
3892
+ const mapsByPath = new Map(maps.map((m) => [m.filePath, m]));
3893
+ if (mapsByPath.size !== maps.length) {
3894
+ throw new Error("Duplicate filePath entries in source maps");
3895
+ }
3896
+ for (const token of presigned.files) {
3897
+ if (!mapsByPath.has(token.filePath)) {
3898
+ throw new Error(
3899
+ `Presigned token for "${token.filePath}" has no matching source map entry`
3900
+ );
3901
+ }
3902
+ }
3903
+ const CONCURRENCY = 5;
3904
+ const uploadResults = [];
3905
+ for (let i = 0; i < presigned.files.length; i += CONCURRENCY) {
3906
+ const chunk = presigned.files.slice(i, i + CONCURRENCY);
3907
+ const chunkResults = await Promise.all(
3908
+ chunk.map(async (token) => {
3909
+ const entry = mapsByPath.get(token.filePath);
3910
+ const result = await blobUploader(token.clientToken, token.pathname, entry.content);
3911
+ return {
3912
+ filePath: token.filePath,
3913
+ sizeBytes: result.size,
3914
+ blobUrl: result.url
3915
+ };
3916
+ })
3917
+ );
3918
+ uploadResults.push(...chunkResults);
3919
+ }
3920
+ return submitManifest(apiKey, endpoint, presigned.uploadId, buildHash, uploadResults);
3921
+ }
3922
+ async function uploadSourceMapsAuto(apiKey, endpoint, buildHash, maps, options) {
3923
+ if (maps.length === 0) {
3924
+ throw new Error("No source maps to upload");
3925
+ }
3926
+ const totalBytes = maps.reduce(
3927
+ (sum, m) => sum + Buffer.byteLength(m.content, "utf-8"),
3928
+ 0
3929
+ );
3930
+ if (totalBytes < PRESIGNED_THRESHOLD_BYTES) {
3931
+ return uploadSourceMaps(apiKey, endpoint, buildHash, maps);
3932
+ }
3933
+ const checkAvailable = options?.checkBlobAvailable ?? (async () => {
3934
+ try {
3935
+ await import("@vercel/blob/client");
3936
+ return true;
3937
+ } catch {
3938
+ return false;
3939
+ }
3940
+ });
3941
+ const blobAvailable = await checkAvailable();
3942
+ if (blobAvailable) {
3943
+ return uploadSourceMapsPresigned(
3944
+ apiKey,
3945
+ endpoint,
3946
+ buildHash,
3947
+ maps,
3948
+ options?.blobUploader
3949
+ );
3950
+ }
3951
+ sdkLog(
3952
+ "warn",
3953
+ `[glasstrace] Build exceeds 4.5MB (${totalBytes} bytes). Install @vercel/blob for presigned uploads to avoid serverless body size limits. Falling back to legacy upload.`
3954
+ );
3955
+ return uploadSourceMaps(apiKey, endpoint, buildHash, maps);
3956
+ }
3709
3957
 
3710
3958
  // src/config-wrapper.ts
3711
3959
  function withGlasstraceConfig(nextConfig) {
@@ -3823,6 +4071,7 @@ function captureError(error) {
3823
4071
  export {
3824
4072
  GlasstraceExporter,
3825
4073
  GlasstraceSpanProcessor,
4074
+ PRESIGNED_THRESHOLD_BYTES,
3826
4075
  SdkError,
3827
4076
  SessionManager,
3828
4077
  buildImportGraph,
@@ -3850,6 +4099,8 @@ export {
3850
4099
  saveCachedConfig,
3851
4100
  sendInitRequest,
3852
4101
  uploadSourceMaps,
4102
+ uploadSourceMapsAuto,
4103
+ uploadSourceMapsPresigned,
3853
4104
  withGlasstraceConfig
3854
4105
  };
3855
4106
  //# sourceMappingURL=index.js.map