@sylphx/sdk 0.8.0-rc.2 → 0.9.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.
@@ -76,18 +76,27 @@ interface SylphxMiddlewareConfig {
76
76
  debug?: boolean;
77
77
  /**
78
78
  * Secret key for authentication.
79
- * Override to use a programmatic key instead of SYLPHX_SECRET_KEY env var.
79
+ * Override to use a programmatic key instead of SYLPHX_SECRET_URL env var.
80
80
  *
81
81
  * Use case: Platform Console uses dynamically generated keys.
82
82
  *
83
- * @default process.env.SYLPHX_SECRET_KEY
83
+ * @default credential parsed from process.env.SYLPHX_SECRET_URL, falling back
84
+ * to process.env.SYLPHX_SECRET_KEY for legacy apps
84
85
  */
85
86
  secretKey?: string;
87
+ /**
88
+ * Server connection URL for authentication.
89
+ *
90
+ * Format: sylphx://sk_<env>_<hex>@<tenant>.api.sylphx.com
91
+ *
92
+ * @default process.env.SYLPHX_SECRET_URL
93
+ */
94
+ secretUrl?: string;
86
95
  /**
87
96
  * Platform URL for API calls.
88
97
  * Override for self-hosted or same-origin deployments.
89
98
  *
90
- * @default https://api.sylphx.com
99
+ * @default resolved from SYLPHX_URL, then legacy 4-part key routing
91
100
  */
92
101
  platformUrl?: string;
93
102
  /**
@@ -216,7 +225,8 @@ interface UserCookieData {
216
225
  * Configure the SDK for server-side usage
217
226
  *
218
227
  * NOTE: This is optional! The SDK auto-configures from environment variables:
219
- * - SYLPHX_SECRET_KEY (required)
228
+ * - SYLPHX_SECRET_URL (recommended)
229
+ * - SYLPHX_SECRET_KEY (legacy fallback)
220
230
  *
221
231
  * Use this only if you need to override the default configuration.
222
232
  *
@@ -226,12 +236,13 @@ interface UserCookieData {
226
236
  * import { configureServer } from '@sylphx/sdk/nextjs'
227
237
  *
228
238
  * configureServer({
229
- * secretKey: process.env.SYLPHX_SECRET_KEY!,
239
+ * secretUrl: process.env.SYLPHX_SECRET_URL!,
230
240
  * })
231
241
  * ```
232
242
  */
233
243
  declare function configureServer(config: {
234
- secretKey: string;
244
+ secretKey?: string;
245
+ secretUrl?: string;
235
246
  platformUrl?: string;
236
247
  }): void;
237
248
  /**
@@ -2,6 +2,9 @@
2
2
  import { NextResponse } from "next/server";
3
3
 
4
4
  // src/constants.ts
5
+ var ENV_URL = "SYLPHX_URL";
6
+ var ENV_SECRET_URL = "SYLPHX_SECRET_URL";
7
+ var ENV_SECRET_KEY = "SYLPHX_SECRET_KEY";
5
8
  var DEFAULT_SDK_API_HOST = "api.sylphx.com";
6
9
  var SDK_PLATFORM = typeof window !== "undefined" ? "browser" : typeof process !== "undefined" && process.versions?.node ? "node" : "unknown";
7
10
  var TOKEN_EXPIRY_BUFFER_MS = 3e4;
@@ -296,6 +299,154 @@ function parseUserCookie(value) {
296
299
  }
297
300
  }
298
301
 
302
+ // src/connection-url.ts
303
+ var SYLPHX_PROTOCOL = "sylphx:";
304
+ var DEFAULT_VERSION = "v1";
305
+ var CREDENTIAL_REGEX = /^(pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}$/;
306
+ var VERSION_REGEX = /^v[0-9]+$/;
307
+ var SLUG_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
308
+ var InvalidConnectionUrlError = class _InvalidConnectionUrlError extends Error {
309
+ code = "INVALID_CONNECTION_URL";
310
+ constructor(message2) {
311
+ super(message2);
312
+ this.name = "InvalidConnectionUrlError";
313
+ Object.setPrototypeOf(this, _InvalidConnectionUrlError.prototype);
314
+ }
315
+ };
316
+ function fail(reason) {
317
+ throw new InvalidConnectionUrlError(`Invalid Sylphx connection URL: ${reason}`);
318
+ }
319
+ function parseCredential(raw) {
320
+ const match = CREDENTIAL_REGEX.exec(raw);
321
+ if (!match) {
322
+ fail(`credential must match (pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}, got "${raw}"`);
323
+ }
324
+ return {
325
+ credentialType: match[1],
326
+ env: match[2]
327
+ };
328
+ }
329
+ function validateSlug(candidate) {
330
+ if (!candidate || candidate.length > 63 || !SLUG_REGEX.test(candidate)) {
331
+ fail(`slug "${candidate}" is not a valid DNS label (lowercase alnum + hyphens, 1-63 chars)`);
332
+ }
333
+ return candidate;
334
+ }
335
+ function parseConnectionUrl(url) {
336
+ if (typeof url !== "string" || url.length === 0) {
337
+ fail("url must be a non-empty string");
338
+ }
339
+ let parsed;
340
+ try {
341
+ parsed = new URL(url);
342
+ } catch {
343
+ fail(`not a valid URL: "${url}"`);
344
+ }
345
+ if (parsed.protocol !== SYLPHX_PROTOCOL) {
346
+ fail(`protocol must be "sylphx:", got "${parsed.protocol}"`);
347
+ }
348
+ const credential = decodeURIComponent(parsed.username);
349
+ if (!credential) {
350
+ fail("missing credential (expected `sylphx://<credential>@<host>`)");
351
+ }
352
+ if (parsed.password) {
353
+ fail("connection URL must not contain a password component");
354
+ }
355
+ const { credentialType, env } = parseCredential(credential);
356
+ const host = parsed.host;
357
+ if (!host) {
358
+ fail("missing host");
359
+ }
360
+ const hostname = parsed.hostname;
361
+ const firstDot = hostname.indexOf(".");
362
+ if (firstDot <= 0) {
363
+ fail(`host "${hostname}" must contain at least one dot (slug.domain)`);
364
+ }
365
+ const slugCandidate = hostname.slice(0, firstDot);
366
+ const domainSuffix = hostname.slice(firstDot + 1);
367
+ if (!domainSuffix) {
368
+ fail(`host "${hostname}" has empty domain suffix`);
369
+ }
370
+ const slug = validateSlug(slugCandidate);
371
+ const rawPath = parsed.pathname.replace(/^\/+/, "").replace(/\/+$/, "");
372
+ let version = DEFAULT_VERSION;
373
+ if (rawPath !== "") {
374
+ if (!VERSION_REGEX.test(rawPath)) {
375
+ fail(`path "${parsed.pathname}" must be empty or match /v{N}`);
376
+ }
377
+ version = rawPath;
378
+ }
379
+ if (parsed.search) {
380
+ fail("connection URL must not contain a query string");
381
+ }
382
+ if (parsed.hash) {
383
+ fail("connection URL must not contain a fragment");
384
+ }
385
+ const apiBaseUrl = `https://${host}/${version}`;
386
+ return {
387
+ credential,
388
+ credentialType,
389
+ env,
390
+ slug,
391
+ host,
392
+ apiBaseUrl
393
+ };
394
+ }
395
+
396
+ // src/nextjs/runtime-config.ts
397
+ var SylphxRoutingConfigurationError = class extends Error {
398
+ name = "SylphxRoutingConfigurationError";
399
+ };
400
+ function normalizePlatformUrl(url) {
401
+ const trimmed = url.trim().replace(/\/+$/, "");
402
+ return trimmed.endsWith("/v1") ? trimmed.slice(0, -3) : trimmed;
403
+ }
404
+ function parseConnection(value) {
405
+ if (!value) return null;
406
+ return parseConnectionUrl(value.trim());
407
+ }
408
+ function platformUrlFromConnectionUrl(value) {
409
+ const parsed = parseConnection(value);
410
+ return parsed ? `https://${parsed.host}` : null;
411
+ }
412
+ function secretKeyFromConnectionUrl(value) {
413
+ const parsed = parseConnection(value);
414
+ if (!parsed || parsed.credentialType !== "sk") return null;
415
+ return parsed.credential;
416
+ }
417
+ function platformUrlFromLegacyKey(secretKey) {
418
+ if (!secretKey) return null;
419
+ const parts = secretKey.trim().toLowerCase().split("_");
420
+ const embeddedRef = parts.length === 4 ? parts[2] : null;
421
+ return embeddedRef ? `https://${embeddedRef}.${DEFAULT_SDK_API_HOST}` : null;
422
+ }
423
+ function resolveNextjsPlatformUrl(options) {
424
+ if (options.explicitPlatformUrl) return normalizePlatformUrl(options.explicitPlatformUrl);
425
+ const fromExplicitSecretUrl = platformUrlFromConnectionUrl(options.secretUrl);
426
+ if (fromExplicitSecretUrl) return fromExplicitSecretUrl;
427
+ const fromSecretUrl = platformUrlFromConnectionUrl(process.env[ENV_SECRET_URL]);
428
+ if (fromSecretUrl) return fromSecretUrl;
429
+ const fromConnectionUrl = platformUrlFromConnectionUrl(process.env[ENV_URL]);
430
+ if (fromConnectionUrl) return fromConnectionUrl;
431
+ const fromBaasOverride = process.env.SYLPHX_BAAS_URL ?? process.env.SYLPHX_RUNTIME_URL;
432
+ if (fromBaasOverride) return normalizePlatformUrl(fromBaasOverride);
433
+ const fromLegacyKey = platformUrlFromLegacyKey(options.secretKey);
434
+ if (fromLegacyKey) return fromLegacyKey;
435
+ throw new SylphxRoutingConfigurationError(
436
+ '[Sylphx] BaaS routing target is required for @sylphx/sdk/nextjs. Set SYLPHX_SECRET_URL="sylphx://sk_<env>_<hex>@<tenant>.api.sylphx.com" or SYLPHX_URL="sylphx://<credential>@<tenant>.api.sylphx.com" or pass platformUrl explicitly. New-format sk_* credentials do not carry routing material.'
437
+ );
438
+ }
439
+ function resolveNextjsSecretKey(options) {
440
+ if (options.explicitSecretKey?.trim()) return options.explicitSecretKey.trim();
441
+ const fromExplicitSecretUrl = secretKeyFromConnectionUrl(options.explicitSecretUrl);
442
+ if (fromExplicitSecretUrl) return fromExplicitSecretUrl;
443
+ const fromSecretUrl = secretKeyFromConnectionUrl(process.env[ENV_SECRET_URL]);
444
+ if (fromSecretUrl) return fromSecretUrl;
445
+ const fromGenericUrl = secretKeyFromConnectionUrl(process.env[ENV_URL]);
446
+ if (fromGenericUrl) return fromGenericUrl;
447
+ return process.env[ENV_SECRET_KEY]?.trim() || null;
448
+ }
449
+
299
450
  // src/nextjs/middleware.ts
300
451
  function isTokenResponse(data) {
301
452
  return typeof data === "object" && data !== null && "accessToken" in data && "refreshToken" in data && "user" in data && typeof data.accessToken === "string" && typeof data.refreshToken === "string";
@@ -452,10 +603,13 @@ function createSylphxMiddleware(userConfig = {}) {
452
603
  let secretKey = null;
453
604
  function resolveSecretKey() {
454
605
  if (secretKey) return secretKey;
455
- const rawSecretKey = userConfig.secretKey || process.env.SYLPHX_SECRET_KEY;
606
+ const rawSecretKey = resolveNextjsSecretKey({
607
+ explicitSecretKey: userConfig.secretKey,
608
+ explicitSecretUrl: userConfig.secretUrl
609
+ });
456
610
  if (!rawSecretKey) {
457
611
  throw new Error(
458
- "[Sylphx] Secret key is required.\nEither pass secretKey in config or set SYLPHX_SECRET_KEY env var.\nGet your key from Sylphx Console \u2192 API Keys."
612
+ "[Sylphx] Server connection URL is required.\nEither pass secretUrl in config or set SYLPHX_SECRET_URL env var.\nLegacy apps may pass secretKey or set SYLPHX_SECRET_KEY during migration."
459
613
  );
460
614
  }
461
615
  secretKey = validateAndSanitizeSecretKey(rawSecretKey);
@@ -466,12 +620,11 @@ function createSylphxMiddleware(userConfig = {}) {
466
620
  let cookieNames;
467
621
  function resolveConfig() {
468
622
  const key = resolveSecretKey();
469
- platformUrl = (() => {
470
- if (userConfig.platformUrl) return userConfig.platformUrl.trim();
471
- const parts = key.split("_");
472
- const ref = parts.length === 4 ? parts[2] : null;
473
- return ref ? `https://${ref}.${DEFAULT_SDK_API_HOST}` : `https://${DEFAULT_SDK_API_HOST}`;
474
- })();
623
+ platformUrl = resolveNextjsPlatformUrl({
624
+ explicitPlatformUrl: userConfig.platformUrl,
625
+ secretKey: key,
626
+ secretUrl: userConfig.secretUrl
627
+ });
475
628
  namespace = getCookieNamespace(key);
476
629
  cookieNames = getCookieNames(namespace);
477
630
  }
@@ -1835,27 +1988,40 @@ function isTokenResponse2(data) {
1835
1988
  }
1836
1989
  var serverConfig = null;
1837
1990
  function configureServer(config) {
1838
- const secretKey = validateAndSanitizeSecretKey(config.secretKey);
1991
+ const rawSecretKey = resolveNextjsSecretKey({
1992
+ explicitSecretKey: config.secretKey,
1993
+ explicitSecretUrl: config.secretUrl
1994
+ });
1995
+ if (!rawSecretKey) {
1996
+ throw new Error(
1997
+ "[Sylphx] Server credential is required. Set SYLPHX_SECRET_URL or pass secretUrl/secretKey to configureServer()."
1998
+ );
1999
+ }
2000
+ const secretKey = validateAndSanitizeSecretKey(rawSecretKey);
1839
2001
  serverConfig = {
1840
2002
  secretKey,
1841
- platformUrl: config.platformUrl?.trim() || `https://${DEFAULT_SDK_API_HOST}`.trim()
2003
+ platformUrl: resolveNextjsPlatformUrl({
2004
+ explicitPlatformUrl: config.platformUrl,
2005
+ secretKey,
2006
+ secretUrl: config.secretUrl
2007
+ })
1842
2008
  };
1843
2009
  }
1844
2010
  function getConfig() {
1845
2011
  if (serverConfig) {
1846
2012
  return serverConfig;
1847
2013
  }
1848
- const rawSecretKey = process.env.SYLPHX_SECRET_KEY;
2014
+ const rawSecretKey = resolveNextjsSecretKey({});
1849
2015
  if (!rawSecretKey) {
1850
2016
  return null;
1851
2017
  }
1852
2018
  try {
1853
2019
  const secretKey = validateAndSanitizeSecretKey(rawSecretKey);
1854
- const platformUrl = `https://${DEFAULT_SDK_API_HOST}`.trim();
2020
+ const platformUrl = resolveNextjsPlatformUrl({ secretKey });
1855
2021
  serverConfig = { secretKey, platformUrl };
1856
2022
  return serverConfig;
1857
2023
  } catch (error) {
1858
- console.warn("[Sylphx] Invalid SYLPHX_SECRET_KEY format:", error);
2024
+ console.warn("[Sylphx] Invalid @sylphx/sdk/nextjs server configuration:", error);
1859
2025
  return null;
1860
2026
  }
1861
2027
  }
@@ -1915,7 +2081,7 @@ async function currentUserId() {
1915
2081
  async function handleCallback2(code) {
1916
2082
  const config = getConfig();
1917
2083
  if (!config) {
1918
- throw new Error("Sylphx SDK not configured. Set SYLPHX_SECRET_KEY environment variable.");
2084
+ throw new Error("Sylphx SDK not configured. Set SYLPHX_SECRET_URL environment variable.");
1919
2085
  }
1920
2086
  const response = await fetch(`${config.platformUrl}/v1/auth/token`, {
1921
2087
  method: "POST",
@@ -1968,7 +2134,7 @@ async function syncAuthToCookies(tokens) {
1968
2134
  function getAuthorizationUrl(options) {
1969
2135
  const config = getConfig();
1970
2136
  if (!config) {
1971
- throw new Error("Sylphx SDK not configured. Set SYLPHX_SECRET_KEY environment variable.");
2137
+ throw new Error("Sylphx SDK not configured. Set SYLPHX_SECRET_URL environment variable.");
1972
2138
  }
1973
2139
  const rawClientId = options?.appId || process.env.NEXT_PUBLIC_SYLPHX_APP_ID;
1974
2140
  if (!rawClientId) {