@scalemule/nextjs 0.0.10 → 0.0.12

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 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 } = useScaleMule();
2855
- const [flags, setFlags] = react.useState({});
2856
- const [loading, setLoading] = react.useState(enabled);
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]);
@@ -2868,7 +2881,25 @@ function useFeatureFlags(options = {}) {
2868
2881
  setLoading(true);
2869
2882
  try {
2870
2883
  const payload = keys && keys.length > 0 ? { flag_keys: keys, environment, context: contextRef.current } : { environment, context: contextRef.current };
2871
- const result = keys && keys.length > 0 ? await client.post("/v1/flags/evaluate/batch", payload) : await client.post("/v1/flags/evaluate/all", payload);
2884
+ const endpoint = keys && keys.length > 0 ? "/v1/flags/evaluate/batch" : "/v1/flags/evaluate/all";
2885
+ let result;
2886
+ if (publishableKey && gatewayUrl) {
2887
+ const response = await fetch(`${gatewayUrl}${endpoint}`, {
2888
+ method: "POST",
2889
+ headers: {
2890
+ "Content-Type": "application/json",
2891
+ "x-api-key": publishableKey
2892
+ },
2893
+ body: JSON.stringify(payload)
2894
+ });
2895
+ if (!response.ok) {
2896
+ throw new Error(`Flag evaluation failed: ${response.status}`);
2897
+ }
2898
+ const json = await response.json();
2899
+ result = json.data || json || {};
2900
+ } else {
2901
+ result = await client.post(endpoint, payload);
2902
+ }
2872
2903
  setFlags(result || {});
2873
2904
  setError(null);
2874
2905
  } catch (err) {
@@ -2876,7 +2907,7 @@ function useFeatureFlags(options = {}) {
2876
2907
  } finally {
2877
2908
  setLoading(false);
2878
2909
  }
2879
- }, [client, enabled, environment, keys, keysKey]);
2910
+ }, [client, enabled, environment, keys, keysKey, publishableKey, gatewayUrl]);
2880
2911
  react.useEffect(() => {
2881
2912
  void refresh();
2882
2913
  }, [refresh]);
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 } = useScaleMule();
2853
- const [flags, setFlags] = useState({});
2854
- const [loading, setLoading] = useState(enabled);
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]);
@@ -2866,7 +2879,25 @@ function useFeatureFlags(options = {}) {
2866
2879
  setLoading(true);
2867
2880
  try {
2868
2881
  const payload = keys && keys.length > 0 ? { flag_keys: keys, environment, context: contextRef.current } : { environment, context: contextRef.current };
2869
- const result = keys && keys.length > 0 ? await client.post("/v1/flags/evaluate/batch", payload) : await client.post("/v1/flags/evaluate/all", payload);
2882
+ const endpoint = keys && keys.length > 0 ? "/v1/flags/evaluate/batch" : "/v1/flags/evaluate/all";
2883
+ let result;
2884
+ if (publishableKey && gatewayUrl) {
2885
+ const response = await fetch(`${gatewayUrl}${endpoint}`, {
2886
+ method: "POST",
2887
+ headers: {
2888
+ "Content-Type": "application/json",
2889
+ "x-api-key": publishableKey
2890
+ },
2891
+ body: JSON.stringify(payload)
2892
+ });
2893
+ if (!response.ok) {
2894
+ throw new Error(`Flag evaluation failed: ${response.status}`);
2895
+ }
2896
+ const json = await response.json();
2897
+ result = json.data || json || {};
2898
+ } else {
2899
+ result = await client.post(endpoint, payload);
2900
+ }
2870
2901
  setFlags(result || {});
2871
2902
  setError(null);
2872
2903
  } catch (err) {
@@ -2874,7 +2905,7 @@ function useFeatureFlags(options = {}) {
2874
2905
  } finally {
2875
2906
  setLoading(false);
2876
2907
  }
2877
- }, [client, enabled, environment, keys, keysKey]);
2908
+ }, [client, enabled, environment, keys, keysKey, publishableKey, gatewayUrl]);
2878
2909
  useEffect(() => {
2879
2910
  void refresh();
2880
2911
  }, [refresh]);
