@shipeasy/sdk 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.d.mts +27 -6
- package/dist/client/index.d.ts +27 -6
- package/dist/client/index.js +46 -20
- package/dist/client/index.mjs +45 -20
- package/dist/server/index.d.mts +78 -1
- package/dist/server/index.d.ts +78 -1
- package/dist/server/index.js +84 -0
- package/dist/server/index.mjs +88 -0
- package/package.json +9 -9
package/dist/client/index.d.mts
CHANGED
|
@@ -110,6 +110,20 @@ interface AttachDevtoolsOptions {
|
|
|
110
110
|
*/
|
|
111
111
|
declare function attachDevtools(client: FlagsClientBrowser, opts?: AttachDevtoolsOptions): () => void;
|
|
112
112
|
/** Configure the singleton. Idempotent — re-calling with the same opts is a no-op. */
|
|
113
|
+
interface ShipeasyClientConfig {
|
|
114
|
+
/** SDK key — same value used on the server via shipeasy(). */
|
|
115
|
+
apiKey: string;
|
|
116
|
+
/** Override the ShipEasy CDN/edge base URL. Defaults to https://cdn.shipeasy.ai. */
|
|
117
|
+
baseUrl?: string;
|
|
118
|
+
/** Override the admin URL for the devtools overlay (dev use). */
|
|
119
|
+
adminUrl?: string;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Initialise the ShipEasy client SDK and wire up lazy devtools.
|
|
123
|
+
* Call this once at app startup (e.g. in a useEffect in your root layout).
|
|
124
|
+
* Returns a cleanup function — call it on unmount to remove event listeners.
|
|
125
|
+
*/
|
|
126
|
+
declare function shipeasy(opts: ShipeasyClientConfig): () => void;
|
|
113
127
|
declare function configureShipeasy(opts: FlagsClientBrowserOptions): FlagsClientBrowser;
|
|
114
128
|
/** Returns the configured singleton, or null if configureShipeasy() hasn't run yet. */
|
|
115
129
|
declare function getShipeasyClient(): FlagsClientBrowser | null;
|
|
@@ -126,6 +140,9 @@ interface BootstrapPayload {
|
|
|
126
140
|
group: string;
|
|
127
141
|
params: Record<string, unknown>;
|
|
128
142
|
}>;
|
|
143
|
+
/** Set by getBootstrapHtml() for auto-init. Not part of evaluate() output. */
|
|
144
|
+
apiKey?: string;
|
|
145
|
+
apiUrl?: string;
|
|
129
146
|
}
|
|
130
147
|
/**
|
|
131
148
|
* Universal flags facade. Methods return safe defaults when the singleton
|
|
@@ -137,10 +154,10 @@ declare const flags: {
|
|
|
137
154
|
identify(user: User): Promise<void>;
|
|
138
155
|
/**
|
|
139
156
|
* Read a feature gate.
|
|
140
|
-
* Priority:
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
157
|
+
* Priority: bootstrap → CDN/URL-override (post-mount) → false.
|
|
158
|
+
* Bootstrap is safe before mount because the server rendered with the same values.
|
|
159
|
+
* Everything else gates on _mountedAndReady to prevent hydration mismatches on
|
|
160
|
+
* force-static pages where SSR has no flag data.
|
|
144
161
|
*/
|
|
145
162
|
get(name: string): boolean;
|
|
146
163
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
@@ -150,7 +167,11 @@ declare const flags: {
|
|
|
150
167
|
/**
|
|
151
168
|
* Called by FlagsBoundary after React hydration to unlock flag reads.
|
|
152
169
|
* Dispatches se:override:change so subscribers (FlagsBoundary) re-render
|
|
153
|
-
* once with real values — URL overrides and
|
|
170
|
+
* once with real values — URL overrides and CDN-loaded flags.
|
|
171
|
+
*
|
|
172
|
+
* Always dispatches even if already mounted: in React hydration-recovery
|
|
173
|
+
* renders the latch is already true so the early-return guard would swallow
|
|
174
|
+
* the event, leaving the re-mounted subtree stuck with stale (empty) values.
|
|
154
175
|
*/
|
|
155
176
|
notifyMounted(): void;
|
|
156
177
|
/** Subscribe for change notifications (identify/override). Used by framework adapters. */
|
|
@@ -202,4 +223,4 @@ declare const i18n: {
|
|
|
202
223
|
onUpdate(cb: () => void): () => void;
|
|
203
224
|
};
|
|
204
225
|
|
|
205
|
-
export { type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, version };
|
|
226
|
+
export { type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, shipeasy, version };
|
package/dist/client/index.d.ts
CHANGED
|
@@ -110,6 +110,20 @@ interface AttachDevtoolsOptions {
|
|
|
110
110
|
*/
|
|
111
111
|
declare function attachDevtools(client: FlagsClientBrowser, opts?: AttachDevtoolsOptions): () => void;
|
|
112
112
|
/** Configure the singleton. Idempotent — re-calling with the same opts is a no-op. */
|
|
113
|
+
interface ShipeasyClientConfig {
|
|
114
|
+
/** SDK key — same value used on the server via shipeasy(). */
|
|
115
|
+
apiKey: string;
|
|
116
|
+
/** Override the ShipEasy CDN/edge base URL. Defaults to https://cdn.shipeasy.ai. */
|
|
117
|
+
baseUrl?: string;
|
|
118
|
+
/** Override the admin URL for the devtools overlay (dev use). */
|
|
119
|
+
adminUrl?: string;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Initialise the ShipEasy client SDK and wire up lazy devtools.
|
|
123
|
+
* Call this once at app startup (e.g. in a useEffect in your root layout).
|
|
124
|
+
* Returns a cleanup function — call it on unmount to remove event listeners.
|
|
125
|
+
*/
|
|
126
|
+
declare function shipeasy(opts: ShipeasyClientConfig): () => void;
|
|
113
127
|
declare function configureShipeasy(opts: FlagsClientBrowserOptions): FlagsClientBrowser;
|
|
114
128
|
/** Returns the configured singleton, or null if configureShipeasy() hasn't run yet. */
|
|
115
129
|
declare function getShipeasyClient(): FlagsClientBrowser | null;
|
|
@@ -126,6 +140,9 @@ interface BootstrapPayload {
|
|
|
126
140
|
group: string;
|
|
127
141
|
params: Record<string, unknown>;
|
|
128
142
|
}>;
|
|
143
|
+
/** Set by getBootstrapHtml() for auto-init. Not part of evaluate() output. */
|
|
144
|
+
apiKey?: string;
|
|
145
|
+
apiUrl?: string;
|
|
129
146
|
}
|
|
130
147
|
/**
|
|
131
148
|
* Universal flags facade. Methods return safe defaults when the singleton
|
|
@@ -137,10 +154,10 @@ declare const flags: {
|
|
|
137
154
|
identify(user: User): Promise<void>;
|
|
138
155
|
/**
|
|
139
156
|
* Read a feature gate.
|
|
140
|
-
* Priority:
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
157
|
+
* Priority: bootstrap → CDN/URL-override (post-mount) → false.
|
|
158
|
+
* Bootstrap is safe before mount because the server rendered with the same values.
|
|
159
|
+
* Everything else gates on _mountedAndReady to prevent hydration mismatches on
|
|
160
|
+
* force-static pages where SSR has no flag data.
|
|
144
161
|
*/
|
|
145
162
|
get(name: string): boolean;
|
|
146
163
|
getConfig<T = unknown>(name: string, decode?: (raw: unknown) => T): T | undefined;
|
|
@@ -150,7 +167,11 @@ declare const flags: {
|
|
|
150
167
|
/**
|
|
151
168
|
* Called by FlagsBoundary after React hydration to unlock flag reads.
|
|
152
169
|
* Dispatches se:override:change so subscribers (FlagsBoundary) re-render
|
|
153
|
-
* once with real values — URL overrides and
|
|
170
|
+
* once with real values — URL overrides and CDN-loaded flags.
|
|
171
|
+
*
|
|
172
|
+
* Always dispatches even if already mounted: in React hydration-recovery
|
|
173
|
+
* renders the latch is already true so the early-return guard would swallow
|
|
174
|
+
* the event, leaving the re-mounted subtree stuck with stale (empty) values.
|
|
154
175
|
*/
|
|
155
176
|
notifyMounted(): void;
|
|
156
177
|
/** Subscribe for change notifications (identify/override). Used by framework adapters. */
|
|
@@ -202,4 +223,4 @@ declare const i18n: {
|
|
|
202
223
|
onUpdate(cb: () => void): () => void;
|
|
203
224
|
};
|
|
204
225
|
|
|
205
|
-
export { type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, version };
|
|
226
|
+
export { type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, shipeasy, version };
|
package/dist/client/index.js
CHANGED
|
@@ -38,6 +38,7 @@ __export(client_exports, {
|
|
|
38
38
|
readConfigOverride: () => readConfigOverride,
|
|
39
39
|
readExpOverride: () => readExpOverride,
|
|
40
40
|
readGateOverride: () => readGateOverride,
|
|
41
|
+
shipeasy: () => shipeasy,
|
|
41
42
|
version: () => version
|
|
42
43
|
});
|
|
43
44
|
module.exports = __toCommonJS(client_exports);
|
|
@@ -647,6 +648,14 @@ function attachDevtools(client, opts = {}) {
|
|
|
647
648
|
};
|
|
648
649
|
}
|
|
649
650
|
var _client = null;
|
|
651
|
+
function shipeasy(opts) {
|
|
652
|
+
const client = configureShipeasy({
|
|
653
|
+
sdkKey: opts.apiKey,
|
|
654
|
+
baseUrl: opts.baseUrl ?? "https://cdn.shipeasy.ai"
|
|
655
|
+
});
|
|
656
|
+
flags.notifyMounted();
|
|
657
|
+
return attachDevtools(client, { adminUrl: opts.adminUrl });
|
|
658
|
+
}
|
|
650
659
|
function configureShipeasy(opts) {
|
|
651
660
|
if (_client) return _client;
|
|
652
661
|
_client = new FlagsClientBrowser(opts);
|
|
@@ -686,30 +695,19 @@ var flags = {
|
|
|
686
695
|
},
|
|
687
696
|
/**
|
|
688
697
|
* Read a feature gate.
|
|
689
|
-
* Priority:
|
|
690
|
-
*
|
|
691
|
-
*
|
|
692
|
-
*
|
|
698
|
+
* Priority: bootstrap → CDN/URL-override (post-mount) → false.
|
|
699
|
+
* Bootstrap is safe before mount because the server rendered with the same values.
|
|
700
|
+
* Everything else gates on _mountedAndReady to prevent hydration mismatches on
|
|
701
|
+
* force-static pages where SSR has no flag data.
|
|
693
702
|
*/
|
|
694
703
|
get(name) {
|
|
695
|
-
const ov = readGateOverride(name);
|
|
696
|
-
if (ov !== null) return ov;
|
|
697
704
|
const bs = getBootstrap();
|
|
698
705
|
if (bs !== null && name in bs.flags) return bs.flags[name];
|
|
699
706
|
if (!_mountedAndReady) return false;
|
|
700
707
|
if (_client) return _client.getFlag(name);
|
|
701
|
-
return false;
|
|
708
|
+
return readGateOverride(name) ?? false;
|
|
702
709
|
},
|
|
703
710
|
getConfig(name, decode) {
|
|
704
|
-
const ov = readConfigOverride(name);
|
|
705
|
-
if (ov !== void 0) {
|
|
706
|
-
if (!decode) return ov;
|
|
707
|
-
try {
|
|
708
|
-
return decode(ov);
|
|
709
|
-
} catch {
|
|
710
|
-
return void 0;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
711
|
const bs = getBootstrap();
|
|
714
712
|
if (bs !== null && name in bs.configs) {
|
|
715
713
|
const raw = bs.configs[name];
|
|
@@ -722,7 +720,14 @@ var flags = {
|
|
|
722
720
|
}
|
|
723
721
|
if (!_mountedAndReady) return void 0;
|
|
724
722
|
if (_client) return _client.getConfig(name, decode);
|
|
725
|
-
|
|
723
|
+
const ov = readConfigOverride(name);
|
|
724
|
+
if (ov === void 0) return void 0;
|
|
725
|
+
if (!decode) return ov;
|
|
726
|
+
try {
|
|
727
|
+
return decode(ov);
|
|
728
|
+
} catch {
|
|
729
|
+
return void 0;
|
|
730
|
+
}
|
|
726
731
|
},
|
|
727
732
|
getExperiment(name, defaultParams, decode, variants) {
|
|
728
733
|
return _client?.getExperiment(name, defaultParams, decode, variants) ?? {
|
|
@@ -740,10 +745,13 @@ var flags = {
|
|
|
740
745
|
/**
|
|
741
746
|
* Called by FlagsBoundary after React hydration to unlock flag reads.
|
|
742
747
|
* Dispatches se:override:change so subscribers (FlagsBoundary) re-render
|
|
743
|
-
* once with real values — URL overrides and
|
|
748
|
+
* once with real values — URL overrides and CDN-loaded flags.
|
|
749
|
+
*
|
|
750
|
+
* Always dispatches even if already mounted: in React hydration-recovery
|
|
751
|
+
* renders the latch is already true so the early-return guard would swallow
|
|
752
|
+
* the event, leaving the re-mounted subtree stuck with stale (empty) values.
|
|
744
753
|
*/
|
|
745
754
|
notifyMounted() {
|
|
746
|
-
if (_mountedAndReady) return;
|
|
747
755
|
_mountedAndReady = true;
|
|
748
756
|
if (typeof window !== "undefined") {
|
|
749
757
|
window.dispatchEvent(new CustomEvent("se:override:change"));
|
|
@@ -775,9 +783,19 @@ function labelAttrs(key, variables, desc) {
|
|
|
775
783
|
return attrs;
|
|
776
784
|
}
|
|
777
785
|
var _createElement = null;
|
|
786
|
+
var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
|
|
787
|
+
function getSSRI18nStore() {
|
|
788
|
+
return globalThis[_I18N_SSR_SYM]?.() ?? null;
|
|
789
|
+
}
|
|
790
|
+
function interpolate(raw, variables) {
|
|
791
|
+
if (!variables) return raw;
|
|
792
|
+
return raw.replace(/\{\{(\w+)\}\}/g, (_, k) => String(variables[k] ?? `{{${k}}}`));
|
|
793
|
+
}
|
|
778
794
|
var i18n = {
|
|
779
795
|
t(key, variables) {
|
|
780
796
|
if (typeof window !== "undefined" && window.i18n) return window.i18n.t(key, variables);
|
|
797
|
+
const store = getSSRI18nStore();
|
|
798
|
+
if (store?.strings[key]) return interpolate(store.strings[key], variables);
|
|
781
799
|
return key;
|
|
782
800
|
},
|
|
783
801
|
/**
|
|
@@ -793,7 +811,8 @@ var i18n = {
|
|
|
793
811
|
* configured (e.g. server-side or in non-JSX contexts).
|
|
794
812
|
*/
|
|
795
813
|
tEl(key, fallback, variables, desc) {
|
|
796
|
-
const
|
|
814
|
+
const hasTranslation = typeof window !== "undefined" && Boolean(window.i18n) || Boolean(getSSRI18nStore()?.strings[key]);
|
|
815
|
+
const text = hasTranslation ? this.t(key, variables) || fallback : fallback;
|
|
797
816
|
if (!_createElement) return text;
|
|
798
817
|
return _createElement("span", labelAttrs(key, variables, desc), text);
|
|
799
818
|
},
|
|
@@ -836,6 +855,12 @@ var i18n = {
|
|
|
836
855
|
};
|
|
837
856
|
}
|
|
838
857
|
};
|
|
858
|
+
if (typeof window !== "undefined") {
|
|
859
|
+
const _initBs = window.__SE_BOOTSTRAP;
|
|
860
|
+
if (_initBs?.apiKey && !_client) {
|
|
861
|
+
shipeasy({ apiKey: _initBs.apiKey, baseUrl: _initBs.apiUrl });
|
|
862
|
+
}
|
|
863
|
+
}
|
|
839
864
|
// Annotate the CommonJS export names for ESM import in node:
|
|
840
865
|
0 && (module.exports = {
|
|
841
866
|
FlagsClientBrowser,
|
|
@@ -856,5 +881,6 @@ var i18n = {
|
|
|
856
881
|
readConfigOverride,
|
|
857
882
|
readExpOverride,
|
|
858
883
|
readGateOverride,
|
|
884
|
+
shipeasy,
|
|
859
885
|
version
|
|
860
886
|
});
|
package/dist/client/index.mjs
CHANGED
|
@@ -605,6 +605,14 @@ function attachDevtools(client, opts = {}) {
|
|
|
605
605
|
};
|
|
606
606
|
}
|
|
607
607
|
var _client = null;
|
|
608
|
+
function shipeasy(opts) {
|
|
609
|
+
const client = configureShipeasy({
|
|
610
|
+
sdkKey: opts.apiKey,
|
|
611
|
+
baseUrl: opts.baseUrl ?? "https://cdn.shipeasy.ai"
|
|
612
|
+
});
|
|
613
|
+
flags.notifyMounted();
|
|
614
|
+
return attachDevtools(client, { adminUrl: opts.adminUrl });
|
|
615
|
+
}
|
|
608
616
|
function configureShipeasy(opts) {
|
|
609
617
|
if (_client) return _client;
|
|
610
618
|
_client = new FlagsClientBrowser(opts);
|
|
@@ -644,30 +652,19 @@ var flags = {
|
|
|
644
652
|
},
|
|
645
653
|
/**
|
|
646
654
|
* Read a feature gate.
|
|
647
|
-
* Priority:
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
655
|
+
* Priority: bootstrap → CDN/URL-override (post-mount) → false.
|
|
656
|
+
* Bootstrap is safe before mount because the server rendered with the same values.
|
|
657
|
+
* Everything else gates on _mountedAndReady to prevent hydration mismatches on
|
|
658
|
+
* force-static pages where SSR has no flag data.
|
|
651
659
|
*/
|
|
652
660
|
get(name) {
|
|
653
|
-
const ov = readGateOverride(name);
|
|
654
|
-
if (ov !== null) return ov;
|
|
655
661
|
const bs = getBootstrap();
|
|
656
662
|
if (bs !== null && name in bs.flags) return bs.flags[name];
|
|
657
663
|
if (!_mountedAndReady) return false;
|
|
658
664
|
if (_client) return _client.getFlag(name);
|
|
659
|
-
return false;
|
|
665
|
+
return readGateOverride(name) ?? false;
|
|
660
666
|
},
|
|
661
667
|
getConfig(name, decode) {
|
|
662
|
-
const ov = readConfigOverride(name);
|
|
663
|
-
if (ov !== void 0) {
|
|
664
|
-
if (!decode) return ov;
|
|
665
|
-
try {
|
|
666
|
-
return decode(ov);
|
|
667
|
-
} catch {
|
|
668
|
-
return void 0;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
668
|
const bs = getBootstrap();
|
|
672
669
|
if (bs !== null && name in bs.configs) {
|
|
673
670
|
const raw = bs.configs[name];
|
|
@@ -680,7 +677,14 @@ var flags = {
|
|
|
680
677
|
}
|
|
681
678
|
if (!_mountedAndReady) return void 0;
|
|
682
679
|
if (_client) return _client.getConfig(name, decode);
|
|
683
|
-
|
|
680
|
+
const ov = readConfigOverride(name);
|
|
681
|
+
if (ov === void 0) return void 0;
|
|
682
|
+
if (!decode) return ov;
|
|
683
|
+
try {
|
|
684
|
+
return decode(ov);
|
|
685
|
+
} catch {
|
|
686
|
+
return void 0;
|
|
687
|
+
}
|
|
684
688
|
},
|
|
685
689
|
getExperiment(name, defaultParams, decode, variants) {
|
|
686
690
|
return _client?.getExperiment(name, defaultParams, decode, variants) ?? {
|
|
@@ -698,10 +702,13 @@ var flags = {
|
|
|
698
702
|
/**
|
|
699
703
|
* Called by FlagsBoundary after React hydration to unlock flag reads.
|
|
700
704
|
* Dispatches se:override:change so subscribers (FlagsBoundary) re-render
|
|
701
|
-
* once with real values — URL overrides and
|
|
705
|
+
* once with real values — URL overrides and CDN-loaded flags.
|
|
706
|
+
*
|
|
707
|
+
* Always dispatches even if already mounted: in React hydration-recovery
|
|
708
|
+
* renders the latch is already true so the early-return guard would swallow
|
|
709
|
+
* the event, leaving the re-mounted subtree stuck with stale (empty) values.
|
|
702
710
|
*/
|
|
703
711
|
notifyMounted() {
|
|
704
|
-
if (_mountedAndReady) return;
|
|
705
712
|
_mountedAndReady = true;
|
|
706
713
|
if (typeof window !== "undefined") {
|
|
707
714
|
window.dispatchEvent(new CustomEvent("se:override:change"));
|
|
@@ -733,9 +740,19 @@ function labelAttrs(key, variables, desc) {
|
|
|
733
740
|
return attrs;
|
|
734
741
|
}
|
|
735
742
|
var _createElement = null;
|
|
743
|
+
var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
|
|
744
|
+
function getSSRI18nStore() {
|
|
745
|
+
return globalThis[_I18N_SSR_SYM]?.() ?? null;
|
|
746
|
+
}
|
|
747
|
+
function interpolate(raw, variables) {
|
|
748
|
+
if (!variables) return raw;
|
|
749
|
+
return raw.replace(/\{\{(\w+)\}\}/g, (_, k) => String(variables[k] ?? `{{${k}}}`));
|
|
750
|
+
}
|
|
736
751
|
var i18n = {
|
|
737
752
|
t(key, variables) {
|
|
738
753
|
if (typeof window !== "undefined" && window.i18n) return window.i18n.t(key, variables);
|
|
754
|
+
const store = getSSRI18nStore();
|
|
755
|
+
if (store?.strings[key]) return interpolate(store.strings[key], variables);
|
|
739
756
|
return key;
|
|
740
757
|
},
|
|
741
758
|
/**
|
|
@@ -751,7 +768,8 @@ var i18n = {
|
|
|
751
768
|
* configured (e.g. server-side or in non-JSX contexts).
|
|
752
769
|
*/
|
|
753
770
|
tEl(key, fallback, variables, desc) {
|
|
754
|
-
const
|
|
771
|
+
const hasTranslation = typeof window !== "undefined" && Boolean(window.i18n) || Boolean(getSSRI18nStore()?.strings[key]);
|
|
772
|
+
const text = hasTranslation ? this.t(key, variables) || fallback : fallback;
|
|
755
773
|
if (!_createElement) return text;
|
|
756
774
|
return _createElement("span", labelAttrs(key, variables, desc), text);
|
|
757
775
|
},
|
|
@@ -794,6 +812,12 @@ var i18n = {
|
|
|
794
812
|
};
|
|
795
813
|
}
|
|
796
814
|
};
|
|
815
|
+
if (typeof window !== "undefined") {
|
|
816
|
+
const _initBs = window.__SE_BOOTSTRAP;
|
|
817
|
+
if (_initBs?.apiKey && !_client) {
|
|
818
|
+
shipeasy({ apiKey: _initBs.apiKey, baseUrl: _initBs.apiUrl });
|
|
819
|
+
}
|
|
820
|
+
}
|
|
797
821
|
export {
|
|
798
822
|
FlagsClientBrowser,
|
|
799
823
|
LABEL_MARKER_END,
|
|
@@ -813,5 +837,6 @@ export {
|
|
|
813
837
|
readConfigOverride,
|
|
814
838
|
readExpOverride,
|
|
815
839
|
readGateOverride,
|
|
840
|
+
shipeasy,
|
|
816
841
|
version
|
|
817
842
|
};
|
package/dist/server/index.d.mts
CHANGED
|
@@ -55,6 +55,32 @@ declare class FlagsClient {
|
|
|
55
55
|
*/
|
|
56
56
|
evaluate(user: User, rawUrl?: string): BootstrapPayload;
|
|
57
57
|
}
|
|
58
|
+
interface I18nForRequest {
|
|
59
|
+
strings: Record<string, string>;
|
|
60
|
+
locale: string;
|
|
61
|
+
}
|
|
62
|
+
declare const i18n: {
|
|
63
|
+
/**
|
|
64
|
+
* Fetch translation labels for the current request and store them in an
|
|
65
|
+
* async-local context so `i18n.t()` / `i18n.tEl()` in SSR'd client
|
|
66
|
+
* components return the real translated strings instead of the key.
|
|
67
|
+
*
|
|
68
|
+
* Call once per request in the root layout (or page). Failure is silent —
|
|
69
|
+
* `i18n.t()` falls back to the hardcoded fallback arg when no labels are
|
|
70
|
+
* loaded.
|
|
71
|
+
*
|
|
72
|
+
* @param key SDK client key (NEXT_PUBLIC_SHIPEASY_CLIENT_KEY)
|
|
73
|
+
* @param profile i18n profile identifier, e.g. "en:prod"
|
|
74
|
+
* @param cdnBaseUrl Optional override for the i18n CDN (default: cdn.i18n.shipeasy.ai)
|
|
75
|
+
*/
|
|
76
|
+
init(key: string, profile: string, cdnBaseUrl?: string): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Return the translation strings loaded for the current request.
|
|
79
|
+
* Use this to include i18n data in the SSR bootstrap payload so the
|
|
80
|
+
* client doesn't need an extra network round-trip.
|
|
81
|
+
*/
|
|
82
|
+
getForRequest(): I18nForRequest;
|
|
83
|
+
};
|
|
58
84
|
interface LabelFile {
|
|
59
85
|
v: number;
|
|
60
86
|
profile: string;
|
|
@@ -72,6 +98,57 @@ declare function fetchLabelsForSSR(opts: FetchLabelsOptions): Promise<LabelFile
|
|
|
72
98
|
declare function configureShipeasyServer(opts: FlagsClientOptions): FlagsClient;
|
|
73
99
|
declare function getShipeasyServerClient(): FlagsClient | null;
|
|
74
100
|
declare function _resetShipeasyServerForTests(): void;
|
|
101
|
+
interface ShipeasyServerConfig {
|
|
102
|
+
/**
|
|
103
|
+
* Server-side API key — authenticates flag/experiment fetches from the edge.
|
|
104
|
+
* Never embedded in browser output. A warning is logged if omitted.
|
|
105
|
+
*/
|
|
106
|
+
apiKey?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Public client key — embedded in window.__SE_BOOTSTRAP and used by the
|
|
109
|
+
* browser SDK. Safe to expose (e.g. NEXT_PUBLIC_ env vars).
|
|
110
|
+
* Defaults to apiKey for single-key setups.
|
|
111
|
+
*/
|
|
112
|
+
clientKey?: string;
|
|
113
|
+
/** Raw URL or query string for applying ?se_ks_* / ?se_cf_* / ?se_exp_* overrides. */
|
|
114
|
+
urlOverrides?: string;
|
|
115
|
+
/** User attributes for flag and experiment evaluation. */
|
|
116
|
+
user?: User;
|
|
117
|
+
/** i18n profile to load for SSR translations, e.g. "en:prod". Defaults to "en:prod". */
|
|
118
|
+
i18nDefaultProfile?: string;
|
|
119
|
+
}
|
|
120
|
+
interface ShipeasyServerHandle {
|
|
121
|
+
flags: Record<string, boolean>;
|
|
122
|
+
configs: Record<string, unknown>;
|
|
123
|
+
experiments: Record<string, ExperimentResult<Record<string, unknown>>>;
|
|
124
|
+
/** Returns a vanilla-JS string for a single inline <script> tag. */
|
|
125
|
+
getBootstrapHtml(): string;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Initialise the ShipEasy server SDK, evaluate flags for this request, and
|
|
129
|
+
* return a handle. Call once per request in your root layout (or page for
|
|
130
|
+
* URL-override support). Failure is non-fatal — evaluation returns empty
|
|
131
|
+
* payloads and i18n falls back to hardcoded strings.
|
|
132
|
+
*/
|
|
133
|
+
declare function shipeasy(opts: ShipeasyServerConfig): Promise<ShipeasyServerHandle>;
|
|
134
|
+
interface BootstrapHtmlOptions {
|
|
135
|
+
/** SDK client key */
|
|
136
|
+
apiKey: string;
|
|
137
|
+
/** i18n profile fed to the loader script. Defaults to "en:prod". */
|
|
138
|
+
i18nProfile?: string;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Returns a vanilla-JS script string for a single <script> tag.
|
|
142
|
+
* Handles everything the client needs at startup:
|
|
143
|
+
* - window.__se_devtools_config (when devtoolsAdminUrl is set)
|
|
144
|
+
* - window.__SE_BOOTSTRAP (flags + configs + experiments + i18n + apiKey for auto-init)
|
|
145
|
+
* - window.i18n shim from SSR strings (prevents hydration mismatches)
|
|
146
|
+
* - dynamic <script> injection for the i18n loader
|
|
147
|
+
*
|
|
148
|
+
* Framework-agnostic: set innerHTML on a <script> element, nothing else required.
|
|
149
|
+
* Pass null for bootstrap on pages without flag evaluation — client still auto-inits.
|
|
150
|
+
*/
|
|
151
|
+
declare function getBootstrapHtml(bootstrap: BootstrapPayload | null, i18nData: I18nForRequest | null, opts: BootstrapHtmlOptions): string;
|
|
75
152
|
declare const flags: {
|
|
76
153
|
configure(opts: FlagsClientOptions): void;
|
|
77
154
|
/**
|
|
@@ -95,4 +172,4 @@ declare const flags: {
|
|
|
95
172
|
evaluate(user: User, rawUrl?: string): BootstrapPayload;
|
|
96
173
|
};
|
|
97
174
|
|
|
98
|
-
export { type BootstrapPayload, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type LabelFile, type User, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getShipeasyServerClient, version };
|
|
175
|
+
export { type BootstrapHtmlOptions, type BootstrapPayload, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, shipeasy, version };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -55,6 +55,32 @@ declare class FlagsClient {
|
|
|
55
55
|
*/
|
|
56
56
|
evaluate(user: User, rawUrl?: string): BootstrapPayload;
|
|
57
57
|
}
|
|
58
|
+
interface I18nForRequest {
|
|
59
|
+
strings: Record<string, string>;
|
|
60
|
+
locale: string;
|
|
61
|
+
}
|
|
62
|
+
declare const i18n: {
|
|
63
|
+
/**
|
|
64
|
+
* Fetch translation labels for the current request and store them in an
|
|
65
|
+
* async-local context so `i18n.t()` / `i18n.tEl()` in SSR'd client
|
|
66
|
+
* components return the real translated strings instead of the key.
|
|
67
|
+
*
|
|
68
|
+
* Call once per request in the root layout (or page). Failure is silent —
|
|
69
|
+
* `i18n.t()` falls back to the hardcoded fallback arg when no labels are
|
|
70
|
+
* loaded.
|
|
71
|
+
*
|
|
72
|
+
* @param key SDK client key (NEXT_PUBLIC_SHIPEASY_CLIENT_KEY)
|
|
73
|
+
* @param profile i18n profile identifier, e.g. "en:prod"
|
|
74
|
+
* @param cdnBaseUrl Optional override for the i18n CDN (default: cdn.i18n.shipeasy.ai)
|
|
75
|
+
*/
|
|
76
|
+
init(key: string, profile: string, cdnBaseUrl?: string): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Return the translation strings loaded for the current request.
|
|
79
|
+
* Use this to include i18n data in the SSR bootstrap payload so the
|
|
80
|
+
* client doesn't need an extra network round-trip.
|
|
81
|
+
*/
|
|
82
|
+
getForRequest(): I18nForRequest;
|
|
83
|
+
};
|
|
58
84
|
interface LabelFile {
|
|
59
85
|
v: number;
|
|
60
86
|
profile: string;
|
|
@@ -72,6 +98,57 @@ declare function fetchLabelsForSSR(opts: FetchLabelsOptions): Promise<LabelFile
|
|
|
72
98
|
declare function configureShipeasyServer(opts: FlagsClientOptions): FlagsClient;
|
|
73
99
|
declare function getShipeasyServerClient(): FlagsClient | null;
|
|
74
100
|
declare function _resetShipeasyServerForTests(): void;
|
|
101
|
+
interface ShipeasyServerConfig {
|
|
102
|
+
/**
|
|
103
|
+
* Server-side API key — authenticates flag/experiment fetches from the edge.
|
|
104
|
+
* Never embedded in browser output. A warning is logged if omitted.
|
|
105
|
+
*/
|
|
106
|
+
apiKey?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Public client key — embedded in window.__SE_BOOTSTRAP and used by the
|
|
109
|
+
* browser SDK. Safe to expose (e.g. NEXT_PUBLIC_ env vars).
|
|
110
|
+
* Defaults to apiKey for single-key setups.
|
|
111
|
+
*/
|
|
112
|
+
clientKey?: string;
|
|
113
|
+
/** Raw URL or query string for applying ?se_ks_* / ?se_cf_* / ?se_exp_* overrides. */
|
|
114
|
+
urlOverrides?: string;
|
|
115
|
+
/** User attributes for flag and experiment evaluation. */
|
|
116
|
+
user?: User;
|
|
117
|
+
/** i18n profile to load for SSR translations, e.g. "en:prod". Defaults to "en:prod". */
|
|
118
|
+
i18nDefaultProfile?: string;
|
|
119
|
+
}
|
|
120
|
+
interface ShipeasyServerHandle {
|
|
121
|
+
flags: Record<string, boolean>;
|
|
122
|
+
configs: Record<string, unknown>;
|
|
123
|
+
experiments: Record<string, ExperimentResult<Record<string, unknown>>>;
|
|
124
|
+
/** Returns a vanilla-JS string for a single inline <script> tag. */
|
|
125
|
+
getBootstrapHtml(): string;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Initialise the ShipEasy server SDK, evaluate flags for this request, and
|
|
129
|
+
* return a handle. Call once per request in your root layout (or page for
|
|
130
|
+
* URL-override support). Failure is non-fatal — evaluation returns empty
|
|
131
|
+
* payloads and i18n falls back to hardcoded strings.
|
|
132
|
+
*/
|
|
133
|
+
declare function shipeasy(opts: ShipeasyServerConfig): Promise<ShipeasyServerHandle>;
|
|
134
|
+
interface BootstrapHtmlOptions {
|
|
135
|
+
/** SDK client key */
|
|
136
|
+
apiKey: string;
|
|
137
|
+
/** i18n profile fed to the loader script. Defaults to "en:prod". */
|
|
138
|
+
i18nProfile?: string;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Returns a vanilla-JS script string for a single <script> tag.
|
|
142
|
+
* Handles everything the client needs at startup:
|
|
143
|
+
* - window.__se_devtools_config (when devtoolsAdminUrl is set)
|
|
144
|
+
* - window.__SE_BOOTSTRAP (flags + configs + experiments + i18n + apiKey for auto-init)
|
|
145
|
+
* - window.i18n shim from SSR strings (prevents hydration mismatches)
|
|
146
|
+
* - dynamic <script> injection for the i18n loader
|
|
147
|
+
*
|
|
148
|
+
* Framework-agnostic: set innerHTML on a <script> element, nothing else required.
|
|
149
|
+
* Pass null for bootstrap on pages without flag evaluation — client still auto-inits.
|
|
150
|
+
*/
|
|
151
|
+
declare function getBootstrapHtml(bootstrap: BootstrapPayload | null, i18nData: I18nForRequest | null, opts: BootstrapHtmlOptions): string;
|
|
75
152
|
declare const flags: {
|
|
76
153
|
configure(opts: FlagsClientOptions): void;
|
|
77
154
|
/**
|
|
@@ -95,4 +172,4 @@ declare const flags: {
|
|
|
95
172
|
evaluate(user: User, rawUrl?: string): BootstrapPayload;
|
|
96
173
|
};
|
|
97
174
|
|
|
98
|
-
export { type BootstrapPayload, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type LabelFile, type User, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getShipeasyServerClient, version };
|
|
175
|
+
export { type BootstrapHtmlOptions, type BootstrapPayload, type ExperimentResult, type FetchLabelsOptions, FlagsClient, type FlagsClientEnv, type FlagsClientOptions, type I18nForRequest, type LabelFile, type ShipeasyServerConfig, type ShipeasyServerHandle, type User, _resetShipeasyServerForTests, configureShipeasyServer, fetchLabelsForSSR, flags, getBootstrapHtml, getShipeasyServerClient, i18n, shipeasy, version };
|
package/dist/server/index.js
CHANGED
|
@@ -25,7 +25,10 @@ __export(server_exports, {
|
|
|
25
25
|
configureShipeasyServer: () => configureShipeasyServer,
|
|
26
26
|
fetchLabelsForSSR: () => fetchLabelsForSSR,
|
|
27
27
|
flags: () => flags,
|
|
28
|
+
getBootstrapHtml: () => getBootstrapHtml,
|
|
28
29
|
getShipeasyServerClient: () => getShipeasyServerClient,
|
|
30
|
+
i18n: () => i18n,
|
|
31
|
+
shipeasy: () => shipeasy,
|
|
29
32
|
version: () => version
|
|
30
33
|
});
|
|
31
34
|
module.exports = __toCommonJS(server_exports);
|
|
@@ -349,6 +352,39 @@ var FlagsClient = class {
|
|
|
349
352
|
return { flags: flags2, configs, experiments };
|
|
350
353
|
}
|
|
351
354
|
};
|
|
355
|
+
var { AsyncLocalStorage } = require("async_hooks");
|
|
356
|
+
var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
|
|
357
|
+
var _i18nALS = new AsyncLocalStorage();
|
|
358
|
+
globalThis[_I18N_SSR_SYM] = () => _i18nALS.getStore() ?? null;
|
|
359
|
+
var i18n = {
|
|
360
|
+
/**
|
|
361
|
+
* Fetch translation labels for the current request and store them in an
|
|
362
|
+
* async-local context so `i18n.t()` / `i18n.tEl()` in SSR'd client
|
|
363
|
+
* components return the real translated strings instead of the key.
|
|
364
|
+
*
|
|
365
|
+
* Call once per request in the root layout (or page). Failure is silent —
|
|
366
|
+
* `i18n.t()` falls back to the hardcoded fallback arg when no labels are
|
|
367
|
+
* loaded.
|
|
368
|
+
*
|
|
369
|
+
* @param key SDK client key (NEXT_PUBLIC_SHIPEASY_CLIENT_KEY)
|
|
370
|
+
* @param profile i18n profile identifier, e.g. "en:prod"
|
|
371
|
+
* @param cdnBaseUrl Optional override for the i18n CDN (default: cdn.i18n.shipeasy.ai)
|
|
372
|
+
*/
|
|
373
|
+
async init(key, profile, cdnBaseUrl) {
|
|
374
|
+
if (_i18nALS.getStore() !== void 0) return;
|
|
375
|
+
const labels = await fetchLabelsForSSR({ key, profile, cdnBaseUrl }).catch(() => null);
|
|
376
|
+
const locale = profile.split(":")[0] || "en";
|
|
377
|
+
_i18nALS.enterWith({ strings: labels?.strings ?? {}, locale });
|
|
378
|
+
},
|
|
379
|
+
/**
|
|
380
|
+
* Return the translation strings loaded for the current request.
|
|
381
|
+
* Use this to include i18n data in the SSR bootstrap payload so the
|
|
382
|
+
* client doesn't need an extra network round-trip.
|
|
383
|
+
*/
|
|
384
|
+
getForRequest() {
|
|
385
|
+
return _i18nALS.getStore() ?? { strings: {}, locale: "en" };
|
|
386
|
+
}
|
|
387
|
+
};
|
|
352
388
|
var DEFAULT_I18N_CDN = "https://cdn.i18n.shipeasy.ai";
|
|
353
389
|
async function fetchJson(url, timeoutMs = 2e3) {
|
|
354
390
|
const controller = new AbortController();
|
|
@@ -392,6 +428,51 @@ function _resetShipeasyServerForTests() {
|
|
|
392
428
|
_server?.destroy();
|
|
393
429
|
_server = null;
|
|
394
430
|
}
|
|
431
|
+
async function shipeasy(opts) {
|
|
432
|
+
if (!opts.apiKey && !opts.clientKey) {
|
|
433
|
+
console.warn("[shipeasy] apiKey is required \u2014 flag evaluation and i18n will not load.");
|
|
434
|
+
} else if (!opts.apiKey) {
|
|
435
|
+
console.warn("[shipeasy] apiKey not set \u2014 falling back to clientKey for server requests.");
|
|
436
|
+
}
|
|
437
|
+
const apiKey = opts.apiKey ?? opts.clientKey ?? "";
|
|
438
|
+
const clientKey = opts.clientKey ?? opts.apiKey ?? "";
|
|
439
|
+
const profile = opts.i18nDefaultProfile ?? "en:prod";
|
|
440
|
+
flags.configure({ apiKey });
|
|
441
|
+
await Promise.allSettled([flags.initOnce(), i18n.init(apiKey, profile)]);
|
|
442
|
+
const bootstrap = flags.evaluate(opts.user ?? {}, opts.urlOverrides);
|
|
443
|
+
const i18nData = i18n.getForRequest();
|
|
444
|
+
return {
|
|
445
|
+
flags: bootstrap.flags,
|
|
446
|
+
configs: bootstrap.configs,
|
|
447
|
+
experiments: bootstrap.experiments,
|
|
448
|
+
getBootstrapHtml() {
|
|
449
|
+
return getBootstrapHtml(bootstrap, i18nData, { apiKey: clientKey });
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function getBootstrapHtml(bootstrap, i18nData, opts) {
|
|
454
|
+
const parts = [];
|
|
455
|
+
const apiUrl = "https://cdn.shipeasy.ai";
|
|
456
|
+
const profile = opts.i18nProfile ?? "en:prod";
|
|
457
|
+
const payload = {
|
|
458
|
+
flags: bootstrap?.flags ?? {},
|
|
459
|
+
configs: bootstrap?.configs ?? {},
|
|
460
|
+
experiments: bootstrap?.experiments ?? {},
|
|
461
|
+
apiKey: opts.apiKey,
|
|
462
|
+
apiUrl
|
|
463
|
+
};
|
|
464
|
+
if (i18nData) payload.i18n = i18nData;
|
|
465
|
+
parts.push(`window.__SE_BOOTSTRAP=${JSON.stringify(payload)};`);
|
|
466
|
+
if (i18nData?.strings && Object.keys(i18nData.strings).length > 0) {
|
|
467
|
+
parts.push(
|
|
468
|
+
`(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(){};}};})();`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
parts.push(
|
|
472
|
+
`(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);})();`
|
|
473
|
+
);
|
|
474
|
+
return parts.join("");
|
|
475
|
+
}
|
|
395
476
|
var flags = {
|
|
396
477
|
configure(opts) {
|
|
397
478
|
configureShipeasyServer(opts);
|
|
@@ -445,6 +526,9 @@ var flags = {
|
|
|
445
526
|
configureShipeasyServer,
|
|
446
527
|
fetchLabelsForSSR,
|
|
447
528
|
flags,
|
|
529
|
+
getBootstrapHtml,
|
|
448
530
|
getShipeasyServerClient,
|
|
531
|
+
i18n,
|
|
532
|
+
shipeasy,
|
|
449
533
|
version
|
|
450
534
|
});
|
package/dist/server/index.mjs
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
1
8
|
// src/server/index.ts
|
|
2
9
|
var version = "1.0.0";
|
|
3
10
|
var C1 = 3432918353;
|
|
@@ -319,6 +326,39 @@ var FlagsClient = class {
|
|
|
319
326
|
return { flags: flags2, configs, experiments };
|
|
320
327
|
}
|
|
321
328
|
};
|
|
329
|
+
var { AsyncLocalStorage } = __require("async_hooks");
|
|
330
|
+
var _I18N_SSR_SYM = /* @__PURE__ */ Symbol.for("@shipeasy/sdk:ssr-i18n");
|
|
331
|
+
var _i18nALS = new AsyncLocalStorage();
|
|
332
|
+
globalThis[_I18N_SSR_SYM] = () => _i18nALS.getStore() ?? null;
|
|
333
|
+
var i18n = {
|
|
334
|
+
/**
|
|
335
|
+
* Fetch translation labels for the current request and store them in an
|
|
336
|
+
* async-local context so `i18n.t()` / `i18n.tEl()` in SSR'd client
|
|
337
|
+
* components return the real translated strings instead of the key.
|
|
338
|
+
*
|
|
339
|
+
* Call once per request in the root layout (or page). Failure is silent —
|
|
340
|
+
* `i18n.t()` falls back to the hardcoded fallback arg when no labels are
|
|
341
|
+
* loaded.
|
|
342
|
+
*
|
|
343
|
+
* @param key SDK client key (NEXT_PUBLIC_SHIPEASY_CLIENT_KEY)
|
|
344
|
+
* @param profile i18n profile identifier, e.g. "en:prod"
|
|
345
|
+
* @param cdnBaseUrl Optional override for the i18n CDN (default: cdn.i18n.shipeasy.ai)
|
|
346
|
+
*/
|
|
347
|
+
async init(key, profile, cdnBaseUrl) {
|
|
348
|
+
if (_i18nALS.getStore() !== void 0) return;
|
|
349
|
+
const labels = await fetchLabelsForSSR({ key, profile, cdnBaseUrl }).catch(() => null);
|
|
350
|
+
const locale = profile.split(":")[0] || "en";
|
|
351
|
+
_i18nALS.enterWith({ strings: labels?.strings ?? {}, locale });
|
|
352
|
+
},
|
|
353
|
+
/**
|
|
354
|
+
* Return the translation strings loaded for the current request.
|
|
355
|
+
* Use this to include i18n data in the SSR bootstrap payload so the
|
|
356
|
+
* client doesn't need an extra network round-trip.
|
|
357
|
+
*/
|
|
358
|
+
getForRequest() {
|
|
359
|
+
return _i18nALS.getStore() ?? { strings: {}, locale: "en" };
|
|
360
|
+
}
|
|
361
|
+
};
|
|
322
362
|
var DEFAULT_I18N_CDN = "https://cdn.i18n.shipeasy.ai";
|
|
323
363
|
async function fetchJson(url, timeoutMs = 2e3) {
|
|
324
364
|
const controller = new AbortController();
|
|
@@ -362,6 +402,51 @@ function _resetShipeasyServerForTests() {
|
|
|
362
402
|
_server?.destroy();
|
|
363
403
|
_server = null;
|
|
364
404
|
}
|
|
405
|
+
async function shipeasy(opts) {
|
|
406
|
+
if (!opts.apiKey && !opts.clientKey) {
|
|
407
|
+
console.warn("[shipeasy] apiKey is required \u2014 flag evaluation and i18n will not load.");
|
|
408
|
+
} else if (!opts.apiKey) {
|
|
409
|
+
console.warn("[shipeasy] apiKey not set \u2014 falling back to clientKey for server requests.");
|
|
410
|
+
}
|
|
411
|
+
const apiKey = opts.apiKey ?? opts.clientKey ?? "";
|
|
412
|
+
const clientKey = opts.clientKey ?? opts.apiKey ?? "";
|
|
413
|
+
const profile = opts.i18nDefaultProfile ?? "en:prod";
|
|
414
|
+
flags.configure({ apiKey });
|
|
415
|
+
await Promise.allSettled([flags.initOnce(), i18n.init(apiKey, profile)]);
|
|
416
|
+
const bootstrap = flags.evaluate(opts.user ?? {}, opts.urlOverrides);
|
|
417
|
+
const i18nData = i18n.getForRequest();
|
|
418
|
+
return {
|
|
419
|
+
flags: bootstrap.flags,
|
|
420
|
+
configs: bootstrap.configs,
|
|
421
|
+
experiments: bootstrap.experiments,
|
|
422
|
+
getBootstrapHtml() {
|
|
423
|
+
return getBootstrapHtml(bootstrap, i18nData, { apiKey: clientKey });
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function getBootstrapHtml(bootstrap, i18nData, opts) {
|
|
428
|
+
const parts = [];
|
|
429
|
+
const apiUrl = "https://cdn.shipeasy.ai";
|
|
430
|
+
const profile = opts.i18nProfile ?? "en:prod";
|
|
431
|
+
const payload = {
|
|
432
|
+
flags: bootstrap?.flags ?? {},
|
|
433
|
+
configs: bootstrap?.configs ?? {},
|
|
434
|
+
experiments: bootstrap?.experiments ?? {},
|
|
435
|
+
apiKey: opts.apiKey,
|
|
436
|
+
apiUrl
|
|
437
|
+
};
|
|
438
|
+
if (i18nData) payload.i18n = i18nData;
|
|
439
|
+
parts.push(`window.__SE_BOOTSTRAP=${JSON.stringify(payload)};`);
|
|
440
|
+
if (i18nData?.strings && Object.keys(i18nData.strings).length > 0) {
|
|
441
|
+
parts.push(
|
|
442
|
+
`(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(){};}};})();`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
parts.push(
|
|
446
|
+
`(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);})();`
|
|
447
|
+
);
|
|
448
|
+
return parts.join("");
|
|
449
|
+
}
|
|
365
450
|
var flags = {
|
|
366
451
|
configure(opts) {
|
|
367
452
|
configureShipeasyServer(opts);
|
|
@@ -414,6 +499,9 @@ export {
|
|
|
414
499
|
configureShipeasyServer,
|
|
415
500
|
fetchLabelsForSSR,
|
|
416
501
|
flags,
|
|
502
|
+
getBootstrapHtml,
|
|
417
503
|
getShipeasyServerClient,
|
|
504
|
+
i18n,
|
|
505
|
+
shipeasy,
|
|
418
506
|
version
|
|
419
507
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipeasy/sdk",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.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",
|
|
@@ -48,13 +48,6 @@
|
|
|
48
48
|
"LICENSE",
|
|
49
49
|
"README.md"
|
|
50
50
|
],
|
|
51
|
-
"scripts": {
|
|
52
|
-
"build": "tsup",
|
|
53
|
-
"type-check": "tsc --noEmit",
|
|
54
|
-
"test": "vitest run",
|
|
55
|
-
"test:watch": "vitest",
|
|
56
|
-
"publish-loader": "wrangler r2 object put shipeasy-sdk/loader-v$npm_package_version.js --file=dist/loader/loader.global.js --content-type 'application/javascript; charset=utf-8' --cache-control 'public, max-age=31536000, immutable' --remote && wrangler r2 object put shipeasy-sdk/loader.js --file=dist/loader/loader.global.js --content-type 'application/javascript; charset=utf-8' --cache-control 'public, max-age=300' --remote"
|
|
57
|
-
},
|
|
58
51
|
"dependencies": {
|
|
59
52
|
"murmurhash-js": "^1.0.0"
|
|
60
53
|
},
|
|
@@ -78,5 +71,12 @@
|
|
|
78
71
|
},
|
|
79
72
|
"engines": {
|
|
80
73
|
"node": ">=20"
|
|
74
|
+
},
|
|
75
|
+
"scripts": {
|
|
76
|
+
"build": "tsup",
|
|
77
|
+
"type-check": "tsc --noEmit",
|
|
78
|
+
"test": "vitest run",
|
|
79
|
+
"test:watch": "vitest",
|
|
80
|
+
"publish-loader": "wrangler r2 object put shipeasy-sdk/loader-v$npm_package_version.js --file=dist/loader/loader.global.js --content-type 'application/javascript; charset=utf-8' --cache-control 'public, max-age=31536000, immutable' --remote && wrangler r2 object put shipeasy-sdk/loader.js --file=dist/loader/loader.global.js --content-type 'application/javascript; charset=utf-8' --cache-control 'public, max-age=300' --remote"
|
|
81
81
|
}
|
|
82
|
-
}
|
|
82
|
+
}
|