@shipeasy/sdk 2.5.1 → 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,16 +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
- * Never embedded in browser output. A warning is logged if omitted.
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.
137
142
  */
138
- apiKey?: string;
139
- /**
140
- * Public client key — embedded in window.__SE_BOOTSTRAP and used by the
141
- * browser SDK. Safe to expose (e.g. NEXT_PUBLIC_ env vars).
142
- * Defaults to apiKey for single-key setups.
143
- */
144
- clientKey?: string;
143
+ serverKey?: string;
145
144
  /** Raw URL or query string for applying ?se_ks_* / ?se_cf_* / ?se_exp_* overrides. */
146
145
  urlOverrides?: string;
147
146
  /** User attributes for flag and experiment evaluation. */
@@ -164,23 +163,24 @@ interface ShipeasyServerHandle {
164
163
  */
165
164
  declare function shipeasy(opts: ShipeasyServerConfig): Promise<ShipeasyServerHandle>;
166
165
  interface BootstrapHtmlOptions {
167
- /** SDK client key */
168
- apiKey: string;
169
- /** 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". */
170
167
  i18nProfile?: string;
171
168
  /** When true, tEl() embeds label markers so the devtools can highlight them. */
172
169
  editLabels?: boolean;
173
170
  }
174
171
  /**
175
- * Returns a vanilla-JS script string for a single <script> tag.
176
- * 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:
177
178
  * - window.__se_devtools_config (when devtoolsAdminUrl is set)
178
- * - window.__SE_BOOTSTRAP (flags + configs + experiments + i18n + apiKey for auto-init)
179
- * - window.i18n shim from SSR strings (prevents hydration mismatches)
180
- * - 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
181
182
  *
182
183
  * Framework-agnostic: set innerHTML on a <script> element, nothing else required.
183
- * Pass null for bootstrap on pages without flag evaluation — client still auto-inits.
184
184
  */
185
185
  declare function getBootstrapHtml(bootstrap: BootstrapPayload | null, i18nData: I18nForRequest | null, opts: BootstrapHtmlOptions): string;
186
186
  declare const flags: {
@@ -132,16 +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
- * Never embedded in browser output. A warning is logged if omitted.
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.
137
142
  */
138
- apiKey?: string;
139
- /**
140
- * Public client key — embedded in window.__SE_BOOTSTRAP and used by the
141
- * browser SDK. Safe to expose (e.g. NEXT_PUBLIC_ env vars).
142
- * Defaults to apiKey for single-key setups.
143
- */
144
- clientKey?: string;
143
+ serverKey?: string;
145
144
  /** Raw URL or query string for applying ?se_ks_* / ?se_cf_* / ?se_exp_* overrides. */
146
145
  urlOverrides?: string;
147
146
  /** User attributes for flag and experiment evaluation. */
@@ -164,23 +163,24 @@ interface ShipeasyServerHandle {
164
163
  */
165
164
  declare function shipeasy(opts: ShipeasyServerConfig): Promise<ShipeasyServerHandle>;
166
165
  interface BootstrapHtmlOptions {
167
- /** SDK client key */
168
- apiKey: string;
169
- /** 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". */
170
167
  i18nProfile?: string;
171
168
  /** When true, tEl() embeds label markers so the devtools can highlight them. */
172
169
  editLabels?: boolean;
173
170
  }
174
171
  /**
175
- * Returns a vanilla-JS script string for a single <script> tag.
176
- * 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:
177
178
  * - window.__se_devtools_config (when devtoolsAdminUrl is set)
178
- * - window.__SE_BOOTSTRAP (flags + configs + experiments + i18n + apiKey for auto-init)
179
- * - window.i18n shim from SSR strings (prevents hydration mismatches)
180
- * - 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
181
182
  *
182
183
  * Framework-agnostic: set innerHTML on a <script> element, nothing else required.
183
- * Pass null for bootstrap on pages without flag evaluation — client still auto-inits.
184
184
  */
185
185
  declare function getBootstrapHtml(bootstrap: BootstrapPayload | null, i18nData: I18nForRequest | null, opts: BootstrapHtmlOptions): string;
186
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,16 +499,14 @@ function _resetShipeasyServerForTests() {
500
499
  _server = null;
501
500
  }
502
501
  async function shipeasy(opts) {
503
- if (!opts.apiKey && !opts.clientKey) {
504
- console.warn("[shipeasy] apiKey is required \u2014 flag evaluation and i18n will not load.");
505
- } else if (!opts.apiKey) {
506
- console.warn("[shipeasy] apiKey not set \u2014 falling back to clientKey for server requests.");
502
+ const serverKey = opts.serverKey ?? "";
503
+ if (!serverKey) {
504
+ console.error(
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."
506
+ );
507
507
  }
508
- const apiKey = opts.apiKey ?? opts.clientKey ?? "";
509
- const clientKey = opts.clientKey ?? _rememberedClientKey ?? opts.apiKey ?? "";
510
- if (opts.clientKey && !_rememberedClientKey) _rememberedClientKey = opts.clientKey;
511
508
  const profile = opts.i18nDefaultProfile ?? "en:prod";
512
- flags.configure({ apiKey });
509
+ flags.configure({ apiKey: serverKey });
513
510
  let resolvedUrlOverrides = opts.urlOverrides;
514
511
  if (!resolvedUrlOverrides) {
515
512
  try {
@@ -529,7 +526,10 @@ async function shipeasy(opts) {
529
526
  }
530
527
  const editLabels = resolvedUrlOverrides ? new URLSearchParams(resolvedUrlOverrides).has("se_edit_labels") : false;
531
528
  globalThis[_EDIT_MODE_SSR_SYM] = editLabels;
532
- await Promise.allSettled([flags.initOnce(), i18n.init(clientKey, profile)]);
529
+ await Promise.allSettled([
530
+ serverKey ? flags.initOnce() : Promise.resolve(),
531
+ serverKey ? i18n.init(serverKey, profile) : Promise.resolve()
532
+ ]);
533
533
  const bootstrap = flags.evaluate(opts.user ?? {}, resolvedUrlOverrides);
534
534
  const i18nData = i18n.getForRequest();
535
535
  return {
@@ -538,7 +538,6 @@ async function shipeasy(opts) {
538
538
  experiments: bootstrap.experiments,
539
539
  getBootstrapHtml() {
540
540
  return getBootstrapHtml(bootstrap, i18nData, {
541
- apiKey: clientKey,
542
541
  editLabels,
543
542
  i18nProfile: profile
544
543
  });
@@ -553,7 +552,9 @@ function getBootstrapHtml(bootstrap, i18nData, opts) {
553
552
  flags: bootstrap?.flags ?? {},
554
553
  configs: bootstrap?.configs ?? {},
555
554
  experiments: bootstrap?.experiments ?? {},
556
- 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,
557
558
  apiUrl
558
559
  };
559
560
  if (i18nData) payload.i18n = i18nData;
@@ -567,9 +568,6 @@ function getBootstrapHtml(bootstrap, i18nData, opts) {
567
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(){};}};})();`
568
569
  );
569
570
  }
570
- parts.push(
571
- `(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);})();`
572
- );
573
571
  parts.push(
574
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);}})();`
575
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,16 +456,14 @@ function _resetShipeasyServerForTests() {
457
456
  _server = null;
458
457
  }
459
458
  async function shipeasy(opts) {
460
- if (!opts.apiKey && !opts.clientKey) {
461
- console.warn("[shipeasy] apiKey is required \u2014 flag evaluation and i18n will not load.");
462
- } else if (!opts.apiKey) {
463
- console.warn("[shipeasy] apiKey not set \u2014 falling back to clientKey for server requests.");
459
+ const serverKey = opts.serverKey ?? "";
460
+ if (!serverKey) {
461
+ console.error(
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."
463
+ );
464
464
  }
465
- const apiKey = opts.apiKey ?? opts.clientKey ?? "";
466
- const clientKey = opts.clientKey ?? _rememberedClientKey ?? opts.apiKey ?? "";
467
- if (opts.clientKey && !_rememberedClientKey) _rememberedClientKey = opts.clientKey;
468
465
  const profile = opts.i18nDefaultProfile ?? "en:prod";
469
- flags.configure({ apiKey });
466
+ flags.configure({ apiKey: serverKey });
470
467
  let resolvedUrlOverrides = opts.urlOverrides;
471
468
  if (!resolvedUrlOverrides) {
472
469
  try {
@@ -486,7 +483,10 @@ async function shipeasy(opts) {
486
483
  }
487
484
  const editLabels = resolvedUrlOverrides ? new URLSearchParams(resolvedUrlOverrides).has("se_edit_labels") : false;
488
485
  globalThis[_EDIT_MODE_SSR_SYM] = editLabels;
489
- await Promise.allSettled([flags.initOnce(), i18n.init(clientKey, profile)]);
486
+ await Promise.allSettled([
487
+ serverKey ? flags.initOnce() : Promise.resolve(),
488
+ serverKey ? i18n.init(serverKey, profile) : Promise.resolve()
489
+ ]);
490
490
  const bootstrap = flags.evaluate(opts.user ?? {}, resolvedUrlOverrides);
491
491
  const i18nData = i18n.getForRequest();
492
492
  return {
@@ -495,7 +495,6 @@ async function shipeasy(opts) {
495
495
  experiments: bootstrap.experiments,
496
496
  getBootstrapHtml() {
497
497
  return getBootstrapHtml(bootstrap, i18nData, {
498
- apiKey: clientKey,
499
498
  editLabels,
500
499
  i18nProfile: profile
501
500
  });
@@ -510,7 +509,9 @@ function getBootstrapHtml(bootstrap, i18nData, opts) {
510
509
  flags: bootstrap?.flags ?? {},
511
510
  configs: bootstrap?.configs ?? {},
512
511
  experiments: bootstrap?.experiments ?? {},
513
- 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,
514
515
  apiUrl
515
516
  };
516
517
  if (i18nData) payload.i18n = i18nData;
@@ -524,9 +525,6 @@ function getBootstrapHtml(bootstrap, i18nData, opts) {
524
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(){};}};})();`
525
526
  );
526
527
  }
527
- parts.push(
528
- `(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);})();`
529
- );
530
528
  parts.push(
531
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);}})();`
532
530
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipeasy/sdk",
3
- "version": "2.5.1",
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",