@@ -433,6 +433,43 @@ 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
+ * @example
443
+ * ```ts
444
+ * // app/layout.tsx
445
+ * import { getBootstrapFlags } from '@scalemule/nextjs/server'
446
+ *
447
+ * export default async function RootLayout({ children }) {
448
+ * const bootstrapFlags = await getBootstrapFlags(['analytics.tracking_enabled'])
449
+ * return (
450
+ * <Providers bootstrapFlags={bootstrapFlags}>
451
+ * {children}
452
+ * </Providers>
453
+ * )
454
+ * }
455
+ * ```
456
+ */
457
+ /**
458
+ * Evaluate feature flags server-side for bootstrapping the client provider.
459
+ *
460
+ * Extracts the client IP from request headers (x-forwarded-for, x-real-ip)
461
+ * and passes it as ip_address in the evaluation context so IP-based targeting
462
+ * rules work correctly.
463
+ *
464
+ * Returns a Record that can be passed directly to ScaleMuleProvider's
465
+ * bootstrapFlags prop.
466
+ *
467
+ * @param flagKeys - Array of flag keys to evaluate
468
+ * @param environment - Environment name (default: 'prod')
469
+ * @param extraContext - Additional context attributes to include
470
+ */
471
+ declare function getBootstrapFlags(flagKeys: string[], environment?: string, extraContext?: Record<string, unknown>): Promise<Record<string, unknown>>;
472
+
436
473
  /**
437
474
  * Next.js Middleware Helpers
438
475
  *
@@ -868,4 +905,4 @@ declare function invalidateBundleCache(key?: string): void;
868
905
  */
869
906
  declare function prefetchBundles(keys: string[]): Promise<void>;
870
907
 
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 };
908
+ 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 };
@@ -433,6 +433,43 @@ 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
+ * @example
443
+ * ```ts
444
+ * // app/layout.tsx
445
+ * import { getBootstrapFlags } from '@scalemule/nextjs/server'
446
+ *
447
+ * export default async function RootLayout({ children }) {
448
+ * const bootstrapFlags = await getBootstrapFlags(['analytics.tracking_enabled'])
449
+ * return (
450
+ * <Providers bootstrapFlags={bootstrapFlags}>
451
+ * {children}
452
+ * </Providers>
453
+ * )
454
+ * }
455
+ * ```
456
+ */
457
+ /**
458
+ * Evaluate feature flags server-side for bootstrapping the client provider.
459
+ *
460
+ * Extracts the client IP from request headers (x-forwarded-for, x-real-ip)
461
+ * and passes it as ip_address in the evaluation context so IP-based targeting
462
+ * rules work correctly.
463
+ *
464
+ * Returns a Record that can be passed directly to ScaleMuleProvider's
465
+ * bootstrapFlags prop.
466
+ *
467
+ * @param flagKeys - Array of flag keys to evaluate
468
+ * @param environment - Environment name (default: 'prod')
469
+ * @param extraContext - Additional context attributes to include
470
+ */
471
+ declare function getBootstrapFlags(flagKeys: string[], environment?: string, extraContext?: Record<string, unknown>): Promise<Record<string, unknown>>;
472
+
436
473
  /**
437
474
  * Next.js Middleware Helpers
438
475
  *
@@ -868,4 +905,4 @@ declare function invalidateBundleCache(key?: string): void;
868
905
  */
869
906
  declare function prefetchBundles(keys: string[]): Promise<void>;
870
907
 
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 };
908
+ 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 };
@@ -28,40 +28,40 @@ function validateIP(ip) {
28
28
  return void 0;
29
29
  }
