@scalemule/nextjs 0.0.11 → 0.0.13
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.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +27 -8
- package/dist/index.mjs +27 -8
- package/dist/server/index.d.mts +47 -1
- package/dist/server/index.d.ts +47 -1
- package/dist/server/index.js +91 -44
- package/dist/server/index.mjs +92 -46
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -26,6 +26,8 @@ interface ScaleMuleContextValue {
|
|
|
26
26
|
publishableKey?: string;
|
|
27
27
|
/** Gateway URL for direct API calls */
|
|
28
28
|
gatewayUrl?: string;
|
|
29
|
+
/** Server-evaluated flag values to bootstrap the client (eliminates loading flash) */
|
|
30
|
+
bootstrapFlags?: Record<string, unknown>;
|
|
29
31
|
}
|
|
30
32
|
interface ScaleMuleProviderProps extends ScaleMuleConfig {
|
|
31
33
|
children: ReactNode;
|
|
@@ -35,8 +37,10 @@ interface ScaleMuleProviderProps extends ScaleMuleConfig {
|
|
|
35
37
|
onLogout?: () => void;
|
|
36
38
|
/** Called on authentication error */
|
|
37
39
|
onAuthError?: (error: ApiError) => void;
|
|
40
|
+
/** Server-evaluated flag values to bootstrap the client (eliminates loading flash) */
|
|
41
|
+
bootstrapFlags?: Record<string, unknown>;
|
|
38
42
|
}
|
|
39
|
-
declare function ScaleMuleProvider({ apiKey, applicationId, environment, gatewayUrl, debug, storage, analyticsProxyUrl, authProxyUrl, publishableKey, children, onLogin, onLogout, onAuthError, }: ScaleMuleProviderProps): react_jsx_runtime.JSX.Element;
|
|
43
|
+
declare function ScaleMuleProvider({ apiKey, applicationId, environment, gatewayUrl, debug, storage, analyticsProxyUrl, authProxyUrl, publishableKey, children, onLogin, onLogout, onAuthError, bootstrapFlags, }: ScaleMuleProviderProps): react_jsx_runtime.JSX.Element;
|
|
40
44
|
declare function useScaleMule(): ScaleMuleContextValue;
|
|
41
45
|
declare function useScaleMuleClient(): ScaleMuleClient;
|
|
42
46
|
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ interface ScaleMuleContextValue {
|
|
|
26
26
|
publishableKey?: string;
|
|
27
27
|
/** Gateway URL for direct API calls */
|
|
28
28
|
gatewayUrl?: string;
|
|
29
|
+
/** Server-evaluated flag values to bootstrap the client (eliminates loading flash) */
|
|
30
|
+
bootstrapFlags?: Record<string, unknown>;
|
|
29
31
|
}
|
|
30
32
|
interface ScaleMuleProviderProps extends ScaleMuleConfig {
|
|
31
33
|
children: ReactNode;
|
|
@@ -35,8 +37,10 @@ interface ScaleMuleProviderProps extends ScaleMuleConfig {
|
|
|
35
37
|
onLogout?: () => void;
|
|
36
38
|
/** Called on authentication error */
|
|
37
39
|
onAuthError?: (error: ApiError) => void;
|
|
40
|
+
/** Server-evaluated flag values to bootstrap the client (eliminates loading flash) */
|
|
41
|
+
bootstrapFlags?: Record<string, unknown>;
|
|
38
42
|
}
|
|
39
|
-
declare function ScaleMuleProvider({ apiKey, applicationId, environment, gatewayUrl, debug, storage, analyticsProxyUrl, authProxyUrl, publishableKey, children, onLogin, onLogout, onAuthError, }: ScaleMuleProviderProps): react_jsx_runtime.JSX.Element;
|
|
43
|
+
declare function ScaleMuleProvider({ apiKey, applicationId, environment, gatewayUrl, debug, storage, analyticsProxyUrl, authProxyUrl, publishableKey, children, onLogin, onLogout, onAuthError, bootstrapFlags, }: ScaleMuleProviderProps): react_jsx_runtime.JSX.Element;
|
|
40
44
|
declare function useScaleMule(): ScaleMuleContextValue;
|
|
41
45
|
declare function useScaleMuleClient(): ScaleMuleClient;
|
|
42
46
|
|
package/dist/index.js
CHANGED
|
@@ -817,7 +817,8 @@ function ScaleMuleProvider({
|
|
|
817
817
|
children,
|
|
818
818
|
onLogin,
|
|
819
819
|
onLogout,
|
|
820
|
-
onAuthError
|
|
820
|
+
onAuthError,
|
|
821
|
+
bootstrapFlags
|
|
821
822
|
}) {
|
|
822
823
|
const [user, setUser] = react.useState(null);
|
|
823
824
|
const [initializing, setInitializing] = react.useState(true);
|
|
@@ -929,9 +930,10 @@ function ScaleMuleProvider({
|
|
|
929
930
|
analyticsProxyUrl,
|
|
930
931
|
authProxyUrl,
|
|
931
932
|
publishableKey,
|
|
932
|
-
gatewayUrl: gatewayUrl || (environment === "dev" ? "https://api-dev.scalemule.com" : "https://api.scalemule.com")
|
|
933
|
+
gatewayUrl: gatewayUrl || (environment === "dev" ? "https://api-dev.scalemule.com" : "https://api.scalemule.com"),
|
|
934
|
+
bootstrapFlags
|
|
933
935
|
}),
|
|
934
|
-
[client, user, handleSetUser, initializing, error, analyticsProxyUrl, authProxyUrl, publishableKey, gatewayUrl, environment]
|
|
936
|
+
[client, user, handleSetUser, initializing, error, analyticsProxyUrl, authProxyUrl, publishableKey, gatewayUrl, environment, bootstrapFlags]
|
|
935
937
|
);
|
|
936
938
|
return /* @__PURE__ */ jsxRuntime.jsx(ScaleMuleContext.Provider, { value, children });
|
|
937
939
|
}
|
|
@@ -2851,9 +2853,20 @@ function useFeatureFlags(options = {}) {
|
|
|
2851
2853
|
keys,
|
|
2852
2854
|
enabled = true
|
|
2853
2855
|
} = options;
|
|
2854
|
-
const { client, publishableKey, gatewayUrl } = useScaleMule();
|
|
2855
|
-
const
|
|
2856
|
-
|
|
2856
|
+
const { client, publishableKey, gatewayUrl, bootstrapFlags } = useScaleMule();
|
|
2857
|
+
const initialFlags = react.useMemo(() => {
|
|
2858
|
+
if (!bootstrapFlags) return {};
|
|
2859
|
+
const result = {};
|
|
2860
|
+
for (const [key, value] of Object.entries(bootstrapFlags)) {
|
|
2861
|
+
if (value && typeof value === "object" && "flag_key" in value) {
|
|
2862
|
+
result[key] = value;
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
return result;
|
|
2866
|
+
}, [bootstrapFlags]);
|
|
2867
|
+
const hasBootstrap = Object.keys(initialFlags).length > 0;
|
|
2868
|
+
const [flags, setFlags] = react.useState(initialFlags);
|
|
2869
|
+
const [loading, setLoading] = react.useState(enabled && !hasBootstrap);
|
|
2857
2870
|
const [error, setError] = react.useState(null);
|
|
2858
2871
|
const contextRef = react.useRef(context);
|
|
2859
2872
|
const keysKey = react.useMemo(() => keys && keys.length > 0 ? [...keys].sort().join("|") : "", [keys]);
|
|
@@ -2895,9 +2908,15 @@ function useFeatureFlags(options = {}) {
|
|
|
2895
2908
|
setLoading(false);
|
|
2896
2909
|
}
|
|
2897
2910
|
}, [client, enabled, environment, keys, keysKey, publishableKey, gatewayUrl]);
|
|
2911
|
+
const bootstrapCoversKeys = react.useMemo(() => {
|
|
2912
|
+
if (!hasBootstrap || !keys || keys.length === 0) return false;
|
|
2913
|
+
return keys.every((k) => k in initialFlags);
|
|
2914
|
+
}, [hasBootstrap, keys, initialFlags]);
|
|
2898
2915
|
react.useEffect(() => {
|
|
2899
|
-
|
|
2900
|
-
|
|
2916
|
+
if (!bootstrapCoversKeys) {
|
|
2917
|
+
void refresh();
|
|
2918
|
+
}
|
|
2919
|
+
}, [refresh, bootstrapCoversKeys]);
|
|
2901
2920
|
const isEnabled = react.useCallback(
|
|
2902
2921
|
(flagKey, fallback = false) => {
|
|
2903
2922
|
const evaluation = flags[flagKey];
|
package/dist/index.mjs
CHANGED
|
@@ -815,7 +815,8 @@ function ScaleMuleProvider({
|
|
|
815
815
|
children,
|
|
816
816
|
onLogin,
|
|
817
817
|
onLogout,
|
|
818
|
-
onAuthError
|
|
818
|
+
onAuthError,
|
|
819
|
+
bootstrapFlags
|
|
819
820
|
}) {
|
|
820
821
|
const [user, setUser] = useState(null);
|
|
821
822
|
const [initializing, setInitializing] = useState(true);
|
|
@@ -927,9 +928,10 @@ function ScaleMuleProvider({
|
|
|
927
928
|
analyticsProxyUrl,
|
|
928
929
|
authProxyUrl,
|
|
929
930
|
publishableKey,
|
|
930
|
-
gatewayUrl: gatewayUrl || (environment === "dev" ? "https://api-dev.scalemule.com" : "https://api.scalemule.com")
|
|
931
|
+
gatewayUrl: gatewayUrl || (environment === "dev" ? "https://api-dev.scalemule.com" : "https://api.scalemule.com"),
|
|
932
|
+
bootstrapFlags
|
|
931
933
|
}),
|
|
932
|
-
[client, user, handleSetUser, initializing, error, analyticsProxyUrl, authProxyUrl, publishableKey, gatewayUrl, environment]
|
|
934
|
+
[client, user, handleSetUser, initializing, error, analyticsProxyUrl, authProxyUrl, publishableKey, gatewayUrl, environment, bootstrapFlags]
|
|
933
935
|
);
|
|
934
936
|
return /* @__PURE__ */ jsx(ScaleMuleContext.Provider, { value, children });
|
|
935
937
|
}
|
|
@@ -2849,9 +2851,20 @@ function useFeatureFlags(options = {}) {
|
|
|
2849
2851
|
keys,
|
|
2850
2852
|
enabled = true
|
|
2851
2853
|
} = options;
|
|
2852
|
-
const { client, publishableKey, gatewayUrl } = useScaleMule();
|
|
2853
|
-
const
|
|
2854
|
-
|
|
2854
|
+
const { client, publishableKey, gatewayUrl, bootstrapFlags } = useScaleMule();
|
|
2855
|
+
const initialFlags = useMemo(() => {
|
|
2856
|
+
if (!bootstrapFlags) return {};
|
|
2857
|
+
const result = {};
|
|
2858
|
+
for (const [key, value] of Object.entries(bootstrapFlags)) {
|
|
2859
|
+
if (value && typeof value === "object" && "flag_key" in value) {
|
|
2860
|
+
result[key] = value;
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
return result;
|
|
2864
|
+
}, [bootstrapFlags]);
|
|
2865
|
+
const hasBootstrap = Object.keys(initialFlags).length > 0;
|
|
2866
|
+
const [flags, setFlags] = useState(initialFlags);
|
|
2867
|
+
const [loading, setLoading] = useState(enabled && !hasBootstrap);
|
|
2855
2868
|
const [error, setError] = useState(null);
|
|
2856
2869
|
const contextRef = useRef(context);
|
|
2857
2870
|
const keysKey = useMemo(() => keys && keys.length > 0 ? [...keys].sort().join("|") : "", [keys]);
|
|
@@ -2893,9 +2906,15 @@ function useFeatureFlags(options = {}) {
|
|
|
2893
2906
|
setLoading(false);
|
|
2894
2907
|
}
|
|
2895
2908
|
}, [client, enabled, environment, keys, keysKey, publishableKey, gatewayUrl]);
|
|
2909
|
+
const bootstrapCoversKeys = useMemo(() => {
|
|
2910
|
+
if (!hasBootstrap || !keys || keys.length === 0) return false;
|
|
2911
|
+
return keys.every((k) => k in initialFlags);
|
|
2912
|
+
}, [hasBootstrap, keys, initialFlags]);
|
|
2896
2913
|
useEffect(() => {
|
|
2897
|
-
|
|
2898
|
-
|
|
2914
|
+
if (!bootstrapCoversKeys) {
|
|
2915
|
+
void refresh();
|
|
2916
|
+
}
|
|
2917
|
+
}, [refresh, bootstrapCoversKeys]);
|
|
2899
2918
|
const isEnabled = useCallback(
|
|
2900
2919
|
(flagKey, fallback = false) => {
|
|
2901
2920
|
const evaluation = flags[flagKey];
|
package/dist/server/index.d.mts
CHANGED
|
@@ -433,6 +433,52 @@ declare function apiHandler(handler: HandlerFn, options?: HandlerOptions): (requ
|
|
|
433
433
|
params: Promise<Record<string, string | string[]>>;
|
|
434
434
|
}) => Promise<Response>;
|
|
435
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Server-side flag bootstrapping for Next.js layouts.
|
|
438
|
+
*
|
|
439
|
+
* Evaluates feature flags on the server during SSR so the client
|
|
440
|
+
* has correct values on first render -- no loading flash, no race condition.
|
|
441
|
+
*
|
|
442
|
+
* Results are cached in-memory with a configurable TTL (default: 60s) to
|
|
443
|
+
* prevent excessive API calls. Since feature flags change infrequently,
|
|
444
|
+
* a short cache window is fine and avoids hammering the flags service on
|
|
445
|
+
* every page render.
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* ```ts
|
|
449
|
+
* // app/layout.tsx
|
|
450
|
+
* import { getBootstrapFlags } from '@scalemule/nextjs/server'
|
|
451
|
+
*
|
|
452
|
+
* export default async function RootLayout({ children }) {
|
|
453
|
+
* const bootstrapFlags = await getBootstrapFlags(['analytics.tracking_enabled'])
|
|
454
|
+
* return (
|
|
455
|
+
* <Providers bootstrapFlags={bootstrapFlags}>
|
|
456
|
+
* {children}
|
|
457
|
+
* </Providers>
|
|
458
|
+
* )
|
|
459
|
+
* }
|
|
460
|
+
* ```
|
|
461
|
+
*/
|
|
462
|
+
/**
|
|
463
|
+
* Evaluate feature flags server-side for bootstrapping the client provider.
|
|
464
|
+
*
|
|
465
|
+
* Extracts the client IP from request headers (x-forwarded-for, x-real-ip)
|
|
466
|
+
* and passes it as ip_address in the evaluation context so IP-based targeting
|
|
467
|
+
* rules work correctly.
|
|
468
|
+
*
|
|
469
|
+
* Results are cached in-memory for `cacheTtlMs` milliseconds (default: 60s).
|
|
470
|
+
* Pass `cacheTtlMs: 0` to disable caching.
|
|
471
|
+
*
|
|
472
|
+
* Returns a Record that can be passed directly to ScaleMuleProvider's
|
|
473
|
+
* bootstrapFlags prop.
|
|
474
|
+
*
|
|
475
|
+
* @param flagKeys - Array of flag keys to evaluate
|
|
476
|
+
* @param environment - Environment name (default: 'prod')
|
|
477
|
+
* @param extraContext - Additional context attributes to include
|
|
478
|
+
* @param cacheTtlMs - Cache TTL in milliseconds (default: 60000). Set to 0 to disable.
|
|
479
|
+
*/
|
|
480
|
+
declare function getBootstrapFlags(flagKeys: string[], environment?: string, extraContext?: Record<string, unknown>, cacheTtlMs?: number): Promise<Record<string, unknown>>;
|
|
481
|
+
|
|
436
482
|
/**
|
|
437
483
|
* Next.js Middleware Helpers
|
|
438
484
|
*
|
|
@@ -868,4 +914,4 @@ declare function invalidateBundleCache(key?: string): void;
|
|
|
868
914
|
*/
|
|
869
915
|
declare function prefetchBundles(keys: string[]): Promise<void>;
|
|
870
916
|
|
|
871
|
-
export { type AnalyticsRoutesConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, type HandlerContext, type HandlerOptions, type MySqlBundle, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PostgresBundle, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, ScaleMuleError, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, prefetchBundles, prefetchSecrets, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
|
917
|
+
export { type AnalyticsRoutesConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, type HandlerContext, type HandlerOptions, type MySqlBundle, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PostgresBundle, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, ScaleMuleError, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, prefetchBundles, prefetchSecrets, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -433,6 +433,52 @@ declare function apiHandler(handler: HandlerFn, options?: HandlerOptions): (requ
|
|
|
433
433
|
params: Promise<Record<string, string | string[]>>;
|
|
434
434
|
}) => Promise<Response>;
|
|
435
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Server-side flag bootstrapping for Next.js layouts.
|
|
438
|
+
*
|
|
439
|
+
* Evaluates feature flags on the server during SSR so the client
|
|
440
|
+
* has correct values on first render -- no loading flash, no race condition.
|
|
441
|
+
*
|
|
442
|
+
* Results are cached in-memory with a configurable TTL (default: 60s) to
|
|
443
|
+
* prevent excessive API calls. Since feature flags change infrequently,
|
|
444
|
+
* a short cache window is fine and avoids hammering the flags service on
|
|
445
|
+
* every page render.
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* ```ts
|
|
449
|
+
* // app/layout.tsx
|
|
450
|
+
* import { getBootstrapFlags } from '@scalemule/nextjs/server'
|
|
451
|
+
*
|
|
452
|
+
* export default async function RootLayout({ children }) {
|
|
453
|
+
* const bootstrapFlags = await getBootstrapFlags(['analytics.tracking_enabled'])
|
|
454
|
+
* return (
|
|
455
|
+
* <Providers bootstrapFlags={bootstrapFlags}>
|
|
456
|
+
* {children}
|
|
457
|
+
* </Providers>
|
|
458
|
+
* )
|
|
459
|
+
* }
|
|
460
|
+
* ```
|
|
461
|
+
*/
|
|
462
|
+
/**
|
|
463
|
+
* Evaluate feature flags server-side for bootstrapping the client provider.
|
|
464
|
+
*
|
|
465
|
+
* Extracts the client IP from request headers (x-forwarded-for, x-real-ip)
|
|
466
|
+
* and passes it as ip_address in the evaluation context so IP-based targeting
|
|
467
|
+
* rules work correctly.
|
|
468
|
+
*
|
|
469
|
+
* Results are cached in-memory for `cacheTtlMs` milliseconds (default: 60s).
|
|
470
|
+
* Pass `cacheTtlMs: 0` to disable caching.
|
|
471
|
+
*
|
|
472
|
+
* Returns a Record that can be passed directly to ScaleMuleProvider's
|
|
473
|
+
* bootstrapFlags prop.
|
|
474
|
+
*
|
|
475
|
+
* @param flagKeys - Array of flag keys to evaluate
|
|
476
|
+
* @param environment - Environment name (default: 'prod')
|
|
477
|
+
* @param extraContext - Additional context attributes to include
|
|
478
|
+
* @param cacheTtlMs - Cache TTL in milliseconds (default: 60000). Set to 0 to disable.
|
|
479
|
+
*/
|
|
480
|
+
declare function getBootstrapFlags(flagKeys: string[], environment?: string, extraContext?: Record<string, unknown>, cacheTtlMs?: number): Promise<Record<string, unknown>>;
|
|
481
|
+
|
|
436
482
|
/**
|
|
437
483
|
* Next.js Middleware Helpers
|
|
438
484
|
*
|
|
@@ -868,4 +914,4 @@ declare function invalidateBundleCache(key?: string): void;
|
|
|
868
914
|
*/
|
|
869
915
|
declare function prefetchBundles(keys: string[]): Promise<void>;
|
|
870
916
|
|
|
871
|
-
export { type AnalyticsRoutesConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, type HandlerContext, type HandlerOptions, type MySqlBundle, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PostgresBundle, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, ScaleMuleError, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, prefetchBundles, prefetchSecrets, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
|
917
|
+
export { type AnalyticsRoutesConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, type HandlerContext, type HandlerOptions, type MySqlBundle, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PostgresBundle, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, ScaleMuleError, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, prefetchBundles, prefetchSecrets, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
package/dist/server/index.js
CHANGED
|
@@ -28,40 +28,40 @@ function validateIP(ip) {
|
|
|
28
28
|
return void 0;
|
|
29
29
|
}
|
|
30
30
|
function extractClientContext(request) {
|
|
31
|
-
const
|
|
31
|
+
const headers2 = request.headers;
|
|
32
32
|
let ip;
|
|
33
|
-
const cfConnectingIp =
|
|
33
|
+
const cfConnectingIp = headers2.get("cf-connecting-ip");
|
|
34
34
|
if (cfConnectingIp) {
|
|
35
35
|
ip = validateIP(cfConnectingIp);
|
|
36
36
|
}
|
|
37
37
|
if (!ip) {
|
|
38
|
-
const doConnectingIp =
|
|
38
|
+
const doConnectingIp = headers2.get("do-connecting-ip");
|
|
39
39
|
if (doConnectingIp) {
|
|
40
40
|
ip = validateIP(doConnectingIp);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
if (!ip) {
|
|
44
|
-
const realIp =
|
|
44
|
+
const realIp = headers2.get("x-real-ip");
|
|
45
45
|
if (realIp) {
|
|
46
46
|
ip = validateIP(realIp);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
if (!ip) {
|
|
50
|
-
const forwardedFor =
|
|
50
|
+
const forwardedFor = headers2.get("x-forwarded-for");
|
|
51
51
|
if (forwardedFor) {
|
|
52
52
|
const firstIp = forwardedFor.split(",")[0]?.trim();
|
|
53
53
|
ip = validateIP(firstIp);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
if (!ip) {
|
|
57
|
-
const vercelForwarded =
|
|
57
|
+
const vercelForwarded = headers2.get("x-vercel-forwarded-for");
|
|
58
58
|
if (vercelForwarded) {
|
|
59
59
|
const firstIp = vercelForwarded.split(",")[0]?.trim();
|
|
60
60
|
ip = validateIP(firstIp);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
if (!ip) {
|
|
64
|
-
const trueClientIp =
|
|
64
|
+
const trueClientIp = headers2.get("true-client-ip");
|
|
65
65
|
if (trueClientIp) {
|
|
66
66
|
ip = validateIP(trueClientIp);
|
|
67
67
|
}
|
|
@@ -69,9 +69,9 @@ function extractClientContext(request) {
|
|
|
69
69
|
if (!ip && request.ip) {
|
|
70
70
|
ip = validateIP(request.ip);
|
|
71
71
|
}
|
|
72
|
-
const userAgent =
|
|
73
|
-
const deviceFingerprint =
|
|
74
|
-
const referrer =
|
|
72
|
+
const userAgent = headers2.get("user-agent") || void 0;
|
|
73
|
+
const deviceFingerprint = headers2.get("x-device-fingerprint") || void 0;
|
|
74
|
+
const referrer = headers2.get("referer") || void 0;
|
|
75
75
|
return {
|
|
76
76
|
ip,
|
|
77
77
|
userAgent,
|
|
@@ -80,9 +80,9 @@ function extractClientContext(request) {
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
function extractClientContextFromReq(req) {
|
|
83
|
-
const
|
|
83
|
+
const headers2 = req.headers;
|
|
84
84
|
const getHeader = (name) => {
|
|
85
|
-
const value =
|
|
85
|
+
const value = headers2[name.toLowerCase()];
|
|
86
86
|
if (Array.isArray(value)) {
|
|
87
87
|
return value[0];
|
|
88
88
|
}
|
|
@@ -139,24 +139,24 @@ function extractClientContextFromReq(req) {
|
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
141
|
function buildClientContextHeaders(context) {
|
|
142
|
-
const
|
|
142
|
+
const headers2 = {};
|
|
143
143
|
if (!context) {
|
|
144
|
-
return
|
|
144
|
+
return headers2;
|
|
145
145
|
}
|
|
146
146
|
if (context.ip) {
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
headers2["x-sm-forwarded-client-ip"] = context.ip;
|
|
148
|
+
headers2["X-Client-IP"] = context.ip;
|
|
149
149
|
}
|
|
150
150
|
if (context.userAgent) {
|
|
151
|
-
|
|
151
|
+
headers2["X-Client-User-Agent"] = context.userAgent;
|
|
152
152
|
}
|
|
153
153
|
if (context.deviceFingerprint) {
|
|
154
|
-
|
|
154
|
+
headers2["X-Client-Device-Fingerprint"] = context.deviceFingerprint;
|
|
155
155
|
}
|
|
156
156
|
if (context.referrer) {
|
|
157
|
-
|
|
157
|
+
headers2["X-Client-Referrer"] = context.referrer;
|
|
158
158
|
}
|
|
159
|
-
return
|
|
159
|
+
return headers2;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
// src/server/client.ts
|
|
@@ -541,7 +541,7 @@ var ScaleMuleServer = class {
|
|
|
541
541
|
formData.append("file", blob, file.filename);
|
|
542
542
|
formData.append("sm_user_id", userId);
|
|
543
543
|
const url = `${this.gatewayUrl}/v1/storage/upload`;
|
|
544
|
-
const
|
|
544
|
+
const headers2 = {
|
|
545
545
|
"x-api-key": this.apiKey,
|
|
546
546
|
"x-user-id": userId,
|
|
547
547
|
...buildClientContextHeaders(options?.clientContext)
|
|
@@ -552,7 +552,7 @@ var ScaleMuleServer = class {
|
|
|
552
552
|
try {
|
|
553
553
|
const response = await fetch(url, {
|
|
554
554
|
method: "POST",
|
|
555
|
-
headers,
|
|
555
|
+
headers: headers2,
|
|
556
556
|
body: formData
|
|
557
557
|
});
|
|
558
558
|
const text = await response.text();
|
|
@@ -763,14 +763,14 @@ var ScaleMuleServer = class {
|
|
|
763
763
|
*/
|
|
764
764
|
async request(method, path, options = {}) {
|
|
765
765
|
const url = `${this.gatewayUrl}${path}`;
|
|
766
|
-
const
|
|
766
|
+
const headers2 = {
|
|
767
767
|
"x-api-key": this.apiKey,
|
|
768
768
|
"Content-Type": "application/json",
|
|
769
769
|
// Forward client context headers if provided
|
|
770
770
|
...buildClientContextHeaders(options.clientContext)
|
|
771
771
|
};
|
|
772
772
|
if (options.sessionToken) {
|
|
773
|
-
|
|
773
|
+
headers2["Authorization"] = `Bearer ${options.sessionToken}`;
|
|
774
774
|
}
|
|
775
775
|
if (this.debug) {
|
|
776
776
|
console.log(`[ScaleMule Server] ${method} ${path}`);
|
|
@@ -781,7 +781,7 @@ var ScaleMuleServer = class {
|
|
|
781
781
|
try {
|
|
782
782
|
const response = await fetch(url, {
|
|
783
783
|
method,
|
|
784
|
-
headers,
|
|
784
|
+
headers: headers2,
|
|
785
785
|
body: options.body ? JSON.stringify(options.body) : void 0
|
|
786
786
|
});
|
|
787
787
|
const text = await response.text();
|
|
@@ -852,45 +852,45 @@ function createClearCookieHeader(name, options = {}) {
|
|
|
852
852
|
return cookie;
|
|
853
853
|
}
|
|
854
854
|
function withSession(loginResponse, responseBody, options = {}) {
|
|
855
|
-
const
|
|
856
|
-
|
|
857
|
-
|
|
855
|
+
const headers2 = new Headers();
|
|
856
|
+
headers2.set("Content-Type", "application/json");
|
|
857
|
+
headers2.append(
|
|
858
858
|
"Set-Cookie",
|
|
859
859
|
createCookieHeader(SESSION_COOKIE_NAME, loginResponse.session_token, options)
|
|
860
860
|
);
|
|
861
|
-
|
|
861
|
+
headers2.append(
|
|
862
862
|
"Set-Cookie",
|
|
863
863
|
createCookieHeader(USER_ID_COOKIE_NAME, loginResponse.user.id, options)
|
|
864
864
|
);
|
|
865
865
|
return new Response(JSON.stringify({ success: true, data: responseBody }), {
|
|
866
866
|
status: 200,
|
|
867
|
-
headers
|
|
867
|
+
headers: headers2
|
|
868
868
|
});
|
|
869
869
|
}
|
|
870
870
|
function withRefreshedSession(sessionToken, userId, responseBody, options = {}) {
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
871
|
+
const headers2 = new Headers();
|
|
872
|
+
headers2.set("Content-Type", "application/json");
|
|
873
|
+
headers2.append(
|
|
874
874
|
"Set-Cookie",
|
|
875
875
|
createCookieHeader(SESSION_COOKIE_NAME, sessionToken, options)
|
|
876
876
|
);
|
|
877
|
-
|
|
877
|
+
headers2.append(
|
|
878
878
|
"Set-Cookie",
|
|
879
879
|
createCookieHeader(USER_ID_COOKIE_NAME, userId, options)
|
|
880
880
|
);
|
|
881
881
|
return new Response(JSON.stringify({ success: true, data: responseBody }), {
|
|
882
882
|
status: 200,
|
|
883
|
-
headers
|
|
883
|
+
headers: headers2
|
|
884
884
|
});
|
|
885
885
|
}
|
|
886
886
|
function clearSession(responseBody, options = {}, status = 200) {
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
887
|
+
const headers2 = new Headers();
|
|
888
|
+
headers2.set("Content-Type", "application/json");
|
|
889
|
+
headers2.append("Set-Cookie", createClearCookieHeader(SESSION_COOKIE_NAME, options));
|
|
890
|
+
headers2.append("Set-Cookie", createClearCookieHeader(USER_ID_COOKIE_NAME, options));
|
|
891
891
|
return new Response(JSON.stringify({ success: status < 300, data: responseBody }), {
|
|
892
892
|
status,
|
|
893
|
-
headers
|
|
893
|
+
headers: headers2
|
|
894
894
|
});
|
|
895
895
|
}
|
|
896
896
|
async function getSession() {
|
|
@@ -1765,6 +1765,52 @@ function createWebhookHandler(config = {}) {
|
|
|
1765
1765
|
}
|
|
1766
1766
|
};
|
|
1767
1767
|
}
|
|
1768
|
+
var _serverClient = null;
|
|
1769
|
+
function getClient() {
|
|
1770
|
+
if (!_serverClient) {
|
|
1771
|
+
_serverClient = createServerClient();
|
|
1772
|
+
}
|
|
1773
|
+
return _serverClient;
|
|
1774
|
+
}
|
|
1775
|
+
var _flagCache = /* @__PURE__ */ new Map();
|
|
1776
|
+
var DEFAULT_CACHE_TTL_MS = 6e4;
|
|
1777
|
+
async function getBootstrapFlags(flagKeys, environment = "prod", extraContext = {}, cacheTtlMs = DEFAULT_CACHE_TTL_MS) {
|
|
1778
|
+
try {
|
|
1779
|
+
const cacheKey = [...flagKeys].sort().join("|") + ":" + environment;
|
|
1780
|
+
if (cacheTtlMs > 0) {
|
|
1781
|
+
const cached = _flagCache.get(cacheKey);
|
|
1782
|
+
if (cached && Date.now() - cached.timestamp < cacheTtlMs) {
|
|
1783
|
+
return cached.result;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
const hdrs = await headers.headers();
|
|
1787
|
+
const forwardedFor = hdrs.get("x-forwarded-for");
|
|
1788
|
+
const realIp = hdrs.get("x-real-ip") || hdrs.get("x-real-client-ip");
|
|
1789
|
+
const clientIp = realIp || (forwardedFor ? forwardedFor.split(",")[0].trim() : void 0);
|
|
1790
|
+
const context = {
|
|
1791
|
+
...extraContext
|
|
1792
|
+
};
|
|
1793
|
+
if (clientIp) {
|
|
1794
|
+
context.ip_address = clientIp;
|
|
1795
|
+
}
|
|
1796
|
+
const result = await getClient().flags.evaluateBatch(flagKeys, context, environment);
|
|
1797
|
+
const flagResult = result || {};
|
|
1798
|
+
if (cacheTtlMs > 0) {
|
|
1799
|
+
_flagCache.set(cacheKey, { result: flagResult, timestamp: Date.now() });
|
|
1800
|
+
if (_flagCache.size > 100) {
|
|
1801
|
+
const now = Date.now();
|
|
1802
|
+
for (const [key, entry] of _flagCache) {
|
|
1803
|
+
if (now - entry.timestamp > cacheTtlMs) {
|
|
1804
|
+
_flagCache.delete(key);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
return flagResult;
|
|
1810
|
+
} catch {
|
|
1811
|
+
return {};
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1768
1814
|
function globToRegex(pattern) {
|
|
1769
1815
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1770
1816
|
return new RegExp(`^${escaped}$`);
|
|
@@ -1892,14 +1938,14 @@ function clearOAuthState(response) {
|
|
|
1892
1938
|
}
|
|
1893
1939
|
|
|
1894
1940
|
// src/server/secrets.ts
|
|
1895
|
-
var
|
|
1941
|
+
var DEFAULT_CACHE_TTL_MS2 = 5 * 60 * 1e3;
|
|
1896
1942
|
var secretsCache = {};
|
|
1897
1943
|
var globalConfig = {};
|
|
1898
1944
|
function configureSecrets(config) {
|
|
1899
1945
|
globalConfig = { ...globalConfig, ...config };
|
|
1900
1946
|
}
|
|
1901
1947
|
async function getAppSecret(key) {
|
|
1902
|
-
const cacheTtl = globalConfig.cacheTtlMs ??
|
|
1948
|
+
const cacheTtl = globalConfig.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS2;
|
|
1903
1949
|
const noCache = globalConfig.noCache ?? false;
|
|
1904
1950
|
if (!noCache) {
|
|
1905
1951
|
const cached = secretsCache[key];
|
|
@@ -1951,14 +1997,14 @@ async function prefetchSecrets(keys) {
|
|
|
1951
1997
|
}
|
|
1952
1998
|
|
|
1953
1999
|
// src/server/bundles.ts
|
|
1954
|
-
var
|
|
2000
|
+
var DEFAULT_CACHE_TTL_MS3 = 5 * 60 * 1e3;
|
|
1955
2001
|
var bundlesCache = {};
|
|
1956
2002
|
var globalConfig2 = {};
|
|
1957
2003
|
function configureBundles(config) {
|
|
1958
2004
|
globalConfig2 = { ...globalConfig2, ...config };
|
|
1959
2005
|
}
|
|
1960
2006
|
async function getBundle(key, resolve = true) {
|
|
1961
|
-
const cacheTtl = globalConfig2.cacheTtlMs ??
|
|
2007
|
+
const cacheTtl = globalConfig2.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS3;
|
|
1962
2008
|
const noCache = globalConfig2.noCache ?? false;
|
|
1963
2009
|
if (!noCache) {
|
|
1964
2010
|
const cached = bundlesCache[key];
|
|
@@ -2077,6 +2123,7 @@ exports.extractClientContextFromReq = extractClientContextFromReq;
|
|
|
2077
2123
|
exports.generateCSRFToken = generateCSRFToken;
|
|
2078
2124
|
exports.getAppSecret = getAppSecret;
|
|
2079
2125
|
exports.getAppSecretOrDefault = getAppSecretOrDefault;
|
|
2126
|
+
exports.getBootstrapFlags = getBootstrapFlags;
|
|
2080
2127
|
exports.getBundle = getBundle;
|
|
2081
2128
|
exports.getCSRFToken = getCSRFToken;
|
|
2082
2129
|
exports.getMySqlBundle = getMySqlBundle;
|
package/dist/server/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cookies } from 'next/headers';
|
|
1
|
+
import { cookies, headers } from 'next/headers';
|
|
2
2
|
import { NextResponse } from 'next/server';
|
|
3
3
|
import { createHmac, timingSafeEqual } from 'crypto';
|
|
4
4
|
|
|
@@ -26,40 +26,40 @@ function validateIP(ip) {
|
|
|
26
26
|
return void 0;
|
|
27
27
|
}
|
|
28
28
|
function extractClientContext(request) {
|
|
29
|
-
const
|
|
29
|
+
const headers2 = request.headers;
|
|
30
30
|
let ip;
|
|
31
|
-
const cfConnectingIp =
|
|
31
|
+
const cfConnectingIp = headers2.get("cf-connecting-ip");
|
|
32
32
|
if (cfConnectingIp) {
|
|
33
33
|
ip = validateIP(cfConnectingIp);
|
|
34
34
|
}
|
|
35
35
|
if (!ip) {
|
|
36
|
-
const doConnectingIp =
|
|
36
|
+
const doConnectingIp = headers2.get("do-connecting-ip");
|
|
37
37
|
if (doConnectingIp) {
|
|
38
38
|
ip = validateIP(doConnectingIp);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
if (!ip) {
|
|
42
|
-
const realIp =
|
|
42
|
+
const realIp = headers2.get("x-real-ip");
|
|
43
43
|
if (realIp) {
|
|
44
44
|
ip = validateIP(realIp);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
if (!ip) {
|
|
48
|
-
const forwardedFor =
|
|
48
|
+
const forwardedFor = headers2.get("x-forwarded-for");
|
|
49
49
|
if (forwardedFor) {
|
|
50
50
|
const firstIp = forwardedFor.split(",")[0]?.trim();
|
|
51
51
|
ip = validateIP(firstIp);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
if (!ip) {
|
|
55
|
-
const vercelForwarded =
|
|
55
|
+
const vercelForwarded = headers2.get("x-vercel-forwarded-for");
|
|
56
56
|
if (vercelForwarded) {
|
|
57
57
|
const firstIp = vercelForwarded.split(",")[0]?.trim();
|
|
58
58
|
ip = validateIP(firstIp);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
if (!ip) {
|
|
62
|
-
const trueClientIp =
|
|
62
|
+
const trueClientIp = headers2.get("true-client-ip");
|
|
63
63
|
if (trueClientIp) {
|
|
64
64
|
ip = validateIP(trueClientIp);
|
|
65
65
|
}
|
|
@@ -67,9 +67,9 @@ function extractClientContext(request) {
|
|
|
67
67
|
if (!ip && request.ip) {
|
|
68
68
|
ip = validateIP(request.ip);
|
|
69
69
|
}
|
|
70
|
-
const userAgent =
|
|
71
|
-
const deviceFingerprint =
|
|
72
|
-
const referrer =
|
|
70
|
+
const userAgent = headers2.get("user-agent") || void 0;
|
|
71
|
+
const deviceFingerprint = headers2.get("x-device-fingerprint") || void 0;
|
|
72
|
+
const referrer = headers2.get("referer") || void 0;
|
|
73
73
|
return {
|
|
74
74
|
ip,
|
|
75
75
|
userAgent,
|
|
@@ -78,9 +78,9 @@ function extractClientContext(request) {
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
function extractClientContextFromReq(req) {
|
|
81
|
-
const
|
|
81
|
+
const headers2 = req.headers;
|
|
82
82
|
const getHeader = (name) => {
|
|
83
|
-
const value =
|
|
83
|
+
const value = headers2[name.toLowerCase()];
|
|
84
84
|
if (Array.isArray(value)) {
|
|
85
85
|
return value[0];
|
|
86
86
|
}
|
|
@@ -137,24 +137,24 @@ function extractClientContextFromReq(req) {
|
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
139
|
function buildClientContextHeaders(context) {
|
|
140
|
-
const
|
|
140
|
+
const headers2 = {};
|
|
141
141
|
if (!context) {
|
|
142
|
-
return
|
|
142
|
+
return headers2;
|
|
143
143
|
}
|
|
144
144
|
if (context.ip) {
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
headers2["x-sm-forwarded-client-ip"] = context.ip;
|
|
146
|
+
headers2["X-Client-IP"] = context.ip;
|
|
147
147
|
}
|
|
148
148
|
if (context.userAgent) {
|
|
149
|
-
|
|
149
|
+
headers2["X-Client-User-Agent"] = context.userAgent;
|
|
150
150
|
}
|
|
151
151
|
if (context.deviceFingerprint) {
|
|
152
|
-
|
|
152
|
+
headers2["X-Client-Device-Fingerprint"] = context.deviceFingerprint;
|
|
153
153
|
}
|
|
154
154
|
if (context.referrer) {
|
|
155
|
-
|
|
155
|
+
headers2["X-Client-Referrer"] = context.referrer;
|
|
156
156
|
}
|
|
157
|
-
return
|
|
157
|
+
return headers2;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
// src/server/client.ts
|
|
@@ -539,7 +539,7 @@ var ScaleMuleServer = class {
|
|
|
539
539
|
formData.append("file", blob, file.filename);
|
|
540
540
|
formData.append("sm_user_id", userId);
|
|
541
541
|
const url = `${this.gatewayUrl}/v1/storage/upload`;
|
|
542
|
-
const
|
|
542
|
+
const headers2 = {
|
|
543
543
|
"x-api-key": this.apiKey,
|
|
544
544
|
"x-user-id": userId,
|
|
545
545
|
...buildClientContextHeaders(options?.clientContext)
|
|
@@ -550,7 +550,7 @@ var ScaleMuleServer = class {
|
|
|
550
550
|
try {
|
|
551
551
|
const response = await fetch(url, {
|
|
552
552
|
method: "POST",
|
|
553
|
-
headers,
|
|
553
|
+
headers: headers2,
|
|
554
554
|
body: formData
|
|
555
555
|
});
|
|
556
556
|
const text = await response.text();
|
|
@@ -761,14 +761,14 @@ var ScaleMuleServer = class {
|
|
|
761
761
|
*/
|
|
762
762
|
async request(method, path, options = {}) {
|
|
763
763
|
const url = `${this.gatewayUrl}${path}`;
|
|
764
|
-
const
|
|
764
|
+
const headers2 = {
|
|
765
765
|
"x-api-key": this.apiKey,
|
|
766
766
|
"Content-Type": "application/json",
|
|
767
767
|
// Forward client context headers if provided
|
|
768
768
|
...buildClientContextHeaders(options.clientContext)
|
|
769
769
|
};
|
|
770
770
|
if (options.sessionToken) {
|
|
771
|
-
|
|
771
|
+
headers2["Authorization"] = `Bearer ${options.sessionToken}`;
|
|
772
772
|
}
|
|
773
773
|
if (this.debug) {
|
|
774
774
|
console.log(`[ScaleMule Server] ${method} ${path}`);
|
|
@@ -779,7 +779,7 @@ var ScaleMuleServer = class {
|
|
|
779
779
|
try {
|
|
780
780
|
const response = await fetch(url, {
|
|
781
781
|
method,
|
|
782
|
-
headers,
|
|
782
|
+
headers: headers2,
|
|
783
783
|
body: options.body ? JSON.stringify(options.body) : void 0
|
|
784
784
|
});
|
|
785
785
|
const text = await response.text();
|
|
@@ -850,45 +850,45 @@ function createClearCookieHeader(name, options = {}) {
|
|
|
850
850
|
return cookie;
|
|
851
851
|
}
|
|
852
852
|
function withSession(loginResponse, responseBody, options = {}) {
|
|
853
|
-
const
|
|
854
|
-
|
|
855
|
-
|
|
853
|
+
const headers2 = new Headers();
|
|
854
|
+
headers2.set("Content-Type", "application/json");
|
|
855
|
+
headers2.append(
|
|
856
856
|
"Set-Cookie",
|
|
857
857
|
createCookieHeader(SESSION_COOKIE_NAME, loginResponse.session_token, options)
|
|
858
858
|
);
|
|
859
|
-
|
|
859
|
+
headers2.append(
|
|
860
860
|
"Set-Cookie",
|
|
861
861
|
createCookieHeader(USER_ID_COOKIE_NAME, loginResponse.user.id, options)
|
|
862
862
|
);
|
|
863
863
|
return new Response(JSON.stringify({ success: true, data: responseBody }), {
|
|
864
864
|
status: 200,
|
|
865
|
-
headers
|
|
865
|
+
headers: headers2
|
|
866
866
|
});
|
|
867
867
|
}
|
|
868
868
|
function withRefreshedSession(sessionToken, userId, responseBody, options = {}) {
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
869
|
+
const headers2 = new Headers();
|
|
870
|
+
headers2.set("Content-Type", "application/json");
|
|
871
|
+
headers2.append(
|
|
872
872
|
"Set-Cookie",
|
|
873
873
|
createCookieHeader(SESSION_COOKIE_NAME, sessionToken, options)
|
|
874
874
|
);
|
|
875
|
-
|
|
875
|
+
headers2.append(
|
|
876
876
|
"Set-Cookie",
|
|
877
877
|
createCookieHeader(USER_ID_COOKIE_NAME, userId, options)
|
|
878
878
|
);
|
|
879
879
|
return new Response(JSON.stringify({ success: true, data: responseBody }), {
|
|
880
880
|
status: 200,
|
|
881
|
-
headers
|
|
881
|
+
headers: headers2
|
|
882
882
|
});
|
|
883
883
|
}
|
|
884
884
|
function clearSession(responseBody, options = {}, status = 200) {
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
885
|
+
const headers2 = new Headers();
|
|
886
|
+
headers2.set("Content-Type", "application/json");
|
|
887
|
+
headers2.append("Set-Cookie", createClearCookieHeader(SESSION_COOKIE_NAME, options));
|
|
888
|
+
headers2.append("Set-Cookie", createClearCookieHeader(USER_ID_COOKIE_NAME, options));
|
|
889
889
|
return new Response(JSON.stringify({ success: status < 300, data: responseBody }), {
|
|
890
890
|
status,
|
|
891
|
-
headers
|
|
891
|
+
headers: headers2
|
|
892
892
|
});
|
|
893
893
|
}
|
|
894
894
|
async function getSession() {
|
|
@@ -1763,6 +1763,52 @@ function createWebhookHandler(config = {}) {
|
|
|
1763
1763
|
}
|
|
1764
1764
|
};
|
|
1765
1765
|
}
|
|
1766
|
+
var _serverClient = null;
|
|
1767
|
+
function getClient() {
|
|
1768
|
+
if (!_serverClient) {
|
|
1769
|
+
_serverClient = createServerClient();
|
|
1770
|
+
}
|
|
1771
|
+
return _serverClient;
|
|
1772
|
+
}
|
|
1773
|
+
var _flagCache = /* @__PURE__ */ new Map();
|
|
1774
|
+
var DEFAULT_CACHE_TTL_MS = 6e4;
|
|
1775
|
+
async function getBootstrapFlags(flagKeys, environment = "prod", extraContext = {}, cacheTtlMs = DEFAULT_CACHE_TTL_MS) {
|
|
1776
|
+
try {
|
|
1777
|
+
const cacheKey = [...flagKeys].sort().join("|") + ":" + environment;
|
|
1778
|
+
if (cacheTtlMs > 0) {
|
|
1779
|
+
const cached = _flagCache.get(cacheKey);
|
|
1780
|
+
if (cached && Date.now() - cached.timestamp < cacheTtlMs) {
|
|
1781
|
+
return cached.result;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
const hdrs = await headers();
|
|
1785
|
+
const forwardedFor = hdrs.get("x-forwarded-for");
|
|
1786
|
+
const realIp = hdrs.get("x-real-ip") || hdrs.get("x-real-client-ip");
|
|
1787
|
+
const clientIp = realIp || (forwardedFor ? forwardedFor.split(",")[0].trim() : void 0);
|
|
1788
|
+
const context = {
|
|
1789
|
+
...extraContext
|
|
1790
|
+
};
|
|
1791
|
+
if (clientIp) {
|
|
1792
|
+
context.ip_address = clientIp;
|
|
1793
|
+
}
|
|
1794
|
+
const result = await getClient().flags.evaluateBatch(flagKeys, context, environment);
|
|
1795
|
+
const flagResult = result || {};
|
|
1796
|
+
if (cacheTtlMs > 0) {
|
|
1797
|
+
_flagCache.set(cacheKey, { result: flagResult, timestamp: Date.now() });
|
|
1798
|
+
if (_flagCache.size > 100) {
|
|
1799
|
+
const now = Date.now();
|
|
1800
|
+
for (const [key, entry] of _flagCache) {
|
|
1801
|
+
if (now - entry.timestamp > cacheTtlMs) {
|
|
1802
|
+
_flagCache.delete(key);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
return flagResult;
|
|
1808
|
+
} catch {
|
|
1809
|
+
return {};
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1766
1812
|
function globToRegex(pattern) {
|
|
1767
1813
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1768
1814
|
return new RegExp(`^${escaped}$`);
|
|
@@ -1890,14 +1936,14 @@ function clearOAuthState(response) {
|
|
|
1890
1936
|
}
|
|
1891
1937
|
|
|
1892
1938
|
// src/server/secrets.ts
|
|
1893
|
-
var
|
|
1939
|
+
var DEFAULT_CACHE_TTL_MS2 = 5 * 60 * 1e3;
|
|
1894
1940
|
var secretsCache = {};
|
|
1895
1941
|
var globalConfig = {};
|
|
1896
1942
|
function configureSecrets(config) {
|
|
1897
1943
|
globalConfig = { ...globalConfig, ...config };
|
|
1898
1944
|
}
|
|
1899
1945
|
async function getAppSecret(key) {
|
|
1900
|
-
const cacheTtl = globalConfig.cacheTtlMs ??
|
|
1946
|
+
const cacheTtl = globalConfig.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS2;
|
|
1901
1947
|
const noCache = globalConfig.noCache ?? false;
|
|
1902
1948
|
if (!noCache) {
|
|
1903
1949
|
const cached = secretsCache[key];
|
|
@@ -1949,14 +1995,14 @@ async function prefetchSecrets(keys) {
|
|
|
1949
1995
|
}
|
|
1950
1996
|
|
|
1951
1997
|
// src/server/bundles.ts
|
|
1952
|
-
var
|
|
1998
|
+
var DEFAULT_CACHE_TTL_MS3 = 5 * 60 * 1e3;
|
|
1953
1999
|
var bundlesCache = {};
|
|
1954
2000
|
var globalConfig2 = {};
|
|
1955
2001
|
function configureBundles(config) {
|
|
1956
2002
|
globalConfig2 = { ...globalConfig2, ...config };
|
|
1957
2003
|
}
|
|
1958
2004
|
async function getBundle(key, resolve = true) {
|
|
1959
|
-
const cacheTtl = globalConfig2.cacheTtlMs ??
|
|
2005
|
+
const cacheTtl = globalConfig2.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS3;
|
|
1960
2006
|
const noCache = globalConfig2.noCache ?? false;
|
|
1961
2007
|
if (!noCache) {
|
|
1962
2008
|
const cached = bundlesCache[key];
|
|
@@ -2050,4 +2096,4 @@ async function prefetchBundles(keys) {
|
|
|
2050
2096
|
await Promise.all(keys.map((key) => getBundle(key)));
|
|
2051
2097
|
}
|
|
2052
2098
|
|
|
2053
|
-
export { CSRF_COOKIE_NAME, CSRF_HEADER_NAME, OAUTH_STATE_COOKIE_NAME, SESSION_COOKIE_NAME, ScaleMuleError, ScaleMuleServer, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createServerClient, createWebhookHandler, createWebhookRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, parseWebhookEvent, prefetchBundles, prefetchSecrets, registerVideoWebhook, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, verifyWebhookSignature, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
|
2099
|
+
export { CSRF_COOKIE_NAME, CSRF_HEADER_NAME, OAUTH_STATE_COOKIE_NAME, SESSION_COOKIE_NAME, ScaleMuleError, ScaleMuleServer, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createServerClient, createWebhookHandler, createWebhookRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, parseWebhookEvent, prefetchBundles, prefetchSecrets, registerVideoWebhook, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, verifyWebhookSignature, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
package/package.json
CHANGED