@shipeasy/sdk 2.5.2 → 3.0.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.
@@ -137,12 +137,23 @@ interface AttachDevtoolsOptions {
137
137
  declare function attachDevtools(client: FlagsClientBrowser, opts?: AttachDevtoolsOptions): () => void;
138
138
  /** Configure the singleton. Idempotent — re-calling with the same opts is a no-op. */
139
139
  interface ShipeasyClientConfig {
140
- /** SDK key — same value used on the server via shipeasy(). */
141
- apiKey: string;
140
+ /**
141
+ * Public client key — the ONLY key the browser entrypoint accepts. Authenticates
142
+ * /sdk/evaluate, /collect and the runtime i18n loader (/sdk/i18n/strings). Safe to
143
+ * expose (e.g. NEXT_PUBLIC_ env vars). This is a different key from the server key
144
+ * passed to `shipeasy({ serverKey })` in @shipeasy/sdk/server — never use the
145
+ * server key here.
146
+ */
147
+ clientKey: string;
142
148
  /** Override the ShipEasy CDN/edge base URL. Defaults to https://cdn.shipeasy.ai. */
143
149
  baseUrl?: string;
144
150
  /** Override the admin URL for the devtools overlay (dev use). */
145
151
  adminUrl?: string;
152
+ /**
153
+ * i18n profile for the runtime string loader, e.g. "en:prod". Defaults to the
154
+ * profile the server recorded in window.__SE_BOOTSTRAP, then "en:prod".
155
+ */
156
+ i18nProfile?: string;
146
157
  /**
147
158
  * Skip the lazy auto-identify({}) at boot. Defaults to true (auto-identify on).
148
159
  * Turn off when the host has its own identify orchestration and wants to
@@ -158,9 +169,9 @@ interface ShipeasyClientConfig {
158
169
  * Pass `false` to disable everything, or a per-group object to narrow:
159
170
  *
160
171
  * ```ts
161
- * shipeasy({ apiKey, autoCollect: false }); // off
162
- * shipeasy({ apiKey, autoCollect: { errors: false } }); // vitals + engagement only
163
- * shipeasy({ apiKey }); // all groups on
172
+ * shipeasy({ clientKey, autoCollect: false }); // off
173
+ * shipeasy({ clientKey, autoCollect: { errors: false } }); // vitals + engagement only
174
+ * shipeasy({ clientKey }); // all groups on
164
175
  * ```
165
176
  */
166
177
  autoCollect?: boolean | Partial<AutoCollectGroups>;
@@ -198,8 +209,8 @@ interface BootstrapPayload {
198
209
  * the killswitch is not whole-killed and the map carries per-switch state.
199
210
  */
200
211
  killswitches?: Record<string, boolean | Record<string, boolean>>;
201
- /** Set by getBootstrapHtml() for auto-init. Not part of evaluate() output. */
202
- apiKey?: string;
212
+ /** i18n profile the server rendered with, so the client loader matches. No key is embedded. */
213
+ i18nProfile?: string;
203
214
  apiUrl?: string;
204
215
  /** When true, tEl() returns marker-wrapped strings for devtools label editing. */
205
216
  editLabels?: boolean;
@@ -137,12 +137,23 @@ interface AttachDevtoolsOptions {
137
137
  declare function attachDevtools(client: FlagsClientBrowser, opts?: AttachDevtoolsOptions): () => void;
138
138
  /** Configure the singleton. Idempotent — re-calling with the same opts is a no-op. */
139
139
  interface ShipeasyClientConfig {
140
- /** SDK key — same value used on the server via shipeasy(). */
141
- apiKey: string;
140
+ /**
141
+ * Public client key — the ONLY key the browser entrypoint accepts. Authenticates
142
+ * /sdk/evaluate, /collect and the runtime i18n loader (/sdk/i18n/strings). Safe to
143
+ * expose (e.g. NEXT_PUBLIC_ env vars). This is a different key from the server key
144
+ * passed to `shipeasy({ serverKey })` in @shipeasy/sdk/server — never use the
145
+ * server key here.
146
+ */
147
+ clientKey: string;
142
148
  /** Override the ShipEasy CDN/edge base URL. Defaults to https://cdn.shipeasy.ai. */
143
149
  baseUrl?: string;
144
150
  /** Override the admin URL for the devtools overlay (dev use). */
145
151
  adminUrl?: string;
152
+ /**
153
+ * i18n profile for the runtime string loader, e.g. "en:prod". Defaults to the
154
+ * profile the server recorded in window.__SE_BOOTSTRAP, then "en:prod".
155
+ */
156
+ i18nProfile?: string;
146
157
  /**
147
158
  * Skip the lazy auto-identify({}) at boot. Defaults to true (auto-identify on).
148
159
  * Turn off when the host has its own identify orchestration and wants to
@@ -158,9 +169,9 @@ interface ShipeasyClientConfig {
158
169
  * Pass `false` to disable everything, or a per-group object to narrow:
159
170
  *
160
171
  * ```ts
161
- * shipeasy({ apiKey, autoCollect: false }); // off
162
- * shipeasy({ apiKey, autoCollect: { errors: false } }); // vitals + engagement only
163
- * shipeasy({ apiKey }); // all groups on
172
+ * shipeasy({ clientKey, autoCollect: false }); // off
173
+ * shipeasy({ clientKey, autoCollect: { errors: false } }); // vitals + engagement only
174
+ * shipeasy({ clientKey }); // all groups on
164
175
  * ```
165
176
  */
166
177
  autoCollect?: boolean | Partial<AutoCollectGroups>;
@@ -198,8 +209,8 @@ interface BootstrapPayload {
198
209
  * the killswitch is not whole-killed and the map carries per-switch state.
199
210
  */
200
211
  killswitches?: Record<string, boolean | Record<string, boolean>>;
201
- /** Set by getBootstrapHtml() for auto-init. Not part of evaluate() output. */
202
- apiKey?: string;
212
+ /** i18n profile the server rendered with, so the client loader matches. No key is embedded. */
213
+ i18nProfile?: string;
203
214
  apiUrl?: string;
204
215
  /** When true, tEl() returns marker-wrapped strings for devtools label editing. */
205
216
  editLabels?: boolean;
@@ -711,12 +711,14 @@ function shipeasy(opts) {
711
711
  const ac = opts.autoCollect;
712
712
  const blanket = ac === false ? false : true;
713
713
  const groups = ac && typeof ac === "object" ? ac : void 0;
714
+ const baseUrl = opts.baseUrl ?? "https://cdn.shipeasy.ai";
714
715
  const client = configureShipeasy({
715
- sdkKey: opts.apiKey,
716
- baseUrl: opts.baseUrl ?? "https://cdn.shipeasy.ai",
716
+ sdkKey: opts.clientKey,
717
+ baseUrl,
717
718
  autoGuardrails: blanket,
718
719
  autoGuardrailGroups: groups
719
720
  });
721
+ injectI18nLoader(opts.clientKey, baseUrl, opts.i18nProfile);
720
722
  flags.notifyMounted();
721
723
  if (opts.autoIdentify !== false) {
722
724
  void client.identify({}).catch((err) => {
@@ -736,6 +738,23 @@ function getShipeasyClient() {
736
738
  function _resetShipeasyForTests() {
737
739
  _client?.destroy();
738
740
  _client = null;
741
+ _i18nLoaderInjected = false;
742
+ }
743
+ var _i18nLoaderInjected = false;
744
+ function injectI18nLoader(clientKey, baseUrl, profileOpt) {
745
+ if (_i18nLoaderInjected || typeof document === "undefined") return;
746
+ if (!clientKey || typeof document.createElement !== "function" || !document.head) return;
747
+ _i18nLoaderInjected = true;
748
+ try {
749
+ const bs = getBootstrap();
750
+ const profile = profileOpt ?? bs?.i18nProfile ?? "en:prod";
751
+ const s = document.createElement("script");
752
+ s.src = `${baseUrl}/sdk/i18n/loader.js`;
753
+ s.setAttribute("data-key", clientKey);
754
+ s.setAttribute("data-profile", profile);
755
+ document.head.appendChild(s);
756
+ } catch {
757
+ }
739
758
  }
740
759
  function getBootstrap() {
741
760
  if (typeof window === "undefined") return null;
@@ -1090,12 +1109,6 @@ var i18n = {
1090
1109
  };
1091
1110
  }
1092
1111
  };
1093
- if (typeof window !== "undefined") {
1094
- const _initBs = window.__SE_BOOTSTRAP;
1095
- if (_initBs?.apiKey && !_client) {
1096
- shipeasy({ apiKey: _initBs.apiKey, baseUrl: _initBs.apiUrl });
1097
- }
1098
- }
1099
1112
  // Annotate the CommonJS export names for ESM import in node:
1100
1113
  0 && (module.exports = {
1101
1114
  FlagsClientBrowser,
@@ -668,12 +668,14 @@ function shipeasy(opts) {
668
668
  const ac = opts.autoCollect;
669
669
  const blanket = ac === false ? false : true;
670
670
  const groups = ac && typeof ac === "object" ? ac : void 0;
671
+ const baseUrl = opts.baseUrl ?? "https://cdn.shipeasy.ai";
671
672
  const client = configureShipeasy({
672
- sdkKey: opts.apiKey,
673
- baseUrl: opts.baseUrl ?? "https://cdn.shipeasy.ai",
673
+ sdkKey: opts.clientKey,
674
+ baseUrl,
674
675
  autoGuardrails: blanket,
675
676
  autoGuardrailGroups: groups
676
677
  });
678
+ injectI18nLoader(opts.clientKey, baseUrl, opts.i18nProfile);
677
679
  flags.notifyMounted();
678
680
  if (opts.autoIdentify !== false) {
679
681
  void client.identify({}).catch((err) => {
@@ -693,6 +695,23 @@ function getShipeasyClient() {
693
695
  function _resetShipeasyForTests() {
694
696
  _client?.destroy();
695
697
  _client = null;
698
+ _i18nLoaderInjected = false;
699
+ }
700
+ var _i18nLoaderInjected = false;
701
+ function injectI18nLoader(clientKey, baseUrl, profileOpt) {
702
+ if (_i18nLoaderInjected || typeof document === "undefined") return;
703
+ if (!clientKey || typeof document.createElement !== "function" || !document.head) return;
704
+ _i18nLoaderInjected = true;
705
+ try {
706
+ const bs = getBootstrap();
707
+ const profile = profileOpt ?? bs?.i18nProfile ?? "en:prod";
708
+ const s = document.createElement("script");
709
+ s.src = `${baseUrl}/sdk/i18n/loader.js`;
710
+ s.setAttribute("data-key", clientKey);
711
+ s.setAttribute("data-profile", profile);
712
+ document.head.appendChild(s);
713
+ } catch {
714
+ }
696
715
  }
697
716
  function getBootstrap() {
698
717
  if (typeof window === "undefined") return null;
@@ -1047,12 +1066,6 @@ var i18n = {
1047
1066
  };
1048
1067
  }
1049
1068
  };
1050
- if (typeof window !== "undefined") {
1051
- const _initBs = window.__SE_BOOTSTRAP;
1052
- if (_initBs?.apiKey && !_client) {
1053
- shipeasy({ apiKey: _initBs.apiKey, baseUrl: _initBs.apiUrl });
1054
- }
1055
- }
1056
1069
  export {
1057
1070
  FlagsClientBrowser,
1058
1071
  LABEL_MARKER_END,
@@ -132,20 +132,15 @@ declare function getShipeasyServerClient(): FlagsClient | null;
132
132
  declare function _resetShipeasyServerForTests(): void;
133
133
  interface ShipeasyServerConfig {
134
134
  /**
135
- * Server-side API key — authenticates flag/experiment fetches from the edge
136
- * (requireKey("server")). Never embedded in browser output. If omitted, flag
137
- * and experiment evaluation is skipped and an error is logged — it is NOT
138
- * substituted with clientKey (a client key 401s against /sdk/flags).
135
+ * Server key — the ONLY key the server entrypoint accepts. Authenticates
136
+ * flag/experiment fetches (requireKey("server")) AND SSR i18n string fetches
137
+ * (the /sdk/i18n/strings route accepts the server key for server-side use).
138
+ * Never embedded in browser output. The browser uses its own client key via
139
+ * `shipeasy({ clientKey })` from `@shipeasy/sdk/client` — the server never
140
+ * sees or forwards the client key. If omitted, flag/experiment/i18n loading
141
+ * is skipped and an error is logged.
139
142
  */
140
- apiKey?: string;
141
- /**
142
- * Public client key — embedded in window.__SE_BOOTSTRAP and used by the
143
- * browser SDK, and authenticates i18n string fetches (requireKey("client")).
144
- * Safe to expose (e.g. NEXT_PUBLIC_ env vars). If omitted, i18n loading is
145
- * skipped and an error is logged — it is NOT substituted with apiKey (a
146
- * server key 401s against /sdk/i18n/strings).
147
- */
148
- clientKey?: string;
143
+ serverKey?: string;
149
144
  /** Raw URL or query string for applying ?se_ks_* / ?se_cf_* / ?se_exp_* overrides. */
150
145
  urlOverrides?: string;
151
146
  /** User attributes for flag and experiment evaluation. */
@@ -168,23 +163,24 @@ interface ShipeasyServerHandle {
168
163
  */
169
164
  declare function shipeasy(opts: ShipeasyServerConfig): Promise<ShipeasyServerHandle>;
170
165
  interface BootstrapHtmlOptions {
171
- /** SDK client key */
172
- apiKey: string;
173
- /** i18n profile fed to the loader script. Defaults to "en:prod". */
166
+ /** i18n profile recorded in the bootstrap so the client loader matches SSR. Defaults to "en:prod". */
174
167
  i18nProfile?: string;
175
168
  /** When true, tEl() embeds label markers so the devtools can highlight them. */
176
169
  editLabels?: boolean;
177
170
  }
178
171
  /**
179
- * Returns a vanilla-JS script string for a single <script> tag.
180
- * Handles everything the client needs at startup:
172
+ * Returns a vanilla-JS string for a single inline <script> tag. Handles
173
+ * everything the client needs at startup EXCEPT the key — no SDK key is ever
174
+ * embedded here (the server only knows the server key, which must stay
175
+ * server-side). The browser supplies its own client key via
176
+ * `shipeasy({ clientKey })` from @shipeasy/sdk/client, which also injects the
177
+ * runtime i18n loader. This script emits:
181
178
  * - window.__se_devtools_config (when devtoolsAdminUrl is set)
182
- * - window.__SE_BOOTSTRAP (flags + configs + experiments + i18n + apiKey for auto-init)
183
- * - window.i18n shim from SSR strings (prevents hydration mismatches)
184
- * - dynamic <script> injection for the i18n loader
179
+ * - window.__SE_BOOTSTRAP (flags + configs + experiments + i18n DATA + i18nProfile, NO key)
180
+ * - window.i18n shim from SSR strings (prevents hydration mismatches / FOUC)
181
+ * - devtools overlay loader when ?se / ?se_devtools is present
185
182
  *
186
183
  * Framework-agnostic: set innerHTML on a <script> element, nothing else required.
187
- * Pass null for bootstrap on pages without flag evaluation — client still auto-inits.
188
184
  */
189
185
  declare function getBootstrapHtml(bootstrap: BootstrapPayload | null, i18nData: I18nForRequest | null, opts: BootstrapHtmlOptions): string;
190
186
  declare const flags: {
@@ -132,20 +132,15 @@ declare function getShipeasyServerClient(): FlagsClient | null;
132
132
  declare function _resetShipeasyServerForTests(): void;
133
133
  interface ShipeasyServerConfig {
134
134
  /**
135
- * Server-side API key — authenticates flag/experiment fetches from the edge
136
- * (requireKey("server")). Never embedded in browser output. If omitted, flag
137
- * and experiment evaluation is skipped and an error is logged — it is NOT
138
- * substituted with clientKey (a client key 401s against /sdk/flags).
135
+ * Server key — the ONLY key the server entrypoint accepts. Authenticates
136
+ * flag/experiment fetches (requireKey("server")) AND SSR i18n string fetches
137
+ * (the /sdk/i18n/strings route accepts the server key for server-side use).
138
+ * Never embedded in browser output. The browser uses its own client key via
139
+ * `shipeasy({ clientKey })` from `@shipeasy/sdk/client` — the server never
140
+ * sees or forwards the client key. If omitted, flag/experiment/i18n loading
141
+ * is skipped and an error is logged.
139
142
  */
140
- apiKey?: string;
141
- /**
142
- * Public client key — embedded in window.__SE_BOOTSTRAP and used by the
143
- * browser SDK, and authenticates i18n string fetches (requireKey("client")).
144
- * Safe to expose (e.g. NEXT_PUBLIC_ env vars). If omitted, i18n loading is
145
- * skipped and an error is logged — it is NOT substituted with apiKey (a
146
- * server key 401s against /sdk/i18n/strings).
147
- */
148
- clientKey?: string;
143
+ serverKey?: string;
149
144
  /** Raw URL or query string for applying ?se_ks_* / ?se_cf_* / ?se_exp_* overrides. */
150
145
  urlOverrides?: string;
151
146
  /** User attributes for flag and experiment evaluation. */
@@ -168,23 +163,24 @@ interface ShipeasyServerHandle {
168
163
  */
169
164
  declare function shipeasy(opts: ShipeasyServerConfig): Promise<ShipeasyServerHandle>;
170
165
  interface BootstrapHtmlOptions {
171
- /** SDK client key */
172
- apiKey: string;
173
- /** i18n profile fed to the loader script. Defaults to "en:prod". */
166
+ /** i18n profile recorded in the bootstrap so the client loader matches SSR. Defaults to "en:prod". */
174
167
  i18nProfile?: string;
175
168
  /** When true, tEl() embeds label markers so the devtools can highlight them. */
176
169
  editLabels?: boolean;
177
170
  }
178
171
  /**
179
- * Returns a vanilla-JS script string for a single <script> tag.
180
- * Handles everything the client needs at startup:
172
+ * Returns a vanilla-JS string for a single inline <script> tag. Handles
173
+ * everything the client needs at startup EXCEPT the key — no SDK key is ever
174
+ * embedded here (the server only knows the server key, which must stay
175
+ * server-side). The browser supplies its own client key via
176
+ * `shipeasy({ clientKey })` from @shipeasy/sdk/client, which also injects the
177
+ * runtime i18n loader. This script emits:
181
178
  * - window.__se_devtools_config (when devtoolsAdminUrl is set)
182
- * - window.__SE_BOOTSTRAP (flags + configs + experiments + i18n + apiKey for auto-init)
183
- * - window.i18n shim from SSR strings (prevents hydration mismatches)
184
- * - dynamic <script> injection for the i18n loader
179
+ * - window.__SE_BOOTSTRAP (flags + configs + experiments + i18n DATA + i18nProfile, NO key)
180
+ * - window.i18n shim from SSR strings (prevents hydration mismatches / FOUC)
181
+ * - devtools overlay loader when ?se / ?se_devtools is present
185
182
  *
186
183
  * Framework-agnostic: set innerHTML on a <script> element, nothing else required.
187
- * Pass null for bootstrap on pages without flag evaluation — client still auto-inits.
188
184
  */
189
185
  declare function getBootstrapHtml(bootstrap: BootstrapPayload | null, i18nData: I18nForRequest | null, opts: BootstrapHtmlOptions): string;
190
186
  declare const flags: {
@@ -486,7 +486,6 @@ async function fetchLabelsForSSR(opts) {
486
486
  }
487
487
  }
488
488
  var _server = null;
489
- var _rememberedClientKey = null;
490
489
  function configureShipeasyServer(opts) {
491
490
  if (_server) return _server;
492
491
  _server = new FlagsClient(opts);
@@ -500,21 +499,14 @@ function _resetShipeasyServerForTests() {
500
499
  _server = null;
501
500
  }
502
501
  async function shipeasy(opts) {
503
- const apiKey = opts.apiKey ?? "";
504
- const clientKey = opts.clientKey ?? _rememberedClientKey ?? "";
505
- if (opts.clientKey && !_rememberedClientKey) _rememberedClientKey = opts.clientKey;
506
- if (!apiKey) {
502
+ const serverKey = opts.serverKey ?? "";
503
+ if (!serverKey) {
507
504
  console.error(
508
- "[shipeasy] No server key \u2014 flags & experiments skipped. Pass `apiKey` to shipeasy() with your server key (SHIPEASY_SERVER_KEY). Set it as a Worker secret with `wrangler secret put SHIPEASY_SERVER_KEY` (or add it to .env for local dev). Do not pass a client key here \u2014 /sdk/flags requires a server key and will 401."
509
- );
510
- }
511
- if (!clientKey) {
512
- console.error(
513
- "[shipeasy] No client key \u2014 i18n strings skipped, falling back to hardcoded text. Pass `clientKey` to shipeasy() with your public client key (NEXT_PUBLIC_SHIPEASY_CLIENT_KEY). Do not pass a server key here \u2014 /sdk/i18n/strings requires a client key and will 401."
505
+ "[shipeasy] No server key \u2014 flags, experiments and SSR i18n skipped. Pass `serverKey` to shipeasy() from @shipeasy/sdk/server with your server key (SHIPEASY_SERVER_KEY). Set it as a Worker secret with `wrangler secret put SHIPEASY_SERVER_KEY` (or add it to .env for local dev). Do not pass a client key here \u2014 the server entrypoint only accepts the server key."
514
506
  );
515
507
  }
516
508
  const profile = opts.i18nDefaultProfile ?? "en:prod";
517
- flags.configure({ apiKey });
509
+ flags.configure({ apiKey: serverKey });
518
510
  let resolvedUrlOverrides = opts.urlOverrides;
519
511
  if (!resolvedUrlOverrides) {
520
512
  try {
@@ -535,8 +527,8 @@ async function shipeasy(opts) {
535
527
  const editLabels = resolvedUrlOverrides ? new URLSearchParams(resolvedUrlOverrides).has("se_edit_labels") : false;
536
528
  globalThis[_EDIT_MODE_SSR_SYM] = editLabels;
537
529
  await Promise.allSettled([
538
- apiKey ? flags.initOnce() : Promise.resolve(),
539
- clientKey ? i18n.init(clientKey, profile) : Promise.resolve()
530
+ serverKey ? flags.initOnce() : Promise.resolve(),
531
+ serverKey ? i18n.init(serverKey, profile) : Promise.resolve()
540
532
  ]);
541
533
  const bootstrap = flags.evaluate(opts.user ?? {}, resolvedUrlOverrides);
542
534
  const i18nData = i18n.getForRequest();
@@ -546,7 +538,6 @@ async function shipeasy(opts) {
546
538
  experiments: bootstrap.experiments,
547
539
  getBootstrapHtml() {
548
540
  return getBootstrapHtml(bootstrap, i18nData, {
549
- apiKey: clientKey,
550
541
  editLabels,
551
542
  i18nProfile: profile
552
543
  });
@@ -561,7 +552,9 @@ function getBootstrapHtml(bootstrap, i18nData, opts) {
561
552
  flags: bootstrap?.flags ?? {},
562
553
  configs: bootstrap?.configs ?? {},
563
554
  experiments: bootstrap?.experiments ?? {},
564
- apiKey: opts.apiKey,
555
+ // No key here — the server only knows the server key, which must never reach
556
+ // the browser. The client supplies its own client key via shipeasy({ clientKey }).
557
+ i18nProfile: profile,
565
558
  apiUrl
566
559
  };
567
560
  if (i18nData) payload.i18n = i18nData;
@@ -575,9 +568,6 @@ function getBootstrapHtml(bootstrap, i18nData, opts) {
575
568
  `(function(){var d=window.__SE_BOOTSTRAP.i18n;if(!d)return;window.i18n={locale:d.locale,t:function(k,v){var r=d.strings[k];if(!r)return k;return v?r.replace(/\\{\\{(\\w+)\\}\\}/g,function(_,p){return v[p]!==undefined?String(v[p]):'{{'+p+'}}'}):r;},on:function(){return function(){};}};})();`
576
569
  );
577
570
  }
578
- parts.push(
579
- `(function(){var s=document.createElement('script');s.src=${JSON.stringify(`${apiUrl}/sdk/i18n/loader.js`)};s.setAttribute('data-key',${JSON.stringify(opts.apiKey)});s.setAttribute('data-profile',${JSON.stringify(profile)});document.head.appendChild(s);})();`
580
- );
581
571
  parts.push(
582
572
  `(function(){var p=new URLSearchParams(location.search);if(p.has('se')||p.has('se_devtools')){var d=document.createElement('script');d.src='https://shipeasy.ai/se-devtools.js';document.head.appendChild(d);}})();`
583
573
  );
@@ -443,7 +443,6 @@ async function fetchLabelsForSSR(opts) {
443
443
  }
444
444
  }
445
445
  var _server = null;
446
- var _rememberedClientKey = null;
447
446
  function configureShipeasyServer(opts) {
448
447
  if (_server) return _server;
449
448
  _server = new FlagsClient(opts);
@@ -457,21 +456,14 @@ function _resetShipeasyServerForTests() {
457
456
  _server = null;
458
457
  }
459
458
  async function shipeasy(opts) {
460
- const apiKey = opts.apiKey ?? "";
461
- const clientKey = opts.clientKey ?? _rememberedClientKey ?? "";
462
- if (opts.clientKey && !_rememberedClientKey) _rememberedClientKey = opts.clientKey;
463
- if (!apiKey) {
459
+ const serverKey = opts.serverKey ?? "";
460
+ if (!serverKey) {
464
461
  console.error(
465
- "[shipeasy] No server key \u2014 flags & experiments skipped. Pass `apiKey` to shipeasy() with your server key (SHIPEASY_SERVER_KEY). Set it as a Worker secret with `wrangler secret put SHIPEASY_SERVER_KEY` (or add it to .env for local dev). Do not pass a client key here \u2014 /sdk/flags requires a server key and will 401."
466
- );
467
- }
468
- if (!clientKey) {
469
- console.error(
470
- "[shipeasy] No client key \u2014 i18n strings skipped, falling back to hardcoded text. Pass `clientKey` to shipeasy() with your public client key (NEXT_PUBLIC_SHIPEASY_CLIENT_KEY). Do not pass a server key here \u2014 /sdk/i18n/strings requires a client key and will 401."
462
+ "[shipeasy] No server key \u2014 flags, experiments and SSR i18n skipped. Pass `serverKey` to shipeasy() from @shipeasy/sdk/server with your server key (SHIPEASY_SERVER_KEY). Set it as a Worker secret with `wrangler secret put SHIPEASY_SERVER_KEY` (or add it to .env for local dev). Do not pass a client key here \u2014 the server entrypoint only accepts the server key."
471
463
  );
472
464
  }
473
465
  const profile = opts.i18nDefaultProfile ?? "en:prod";
474
- flags.configure({ apiKey });
466
+ flags.configure({ apiKey: serverKey });
475
467
  let resolvedUrlOverrides = opts.urlOverrides;
476
468
  if (!resolvedUrlOverrides) {
477
469
  try {
@@ -492,8 +484,8 @@ async function shipeasy(opts) {
492
484
  const editLabels = resolvedUrlOverrides ? new URLSearchParams(resolvedUrlOverrides).has("se_edit_labels") : false;
493
485
  globalThis[_EDIT_MODE_SSR_SYM] = editLabels;
494
486
  await Promise.allSettled([
495
- apiKey ? flags.initOnce() : Promise.resolve(),
496
- clientKey ? i18n.init(clientKey, profile) : Promise.resolve()
487
+ serverKey ? flags.initOnce() : Promise.resolve(),
488
+ serverKey ? i18n.init(serverKey, profile) : Promise.resolve()
497
489
  ]);
498
490
  const bootstrap = flags.evaluate(opts.user ?? {}, resolvedUrlOverrides);
499
491
  const i18nData = i18n.getForRequest();
@@ -503,7 +495,6 @@ async function shipeasy(opts) {
503
495
  experiments: bootstrap.experiments,
504
496
  getBootstrapHtml() {
505
497
  return getBootstrapHtml(bootstrap, i18nData, {
506
- apiKey: clientKey,
507
498
  editLabels,
508
499
  i18nProfile: profile
509
500
  });
@@ -518,7 +509,9 @@ function getBootstrapHtml(bootstrap, i18nData, opts) {
518
509
  flags: bootstrap?.flags ?? {},
519
510
  configs: bootstrap?.configs ?? {},
520
511
  experiments: bootstrap?.experiments ?? {},
521
- apiKey: opts.apiKey,
512
+ // No key here — the server only knows the server key, which must never reach
513
+ // the browser. The client supplies its own client key via shipeasy({ clientKey }).
514
+ i18nProfile: profile,
522
515
  apiUrl
523
516
  };
524
517
  if (i18nData) payload.i18n = i18nData;
@@ -532,9 +525,6 @@ function getBootstrapHtml(bootstrap, i18nData, opts) {
532
525
  `(function(){var d=window.__SE_BOOTSTRAP.i18n;if(!d)return;window.i18n={locale:d.locale,t:function(k,v){var r=d.strings[k];if(!r)return k;return v?r.replace(/\\{\\{(\\w+)\\}\\}/g,function(_,p){return v[p]!==undefined?String(v[p]):'{{'+p+'}}'}):r;},on:function(){return function(){};}};})();`
533
526
  );
534
527
  }
535
- parts.push(
536
- `(function(){var s=document.createElement('script');s.src=${JSON.stringify(`${apiUrl}/sdk/i18n/loader.js`)};s.setAttribute('data-key',${JSON.stringify(opts.apiKey)});s.setAttribute('data-profile',${JSON.stringify(profile)});document.head.appendChild(s);})();`
537
- );
538
528
  parts.push(
539
529
  `(function(){var p=new URLSearchParams(location.search);if(p.has('se')||p.has('se_devtools')){var d=document.createElement('script');d.src='https://shipeasy.ai/se-devtools.js';document.head.appendChild(d);}})();`
540
530
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipeasy/sdk",
3
- "version": "2.5.2",
3
+ "version": "3.0.0",
4
4
  "description": "Shipeasy SDK — feature gates, runtime configs, experiments, and metrics for the Shipeasy hosted service.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://shipeasy.ai",