30
30
  function extractClientContext(request) {
31
- const headers = request.headers;
31
+ const headers2 = request.headers;
32
32
  let ip;
33
- const cfConnectingIp = headers.get("cf-connecting-ip");
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 = headers.get("do-connecting-ip");
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 = headers.get("x-real-ip");
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 = headers.get("x-forwarded-for");
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 = headers.get("x-vercel-forwarded-for");
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 = headers.get("true-client-ip");
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 = headers.get("user-agent") || void 0;
73
- const deviceFingerprint = headers.get("x-device-fingerprint") || void 0;
74
- const referrer = headers.get("referer") || void 0;
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 headers = req.headers;
83
+ const headers2 = req.headers;
84
84
  const getHeader = (name) => {
85
- const value = headers[name.toLowerCase()];
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 headers = {};
142
+ const headers2 = {};
143
143
  if (!context) {
144
- return headers;
144
+ return headers2;
145
145
  }
146
146
  if (context.ip) {
147
- headers["x-sm-forwarded-client-ip"] = context.ip;
148
- headers["X-Client-IP"] = context.ip;
147
+ headers2["x-sm-forwarded-client-ip"] = context.ip;
148
+ headers2["X-Client-IP"] = context.ip;
149
149
  }
150
150
  if (context.userAgent) {
151
- headers["X-Client-User-Agent"] = context.userAgent;
151
+ headers2["X-Client-User-Agent"] = context.userAgent;
152
152
  }
153
153
  if (context.deviceFingerprint) {
154
- headers["X-Client-Device-Fingerprint"] = context.deviceFingerprint;
154
+ headers2["X-Client-Device-Fingerprint"] = context.deviceFingerprint;
155
155
  }
156
156
  if (context.referrer) {
157
- headers["X-Client-Referrer"] = context.referrer;
157
+ headers2["X-Client-Referrer"] = context.referrer;
158
158
  }
159
- return headers;
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 headers = {
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 headers = {
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
- headers["Authorization"] = `Bearer ${options.sessionToken}`;
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 headers = new Headers();
856
- headers.set("Content-Type", "application/json");
857
- headers.append(
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
- headers.append(
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 headers = new Headers();
872
- headers.set("Content-Type", "application/json");
873
- headers.append(
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
- headers.append(
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 headers = new Headers();
888
- headers.set("Content-Type", "application/json");
889
- headers.append("Set-Cookie", createClearCookieHeader(SESSION_COOKIE_NAME, options));
890
- headers.append("Set-Cookie", createClearCookieHeader(USER_ID_COOKIE_NAME, options));
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,31 @@ 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
+ async function getBootstrapFlags(flagKeys, environment = "prod", extraContext = {}) {
1776
+ try {
1777
+ const hdrs = await headers.headers();
1778
+ const forwardedFor = hdrs.get("x-forwarded-for");
1779
+ const realIp = hdrs.get("x-real-ip") || hdrs.get("x-real-client-ip");
1780
+ const clientIp = realIp || (forwardedFor ? forwardedFor.split(",")[0].trim() : void 0);
1781
+ const context = {
1782
+ ...extraContext
1783
+ };
1784
+ if (clientIp) {
1785
+ context.ip_address = clientIp;
1786
+ }
1787
+ const result = await getClient().flags.evaluateBatch(flagKeys, context, environment);
1788
+ return result || {};
1789
+ } catch {
1790
+ return {};
1791
+ }
1792
+ }
1768
1793
  function globToRegex(pattern) {
1769
1794
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
1770
1795
  return new RegExp(`^${escaped}$`);
@@ -2077,6 +2102,7 @@ exports.extractClientContextFromReq = extractClientContextFromReq;
2077
2102
  exports.generateCSRFToken = generateCSRFToken;
2078
2103
  exports.getAppSecret = getAppSecret;
2079
2104
  exports.getAppSecretOrDefault = getAppSecretOrDefault;
2105
+ exports.getBootstrapFlags = getBootstrapFlags;
2080
2106
  exports.getBundle = getBundle;
2081
2107
  exports.getCSRFToken = getCSRFToken;
2082
2108
  exports.getMySqlBundle = getMySqlBundle;
@@ -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 headers = request.headers;
29
+ const headers2 = request.headers;
30
30
  let ip;
31
- const cfConnectingIp = headers.get("cf-connecting-ip");
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 = headers.get("do-connecting-ip");
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 = headers.get("x-real-ip");
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 = headers.get("x-forwarded-for");
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 = headers.get("x-vercel-forwarded-for");
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 = headers.get("true-client-ip");
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 = headers.get("user-agent") || void 0;
71
- const deviceFingerprint = headers.get("x-device-fingerprint") || void 0;
72
- const referrer = headers.get("referer") || void 0;
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 headers = req.headers;
81
+ const headers2 = req.headers;
82
82
  const getHeader = (name) => {
83
- const value = headers[name.toLowerCase()];
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 headers = {};
140
+ const headers2 = {};
141
141
  if (!context) {
142
- return headers;
142
+ return headers2;
143
143
  }
144
144
  if (context.ip) {
145
- headers["x-sm-forwarded-client-ip"] = context.ip;
146
- headers["X-Client-IP"] = context.ip;
145
+ headers2["x-sm-forwarded-client-ip"] = context.ip;
146
+ headers2["X-Client-IP"] = context.ip;
147
147
  }
148
148
  if (context.userAgent) {
149
- headers["X-Client-User-Agent"] = context.userAgent;
149
+ headers2["X-Client-User-Agent"] = context.userAgent;
150
150
  }
151
151
  if (context.deviceFingerprint) {
152
- headers["X-Client-Device-Fingerprint"] = context.deviceFingerprint;
152
+ headers2["X-Client-Device-Fingerprint"] = context.deviceFingerprint;
153
153
  }
154
154
  if (context.referrer) {
155
- headers["X-Client-Referrer"] = context.referrer;
155
+ headers2["X-Client-Referrer"] = context.referrer;
156
156
  }
157
- return headers;
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 headers = {
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 headers = {
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
- headers["Authorization"] = `Bearer ${options.sessionToken}`;
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 headers = new Headers();
854
- headers.set("Content-Type", "application/json");
855
- headers.append(
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
- headers.append(
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 headers = new Headers();
870
- headers.set("Content-Type", "application/json");
871
- headers.append(
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
- headers.append(
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 headers = new Headers();
886
- headers.set("Content-Type", "application/json");
887
- headers.append("Set-Cookie", createClearCookieHeader(SESSION_COOKIE_NAME, options));
888
- headers.append("Set-Cookie", createClearCookieHeader(USER_ID_COOKIE_NAME, options));
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,31 @@ 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
+ async function getBootstrapFlags(flagKeys, environment = "prod", extraContext = {}) {
1774
+ try {
1775
+ const hdrs = await headers();
1776
+ const forwardedFor = hdrs.get("x-forwarded-for");
1777
+ const realIp = hdrs.get("x-real-ip") || hdrs.get("x-real-client-ip");
1778
+ const clientIp = realIp || (forwardedFor ? forwardedFor.split(",")[0].trim() : void 0);
1779
+ const context = {
1780
+ ...extraContext
1781
+ };
1782
+ if (clientIp) {
1783
+ context.ip_address = clientIp;
1784
+ }
1785
+ const result = await getClient().flags.evaluateBatch(flagKeys, context, environment);
1786
+ return result || {};
1787
+ } catch {
1788
+ return {};
1789
+ }
1790
+ }
1766
1791
  function globToRegex(pattern) {
1767
1792
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
1768
1793
  return new RegExp(`^${escaped}$`);
@@ -2050,4 +2075,4 @@ async function prefetchBundles(keys) {
2050
2075
  await Promise.all(keys.map((key) => getBundle(key)));
2051
2076
  }
2052
2077
 
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 };
2078
+ 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/nextjs",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "ScaleMule SDK for Next.js applications - authentication, storage, and user management",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",