@sylphx/sdk 0.8.0-rc.2 → 0.8.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/index.d.ts +33 -2
- package/dist/index.mjs +32 -5
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs/index.d.ts +17 -6
- package/dist/nextjs/index.mjs +181 -15
- package/dist/nextjs/index.mjs.map +1 -1
- package/dist/react/index.d.ts +123 -4
- package/dist/react/index.mjs +4917 -46404
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/index.d.ts +27 -3
- package/dist/server/index.mjs +2 -2
- package/dist/server/index.mjs.map +1 -1
- package/dist/web-analytics.mjs.map +1 -1
- package/package.json +1 -2
package/dist/nextjs/index.d.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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
|
-
* -
|
|
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
|
-
*
|
|
239
|
+
* secretUrl: process.env.SYLPHX_SECRET_URL!,
|
|
230
240
|
* })
|
|
231
241
|
* ```
|
|
232
242
|
*/
|
|
233
243
|
declare function configureServer(config: {
|
|
234
|
-
secretKey
|
|
244
|
+
secretKey?: string;
|
|
245
|
+
secretUrl?: string;
|
|
235
246
|
platformUrl?: string;
|
|
236
247
|
}): void;
|
|
237
248
|
/**
|
package/dist/nextjs/index.mjs
CHANGED
|
@@ -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 =
|
|
606
|
+
const rawSecretKey = resolveNextjsSecretKey({
|
|
607
|
+
explicitSecretKey: userConfig.secretKey,
|
|
608
|
+
explicitSecretUrl: userConfig.secretUrl
|
|
609
|
+
});
|
|
456
610
|
if (!rawSecretKey) {
|
|
457
611
|
throw new Error(
|
|
458
|
-
"[Sylphx]
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
|
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:
|
|
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 =
|
|
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 =
|
|
2020
|
+
const platformUrl = resolveNextjsPlatformUrl({ secretKey });
|
|
1855
2021
|
serverConfig = { secretKey, platformUrl };
|
|
1856
2022
|
return serverConfig;
|
|
1857
2023
|
} catch (error) {
|
|
1858
|
-
console.warn("[Sylphx] Invalid
|
|
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
|
|
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
|
|
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) {
|