@stringpush/sdk 0.1.1 → 0.3.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @stringpush/sdk
2
2
 
3
- Framework-agnostic plain JavaScript SDK for the Translation Platform.
3
+ Framework-agnostic plain JavaScript SDK for StringPush.
4
4
 
5
5
  ## Install
6
6
 
@@ -18,8 +18,8 @@ await init({
18
18
  environment: "staging",
19
19
  locale: "en",
20
20
  apiKey: "trt_…",
21
- apiBaseUrl: "http://localhost:3000",
22
- origin: "http://localhost:5173",
21
+ apiBaseUrl: "https://api.platform.stringpush.com",
22
+ origin: "https://your-app.example.com",
23
23
  onLocaleChange: () => app.rerender(),
24
24
  onTranslationsUpdated: () => app.rerender(),
25
25
  });
@@ -46,26 +46,27 @@ Enable when your plan includes MAU metering. No PII is sent — only a stable ha
46
46
 
47
47
  `t(key, values)` supports ICU MessageFormat (plural, select) and `{name}` interpolation via `intl-messageformat`. Configure missing-key behavior with `missingKeyFallback` (`key`, `default_message`, or `empty`) and optional `defaultMessages` at init.
48
48
 
49
- See [examples/vanilla](../../examples/vanilla/) for a runnable demo.
49
+ Try the [hosted sample app](https://sample.platform.stringpush.com) or follow [Getting started](https://docs.platform.stringpush.com/getting-started).
50
50
 
51
51
  ### Staging vs production
52
52
 
53
53
  Pass `environment: "staging"` or `environment: "production"` in `init()`. The manifest and bundle URLs are scoped per environment (`/bundles/{projectId}/staging/…` vs `…/production/…`). Use **staging** for pre-release copy and overlay edit mode; use **production** for live customer traffic after promote + publish in admin.
54
54
 
55
- Overlay edit mode is intended for **staging** only unless your organization explicitly enables production overlay (see platform `docs/CONTEXT.md` D-014).
55
+ Overlay edit mode is intended for **staging** only unless your organization explicitly enables production overlay in admin Settings (Business+).
56
56
 
57
57
  ## Edit mode (staging overlay)
58
58
 
59
59
  ### Recommended: edit launcher (M2-SDK-05)
60
60
 
61
- Arm with `?translation_edit=1` only — a **Translate** FAB appears (staging). Users sign in, then overlay starts on click. JWT is stored in `sessionStorage`, not left in the query string.
61
+ Arm with `?translation_edit=1` only — a **Translate** FAB appears (staging). Click **Translate** to open an admin popup for sign-in (or enable overlay immediately when a stored edit token exists). JWT is stored in `sessionStorage`, not left in the query string.
62
62
 
63
63
  ```ts
64
64
  await init({
65
65
  /* … */
66
66
  environment: "staging",
67
67
  editLauncher: {
68
- signInUrl: "https://admin.example.com/overlay-grant", // redirect auth
68
+ signInUrl: "https://admin.example.com/overlay-grant",
69
+ // signInMode: "redirect" — full-page navigation instead of popup
69
70
  // or requestEditToken: async () => { … } // demo/local only
70
71
  },
71
72
  });
@@ -73,7 +74,7 @@ await init({
73
74
 
74
75
  Optional shared secret: `?translation_edit=1&translation_edit_key=<armingKey>` when `editLauncher.armingKey` is set.
75
76
 
76
- After SSO, redirect back with `#edit_token=<jwt>`; the SDK persists it and strips the hash from the URL.
77
+ When already signed into admin, the popup mints an edit token and closes automatically. Otherwise complete login in the popup; the customer page stays open.
77
78
 
78
79
  ### Magic link (auto-enable)
79
80
 
@@ -111,24 +112,26 @@ While edit mode is active, the overlay WebSocket applies `translation.updated` (
111
112
 
112
113
  - **Runtime key** (`trt_…`): read-only; safe in customer frontends; restrict via application **allowed domains**.
113
114
  - **Edit token**: short-lived; overlay/write only; never commit or ship in production bundles.
114
- - **Environment**: use `staging` for overlay QA; production edit requires the organization to enable **production overlay** in admin Settings (Business+), or the API env `EDIT_ON_PRODUCTION_ALLOWED=true` for local ops.
115
+ - **Environment**: use `staging` for overlay QA; production edit requires **production overlay** enabled in admin Settings (Business+).
115
116
  - **Origin**: set `origin` in `init()` to match an allowlisted domain (defaults to `window.location.origin`).
116
117
 
118
+ Report vulnerabilities to [security@stringpush.com](mailto:security@stringpush.com).
119
+
117
120
  ## Troubleshooting
118
121
 
119
122
  | Symptom | What to check |
120
123
  |---------|----------------|
121
- | Manifest `401` / `403` | Runtime API key; `Origin` header vs allowlisted domains |
122
- | Overlay `403` | Page `Origin` not in application allowlist |
124
+ | Manifest `401` / `403` | Runtime API key; `Origin` header vs **verified** allowlisted domains |
125
+ | Overlay `403` | Page `Origin` not verified for this application |
123
126
  | `edit token required` | Pass `editToken`, or `?translation_edit=1&edit_token=` |
124
127
  | `edit token is malformed` | JWT truncated in URL — use `encodeURIComponent` |
125
128
  | `environment does not match` | `init({ environment })` must match the edit session environment |
126
129
  | `401` on overlay save | Edit JWT expired — re-issue via `POST /v1/auth/edit-session` |
127
130
  | `409` on save | Another editor saved first — use Reload in the panel |
128
- | Live updates missing | Realtime gateway + `REDIS_URL`; same project/environment |
129
- | CORS errors on manifest or overlay | API must allow your origin; add hostname to application **allowed domains** |
131
+ | Live updates missing | Confirm staging overlay and realtime are enabled for your org; same project/environment as the edit session |
132
+ | CORS errors on manifest or overlay | Origin must be **verified** in admin see [Verify a browser origin](https://docs.platform.stringpush.com/integration/verify-domain) |
130
133
  | CORS errors on bundle CDN | Hosted CDN allows `*`; SDK omits runtime key on cross-origin bundle URLs. BYO CDN: set `Access-Control-Allow-Origin: *` (or your origins) on bundle GET |
131
134
 
132
- Full staging setup: [docs/integration/overlay-staging.md](../../docs/integration/overlay-staging.md).
135
+ Full staging setup: [Staging overlay](https://docs.platform.stringpush.com/integration/overlay-staging).
133
136
 
134
- See [docs/big-picture.md](../../docs/big-picture.md) for the full integration model.
137
+ See [Concepts overview](https://docs.platform.stringpush.com/concepts/overview) for the full integration model.
@@ -60,6 +60,17 @@ function readEditTokenFromStorage() {
60
60
  return null;
61
61
  }
62
62
  }
63
+ function clearEditToken() {
64
+ if (typeof globalThis === "undefined" || !("sessionStorage" in globalThis)) {
65
+ return;
66
+ }
67
+ try {
68
+ globalThis.sessionStorage.removeItem(
69
+ EDIT_TOKEN_STORAGE_KEY
70
+ );
71
+ } catch (e2) {
72
+ }
73
+ }
63
74
  function persistEditToken(token) {
64
75
  if (typeof globalThis === "undefined" || !("sessionStorage" in globalThis)) {
65
76
  return;
@@ -69,7 +80,7 @@ function persistEditToken(token) {
69
80
  EDIT_TOKEN_STORAGE_KEY,
70
81
  token
71
82
  );
72
- } catch (e2) {
83
+ } catch (e3) {
73
84
  }
74
85
  }
75
86
  function resolveEditToken(options, override) {
@@ -126,7 +137,7 @@ function decodeEditTokenClaims(token) {
126
137
  environmentId: env,
127
138
  environmentName: envName
128
139
  };
129
- } catch (e3) {
140
+ } catch (e4) {
130
141
  return null;
131
142
  }
132
143
  }
@@ -139,6 +150,125 @@ function base64UrlDecode(segment) {
139
150
  return Buffer.from(padded + pad, "base64").toString("utf8");
140
151
  }
141
152
 
153
+ // src/edit-launcher/overlay-grant-message.ts
154
+ var OVERLAY_GRANT_MESSAGE_TYPE = "stringpush:overlay-grant";
155
+ function isRecord(data) {
156
+ return data !== null && typeof data === "object";
157
+ }
158
+ function parseOverlayGrantMessage(data, expectedRequestId) {
159
+ if (!isRecord(data) || data.type !== OVERLAY_GRANT_MESSAGE_TYPE) {
160
+ return null;
161
+ }
162
+ if (data.status === "granted" && typeof data.editToken === "string") {
163
+ const requestId = typeof data.requestId === "string" ? data.requestId : "";
164
+ if (expectedRequestId && requestId !== expectedRequestId) {
165
+ return null;
166
+ }
167
+ return {
168
+ type: OVERLAY_GRANT_MESSAGE_TYPE,
169
+ status: "granted",
170
+ editToken: data.editToken,
171
+ requestId
172
+ };
173
+ }
174
+ if (data.status === "error" && typeof data.message === "string") {
175
+ const requestId = typeof data.requestId === "string" ? data.requestId : "";
176
+ if (expectedRequestId && requestId !== expectedRequestId) {
177
+ return null;
178
+ }
179
+ return {
180
+ type: OVERLAY_GRANT_MESSAGE_TYPE,
181
+ status: "error",
182
+ message: data.message,
183
+ requestId
184
+ };
185
+ }
186
+ if (!expectedRequestId && typeof data.editToken === "string" && data.status === void 0) {
187
+ return {
188
+ type: OVERLAY_GRANT_MESSAGE_TYPE,
189
+ status: "granted",
190
+ editToken: data.editToken,
191
+ requestId: ""
192
+ };
193
+ }
194
+ return null;
195
+ }
196
+
197
+ // src/edit-launcher/sign-in-popup.ts
198
+ var POPUP_NAME = "stringpush-overlay-grant";
199
+ var POPUP_FEATURES = "width=520,height=720,menubar=no,toolbar=no,location=yes,status=no";
200
+ var POPUP_CLOSED_POLL_MS = 500;
201
+ function newRequestId() {
202
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
203
+ return crypto.randomUUID();
204
+ }
205
+ return `sp-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
206
+ }
207
+ function adminOriginFromSignInUrl(signInUrl) {
208
+ return new URL(signInUrl).origin;
209
+ }
210
+ function buildOverlayGrantUrl(signInUrl, params) {
211
+ const search = new URLSearchParams();
212
+ search.set("returnUrl", params.returnUrl);
213
+ search.set("applicationId", params.applicationId);
214
+ search.set("environment", params.environment);
215
+ search.set("mode", _nullishCoalesce(params.mode, () => ( "popup")));
216
+ if (params.requestId) {
217
+ search.set("requestId", params.requestId);
218
+ }
219
+ const separator = signInUrl.includes("?") ? "&" : "?";
220
+ return `${signInUrl}${separator}${search.toString()}`;
221
+ }
222
+ function openOverlayGrantPopup(url) {
223
+ try {
224
+ const popup = window.open(url, POPUP_NAME, POPUP_FEATURES);
225
+ if (popup) {
226
+ popup.focus();
227
+ }
228
+ return popup;
229
+ } catch (e5) {
230
+ return null;
231
+ }
232
+ }
233
+ function watchPopupClosed(popup, onClosed) {
234
+ const timer = window.setInterval(() => {
235
+ if (popup.closed) {
236
+ window.clearInterval(timer);
237
+ onClosed();
238
+ }
239
+ }, POPUP_CLOSED_POLL_MS);
240
+ return () => window.clearInterval(timer);
241
+ }
242
+ function listenForOverlayGrant(adminOrigin, getExpectedRequestId, listener) {
243
+ const handler = (event) => {
244
+ if (event.origin !== adminOrigin) {
245
+ return;
246
+ }
247
+ const parsed = parseOverlayGrantMessage(event.data, getExpectedRequestId());
248
+ if (!parsed) {
249
+ return;
250
+ }
251
+ if (parsed.status === "granted") {
252
+ listener.onGranted(parsed.editToken);
253
+ } else {
254
+ listener.onError(parsed.message);
255
+ }
256
+ };
257
+ window.addEventListener("message", handler);
258
+ return () => window.removeEventListener("message", handler);
259
+ }
260
+ function redirectToOverlayGrant(url) {
261
+ window.location.href = url;
262
+ }
263
+
264
+
265
+
266
+
267
+
268
+
269
+
270
+
271
+
142
272
 
143
273
 
144
274
 
@@ -149,5 +279,5 @@ function base64UrlDecode(segment) {
149
279
 
150
280
 
151
281
 
152
- exports.readEditTokenFromQuery = readEditTokenFromQuery; exports.readEditTokenFromHash = readEditTokenFromHash; exports.isEditLauncherArmed = isEditLauncherArmed; exports.readEditTokenFromStorage = readEditTokenFromStorage; exports.persistEditToken = persistEditToken; exports.resolveEditToken = resolveEditToken; exports.stripEditTokenFromUrl = stripEditTokenFromUrl; exports.shouldAutoEnableEditMode = shouldAutoEnableEditMode; exports.decodeEditTokenClaims = decodeEditTokenClaims;
153
- //# sourceMappingURL=chunk-FROJCNV7.umd.cjs.map
282
+ exports.readEditTokenFromQuery = readEditTokenFromQuery; exports.readEditTokenFromHash = readEditTokenFromHash; exports.isEditLauncherArmed = isEditLauncherArmed; exports.readEditTokenFromStorage = readEditTokenFromStorage; exports.clearEditToken = clearEditToken; exports.persistEditToken = persistEditToken; exports.resolveEditToken = resolveEditToken; exports.stripEditTokenFromUrl = stripEditTokenFromUrl; exports.shouldAutoEnableEditMode = shouldAutoEnableEditMode; exports.decodeEditTokenClaims = decodeEditTokenClaims; exports.newRequestId = newRequestId; exports.adminOriginFromSignInUrl = adminOriginFromSignInUrl; exports.buildOverlayGrantUrl = buildOverlayGrantUrl; exports.openOverlayGrantPopup = openOverlayGrantPopup; exports.watchPopupClosed = watchPopupClosed; exports.listenForOverlayGrant = listenForOverlayGrant; exports.redirectToOverlayGrant = redirectToOverlayGrant;
283
+ //# sourceMappingURL=chunk-D2H2PTN2.umd.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/translations/translations/packages/sdk/dist/chunk-D2H2PTN2.umd.cjs","../src/edit-token.ts","../src/edit-token-decode.ts","../src/edit-launcher/overlay-grant-message.ts","../src/edit-launcher/sign-in-popup.ts"],"names":[],"mappings":"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","file":"/home/runner/work/translations/translations/packages/sdk/dist/chunk-D2H2PTN2.umd.cjs","sourcesContent":[null,"/**\n * Resolves overlay edit JWT from init options, URL, or sessionStorage (M2-SDK-01).\n */\nimport type { InitOptions } from \"./types.js\";\n\nexport const EDIT_TOKEN_QUERY_PARAM = \"edit_token\";\nexport const EDIT_MODE_QUERY_PARAM = \"translation_edit\";\nexport const EDIT_LAUNCHER_KEY_QUERY_PARAM = \"translation_edit_key\";\nexport const EDIT_TOKEN_STORAGE_KEY = \"@translation/edit-token\";\n\nexport function isEditModeQueryEnabled(): boolean {\n if (typeof globalThis === \"undefined\" || !(\"location\" in globalThis)) {\n return false;\n }\n const params = new URLSearchParams(\n (globalThis as Window & typeof globalThis).location.search,\n );\n const value = params.get(EDIT_MODE_QUERY_PARAM);\n return value === \"1\" || value === \"true\";\n}\n\nexport function readEditTokenFromQuery(): string | null {\n if (typeof globalThis === \"undefined\" || !(\"location\" in globalThis)) {\n return null;\n }\n const params = new URLSearchParams(\n (globalThis as Window & typeof globalThis).location.search,\n );\n return params.get(EDIT_TOKEN_QUERY_PARAM);\n}\n\nexport function readEditTokenFromHash(): string | null {\n if (typeof globalThis === \"undefined\" || !(\"location\" in globalThis)) {\n return null;\n }\n const hash = (globalThis as Window & typeof globalThis).location.hash;\n if (!hash || hash.length <= 1) {\n return null;\n }\n const params = new URLSearchParams(hash.startsWith(\"#\") ? hash.slice(1) : hash);\n return params.get(EDIT_TOKEN_QUERY_PARAM);\n}\n\nexport function isEditLauncherArmed(launcher?: { armingKey?: string }): boolean {\n if (!isEditModeQueryEnabled()) {\n return false;\n }\n if (!launcher?.armingKey) {\n return true;\n }\n if (typeof globalThis === \"undefined\" || !(\"location\" in globalThis)) {\n return false;\n }\n const params = new URLSearchParams(\n (globalThis as Window & typeof globalThis).location.search,\n );\n return params.get(EDIT_LAUNCHER_KEY_QUERY_PARAM) === launcher.armingKey;\n}\n\nexport function readEditTokenFromStorage(): string | null {\n if (typeof globalThis === \"undefined\" || !(\"sessionStorage\" in globalThis)) {\n return null;\n }\n try {\n return (\n (globalThis as Window & typeof globalThis).sessionStorage.getItem(\n EDIT_TOKEN_STORAGE_KEY,\n ) ?? null\n );\n } catch {\n return null;\n }\n}\n\nexport function clearEditToken(): void {\n if (typeof globalThis === \"undefined\" || !(\"sessionStorage\" in globalThis)) {\n return;\n }\n try {\n (globalThis as Window & typeof globalThis).sessionStorage.removeItem(\n EDIT_TOKEN_STORAGE_KEY,\n );\n } catch {\n // Intent: best-effort clear when storage is blocked.\n }\n}\n\nexport function persistEditToken(token: string): void {\n if (typeof globalThis === \"undefined\" || !(\"sessionStorage\" in globalThis)) {\n return;\n }\n try {\n (globalThis as Window & typeof globalThis).sessionStorage.setItem(\n EDIT_TOKEN_STORAGE_KEY,\n token,\n );\n } catch {\n // Intent: private mode or blocked storage — overlay still works for this page load.\n }\n}\n\nexport function resolveEditToken(\n options: InitOptions,\n override?: string,\n): string | null {\n const token =\n override ??\n options.editToken ??\n readEditTokenFromQuery() ??\n readEditTokenFromHash() ??\n readEditTokenFromStorage();\n if (token) {\n persistEditToken(token);\n }\n return token;\n}\n\n/**\n * Removes edit JWT from the URL after persisting to sessionStorage (M2-SDK-05).\n */\nexport function stripEditTokenFromUrl(): void {\n if (typeof globalThis === \"undefined\" || !(\"location\" in globalThis)) {\n return;\n }\n const win = globalThis as Window & typeof globalThis;\n const url = new URL(win.location.href);\n url.searchParams.delete(EDIT_TOKEN_QUERY_PARAM);\n if (url.hash) {\n const hashParams = new URLSearchParams(url.hash.slice(1));\n if (hashParams.has(EDIT_TOKEN_QUERY_PARAM)) {\n hashParams.delete(EDIT_TOKEN_QUERY_PARAM);\n const nextHash = hashParams.toString();\n url.hash = nextHash ? `#${nextHash}` : \"\";\n }\n }\n const next = `${url.pathname}${url.search}${url.hash}`;\n win.history.replaceState(win.history.state, \"\", next);\n}\n\nexport function shouldAutoEnableEditMode(options: InitOptions): boolean {\n if (options.autoEnableEditFromQuery === false) {\n return false;\n }\n return isEditModeQueryEnabled() && resolveEditToken(options) !== null;\n}\n","/**\n * Reads edit JWT claims without verification (M2-SDK-04).\n *\n * Intent: token was already issued by our API; overlay uses claims for env scoping only.\n */\nexport type EditTokenClaims = {\n projectId: string;\n applicationId: string;\n environmentId: string;\n environmentName: string;\n};\n\nexport function decodeEditTokenClaims(token: string): EditTokenClaims | null {\n const parts = token.split(\".\");\n if (parts.length < 2) {\n return null;\n }\n\n try {\n const payloadJson = base64UrlDecode(parts[1]!);\n const payload = JSON.parse(payloadJson) as Record<string, unknown>;\n const project = payload.project;\n const app = payload.app;\n const env = payload.env;\n const envName = payload.envName;\n if (\n typeof project !== \"string\" ||\n typeof app !== \"string\" ||\n typeof env !== \"string\" ||\n typeof envName !== \"string\"\n ) {\n return null;\n }\n return {\n projectId: project,\n applicationId: app,\n environmentId: env,\n environmentName: envName,\n };\n } catch {\n return null;\n }\n}\n\nfunction base64UrlDecode(segment: string): string {\n const padded = segment.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const pad = padded.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (padded.length % 4));\n if (typeof atob === \"function\") {\n return atob(padded + pad);\n }\n return Buffer.from(padded + pad, \"base64\").toString(\"utf8\");\n}\n","/**\n * Overlay grant postMessage payload — shared contract with admin grant page (M7-SSO-02).\n *\n * Intent: discriminated status + requestId so stale popup messages cannot satisfy a newer attempt.\n */\nexport const OVERLAY_GRANT_MESSAGE_TYPE = \"stringpush:overlay-grant\";\n\nexport type OverlayGrantGrantedMessage = {\n type: typeof OVERLAY_GRANT_MESSAGE_TYPE;\n status: \"granted\";\n editToken: string;\n requestId: string;\n};\n\nexport type OverlayGrantErrorMessage = {\n type: typeof OVERLAY_GRANT_MESSAGE_TYPE;\n status: \"error\";\n message: string;\n requestId: string;\n};\n\nexport type OverlayGrantMessage = OverlayGrantGrantedMessage | OverlayGrantErrorMessage;\n\n/** Legacy token-only payload from earlier SDK builds. */\nexport type LegacyOverlayGrantMessage = {\n type: typeof OVERLAY_GRANT_MESSAGE_TYPE;\n editToken: string;\n};\n\nfunction isRecord(data: unknown): data is Record<string, unknown> {\n return data !== null && typeof data === \"object\";\n}\n\nexport function parseOverlayGrantMessage(\n data: unknown,\n expectedRequestId: string | null,\n): OverlayGrantMessage | null {\n if (!isRecord(data) || data.type !== OVERLAY_GRANT_MESSAGE_TYPE) {\n return null;\n }\n\n if (data.status === \"granted\" && typeof data.editToken === \"string\") {\n const requestId = typeof data.requestId === \"string\" ? data.requestId : \"\";\n if (expectedRequestId && requestId !== expectedRequestId) {\n return null;\n }\n return {\n type: OVERLAY_GRANT_MESSAGE_TYPE,\n status: \"granted\",\n editToken: data.editToken,\n requestId,\n };\n }\n\n if (data.status === \"error\" && typeof data.message === \"string\") {\n const requestId = typeof data.requestId === \"string\" ? data.requestId : \"\";\n if (expectedRequestId && requestId !== expectedRequestId) {\n return null;\n }\n return {\n type: OVERLAY_GRANT_MESSAGE_TYPE,\n status: \"error\",\n message: data.message,\n requestId,\n };\n }\n\n // Intent: transitional support for legacy { type, editToken } messages without requestId.\n if (\n !expectedRequestId &&\n typeof data.editToken === \"string\" &&\n data.status === undefined\n ) {\n return {\n type: OVERLAY_GRANT_MESSAGE_TYPE,\n status: \"granted\",\n editToken: data.editToken,\n requestId: \"\",\n };\n }\n\n return null;\n}\n","/**\n * Popup sign-in for overlay grant — keeps the customer page open (M7-SSO-02).\n *\n * Intent: overlay-grant posts edit JWT to opener; falls back to full redirect when popup is blocked.\n */\nimport { parseOverlayGrantMessage, OVERLAY_GRANT_MESSAGE_TYPE } from \"./overlay-grant-message.js\";\n\nconst POPUP_NAME = \"stringpush-overlay-grant\";\nconst POPUP_FEATURES = \"width=520,height=720,menubar=no,toolbar=no,location=yes,status=no\";\nconst POPUP_CLOSED_POLL_MS = 500;\n\nexport type OverlayGrantMode = \"popup\" | \"redirect\";\n\nexport type OverlayGrantListener = {\n onGranted: (editToken: string) => void;\n onError: (message: string) => void;\n};\n\nexport function newRequestId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n return `sp-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n\nexport function adminOriginFromSignInUrl(signInUrl: string): string {\n return new URL(signInUrl).origin;\n}\n\nexport function buildOverlayGrantUrl(\n signInUrl: string,\n params: {\n returnUrl: string;\n applicationId: string;\n environment: string;\n mode?: OverlayGrantMode;\n requestId?: string;\n },\n): string {\n const search = new URLSearchParams();\n search.set(\"returnUrl\", params.returnUrl);\n search.set(\"applicationId\", params.applicationId);\n search.set(\"environment\", params.environment);\n search.set(\"mode\", params.mode ?? \"popup\");\n if (params.requestId) {\n search.set(\"requestId\", params.requestId);\n }\n const separator = signInUrl.includes(\"?\") ? \"&\" : \"?\";\n return `${signInUrl}${separator}${search.toString()}`;\n}\n\nexport function openOverlayGrantPopup(url: string): Window | null {\n try {\n const popup = window.open(url, POPUP_NAME, POPUP_FEATURES);\n if (popup) {\n popup.focus();\n }\n return popup;\n } catch {\n return null;\n }\n}\n\nexport function watchPopupClosed(popup: Window, onClosed: () => void): () => void {\n const timer = window.setInterval(() => {\n if (popup.closed) {\n window.clearInterval(timer);\n onClosed();\n }\n }, POPUP_CLOSED_POLL_MS);\n return () => window.clearInterval(timer);\n}\n\nexport function listenForOverlayGrant(\n adminOrigin: string,\n getExpectedRequestId: () => string | null,\n listener: OverlayGrantListener,\n): () => void {\n const handler = (event: MessageEvent) => {\n if (event.origin !== adminOrigin) {\n return;\n }\n const parsed = parseOverlayGrantMessage(event.data, getExpectedRequestId());\n if (!parsed) {\n return;\n }\n if (parsed.status === \"granted\") {\n listener.onGranted(parsed.editToken);\n } else {\n listener.onError(parsed.message);\n }\n };\n window.addEventListener(\"message\", handler);\n return () => window.removeEventListener(\"message\", handler);\n}\n\nexport function redirectToOverlayGrant(url: string): void {\n window.location.href = url;\n}\n\n/** Re-export for admin parity when documenting the contract. */\nexport { OVERLAY_GRANT_MESSAGE_TYPE };\n"]}
@@ -60,6 +60,17 @@ function readEditTokenFromStorage() {
60
60
  return null;
61
61
  }
62
62
  }
63
+ function clearEditToken() {
64
+ if (typeof globalThis === "undefined" || !("sessionStorage" in globalThis)) {
65
+ return;
66
+ }
67
+ try {
68
+ globalThis.sessionStorage.removeItem(
69
+ EDIT_TOKEN_STORAGE_KEY
70
+ );
71
+ } catch {
72
+ }
73
+ }
63
74
  function persistEditToken(token) {
64
75
  if (typeof globalThis === "undefined" || !("sessionStorage" in globalThis)) {
65
76
  return;
@@ -139,15 +150,134 @@ function base64UrlDecode(segment) {
139
150
  return Buffer.from(padded + pad, "base64").toString("utf8");
140
151
  }
141
152
 
153
+ // src/edit-launcher/overlay-grant-message.ts
154
+ var OVERLAY_GRANT_MESSAGE_TYPE = "stringpush:overlay-grant";
155
+ function isRecord(data) {
156
+ return data !== null && typeof data === "object";
157
+ }
158
+ function parseOverlayGrantMessage(data, expectedRequestId) {
159
+ if (!isRecord(data) || data.type !== OVERLAY_GRANT_MESSAGE_TYPE) {
160
+ return null;
161
+ }
162
+ if (data.status === "granted" && typeof data.editToken === "string") {
163
+ const requestId = typeof data.requestId === "string" ? data.requestId : "";
164
+ if (expectedRequestId && requestId !== expectedRequestId) {
165
+ return null;
166
+ }
167
+ return {
168
+ type: OVERLAY_GRANT_MESSAGE_TYPE,
169
+ status: "granted",
170
+ editToken: data.editToken,
171
+ requestId
172
+ };
173
+ }
174
+ if (data.status === "error" && typeof data.message === "string") {
175
+ const requestId = typeof data.requestId === "string" ? data.requestId : "";
176
+ if (expectedRequestId && requestId !== expectedRequestId) {
177
+ return null;
178
+ }
179
+ return {
180
+ type: OVERLAY_GRANT_MESSAGE_TYPE,
181
+ status: "error",
182
+ message: data.message,
183
+ requestId
184
+ };
185
+ }
186
+ if (!expectedRequestId && typeof data.editToken === "string" && data.status === void 0) {
187
+ return {
188
+ type: OVERLAY_GRANT_MESSAGE_TYPE,
189
+ status: "granted",
190
+ editToken: data.editToken,
191
+ requestId: ""
192
+ };
193
+ }
194
+ return null;
195
+ }
196
+
197
+ // src/edit-launcher/sign-in-popup.ts
198
+ var POPUP_NAME = "stringpush-overlay-grant";
199
+ var POPUP_FEATURES = "width=520,height=720,menubar=no,toolbar=no,location=yes,status=no";
200
+ var POPUP_CLOSED_POLL_MS = 500;
201
+ function newRequestId() {
202
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
203
+ return crypto.randomUUID();
204
+ }
205
+ return `sp-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
206
+ }
207
+ function adminOriginFromSignInUrl(signInUrl) {
208
+ return new URL(signInUrl).origin;
209
+ }
210
+ function buildOverlayGrantUrl(signInUrl, params) {
211
+ const search = new URLSearchParams();
212
+ search.set("returnUrl", params.returnUrl);
213
+ search.set("applicationId", params.applicationId);
214
+ search.set("environment", params.environment);
215
+ search.set("mode", params.mode ?? "popup");
216
+ if (params.requestId) {
217
+ search.set("requestId", params.requestId);
218
+ }
219
+ const separator = signInUrl.includes("?") ? "&" : "?";
220
+ return `${signInUrl}${separator}${search.toString()}`;
221
+ }
222
+ function openOverlayGrantPopup(url) {
223
+ try {
224
+ const popup = window.open(url, POPUP_NAME, POPUP_FEATURES);
225
+ if (popup) {
226
+ popup.focus();
227
+ }
228
+ return popup;
229
+ } catch {
230
+ return null;
231
+ }
232
+ }
233
+ function watchPopupClosed(popup, onClosed) {
234
+ const timer = window.setInterval(() => {
235
+ if (popup.closed) {
236
+ window.clearInterval(timer);
237
+ onClosed();
238
+ }
239
+ }, POPUP_CLOSED_POLL_MS);
240
+ return () => window.clearInterval(timer);
241
+ }
242
+ function listenForOverlayGrant(adminOrigin, getExpectedRequestId, listener) {
243
+ const handler = (event) => {
244
+ if (event.origin !== adminOrigin) {
245
+ return;
246
+ }
247
+ const parsed = parseOverlayGrantMessage(event.data, getExpectedRequestId());
248
+ if (!parsed) {
249
+ return;
250
+ }
251
+ if (parsed.status === "granted") {
252
+ listener.onGranted(parsed.editToken);
253
+ } else {
254
+ listener.onError(parsed.message);
255
+ }
256
+ };
257
+ window.addEventListener("message", handler);
258
+ return () => window.removeEventListener("message", handler);
259
+ }
260
+ function redirectToOverlayGrant(url) {
261
+ window.location.href = url;
262
+ }
263
+
142
264
  export {
143
265
  readEditTokenFromQuery,
144
266
  readEditTokenFromHash,
145
267
  isEditLauncherArmed,
146
268
  readEditTokenFromStorage,
269
+ clearEditToken,
147
270
  persistEditToken,
148
271
  resolveEditToken,
149
272
  stripEditTokenFromUrl,
150
273
  shouldAutoEnableEditMode,
151
- decodeEditTokenClaims
274
+ decodeEditTokenClaims,
275
+ newRequestId,
276
+ adminOriginFromSignInUrl,
277
+ buildOverlayGrantUrl,
278
+ openOverlayGrantPopup,
279
+ watchPopupClosed,
280
+ listenForOverlayGrant,
281
+ redirectToOverlayGrant
152
282
  };
153
- //# sourceMappingURL=chunk-X3WTVBZ6.mjs.map
283
+ //# sourceMappingURL=chunk-YYR2BH3L.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/edit-token.ts","../src/edit-token-decode.ts","../src/edit-launcher/overlay-grant-message.ts","../src/edit-launcher/sign-in-popup.ts"],"sourcesContent":["/**\n * Resolves overlay edit JWT from init options, URL, or sessionStorage (M2-SDK-01).\n */\nimport type { InitOptions } from \"./types.js\";\n\nexport const EDIT_TOKEN_QUERY_PARAM = \"edit_token\";\nexport const EDIT_MODE_QUERY_PARAM = \"translation_edit\";\nexport const EDIT_LAUNCHER_KEY_QUERY_PARAM = \"translation_edit_key\";\nexport const EDIT_TOKEN_STORAGE_KEY = \"@translation/edit-token\";\n\nexport function isEditModeQueryEnabled(): boolean {\n if (typeof globalThis === \"undefined\" || !(\"location\" in globalThis)) {\n return false;\n }\n const params = new URLSearchParams(\n (globalThis as Window & typeof globalThis).location.search,\n );\n const value = params.get(EDIT_MODE_QUERY_PARAM);\n return value === \"1\" || value === \"true\";\n}\n\nexport function readEditTokenFromQuery(): string | null {\n if (typeof globalThis === \"undefined\" || !(\"location\" in globalThis)) {\n return null;\n }\n const params = new URLSearchParams(\n (globalThis as Window & typeof globalThis).location.search,\n );\n return params.get(EDIT_TOKEN_QUERY_PARAM);\n}\n\nexport function readEditTokenFromHash(): string | null {\n if (typeof globalThis === \"undefined\" || !(\"location\" in globalThis)) {\n return null;\n }\n const hash = (globalThis as Window & typeof globalThis).location.hash;\n if (!hash || hash.length <= 1) {\n return null;\n }\n const params = new URLSearchParams(hash.startsWith(\"#\") ? hash.slice(1) : hash);\n return params.get(EDIT_TOKEN_QUERY_PARAM);\n}\n\nexport function isEditLauncherArmed(launcher?: { armingKey?: string }): boolean {\n if (!isEditModeQueryEnabled()) {\n return false;\n }\n if (!launcher?.armingKey) {\n return true;\n }\n if (typeof globalThis === \"undefined\" || !(\"location\" in globalThis)) {\n return false;\n }\n const params = new URLSearchParams(\n (globalThis as Window & typeof globalThis).location.search,\n );\n return params.get(EDIT_LAUNCHER_KEY_QUERY_PARAM) === launcher.armingKey;\n}\n\nexport function readEditTokenFromStorage(): string | null {\n if (typeof globalThis === \"undefined\" || !(\"sessionStorage\" in globalThis)) {\n return null;\n }\n try {\n return (\n (globalThis as Window & typeof globalThis).sessionStorage.getItem(\n EDIT_TOKEN_STORAGE_KEY,\n ) ?? null\n );\n } catch {\n return null;\n }\n}\n\nexport function clearEditToken(): void {\n if (typeof globalThis === \"undefined\" || !(\"sessionStorage\" in globalThis)) {\n return;\n }\n try {\n (globalThis as Window & typeof globalThis).sessionStorage.removeItem(\n EDIT_TOKEN_STORAGE_KEY,\n );\n } catch {\n // Intent: best-effort clear when storage is blocked.\n }\n}\n\nexport function persistEditToken(token: string): void {\n if (typeof globalThis === \"undefined\" || !(\"sessionStorage\" in globalThis)) {\n return;\n }\n try {\n (globalThis as Window & typeof globalThis).sessionStorage.setItem(\n EDIT_TOKEN_STORAGE_KEY,\n token,\n );\n } catch {\n // Intent: private mode or blocked storage — overlay still works for this page load.\n }\n}\n\nexport function resolveEditToken(\n options: InitOptions,\n override?: string,\n): string | null {\n const token =\n override ??\n options.editToken ??\n readEditTokenFromQuery() ??\n readEditTokenFromHash() ??\n readEditTokenFromStorage();\n if (token) {\n persistEditToken(token);\n }\n return token;\n}\n\n/**\n * Removes edit JWT from the URL after persisting to sessionStorage (M2-SDK-05).\n */\nexport function stripEditTokenFromUrl(): void {\n if (typeof globalThis === \"undefined\" || !(\"location\" in globalThis)) {\n return;\n }\n const win = globalThis as Window & typeof globalThis;\n const url = new URL(win.location.href);\n url.searchParams.delete(EDIT_TOKEN_QUERY_PARAM);\n if (url.hash) {\n const hashParams = new URLSearchParams(url.hash.slice(1));\n if (hashParams.has(EDIT_TOKEN_QUERY_PARAM)) {\n hashParams.delete(EDIT_TOKEN_QUERY_PARAM);\n const nextHash = hashParams.toString();\n url.hash = nextHash ? `#${nextHash}` : \"\";\n }\n }\n const next = `${url.pathname}${url.search}${url.hash}`;\n win.history.replaceState(win.history.state, \"\", next);\n}\n\nexport function shouldAutoEnableEditMode(options: InitOptions): boolean {\n if (options.autoEnableEditFromQuery === false) {\n return false;\n }\n return isEditModeQueryEnabled() && resolveEditToken(options) !== null;\n}\n","/**\n * Reads edit JWT claims without verification (M2-SDK-04).\n *\n * Intent: token was already issued by our API; overlay uses claims for env scoping only.\n */\nexport type EditTokenClaims = {\n projectId: string;\n applicationId: string;\n environmentId: string;\n environmentName: string;\n};\n\nexport function decodeEditTokenClaims(token: string): EditTokenClaims | null {\n const parts = token.split(\".\");\n if (parts.length < 2) {\n return null;\n }\n\n try {\n const payloadJson = base64UrlDecode(parts[1]!);\n const payload = JSON.parse(payloadJson) as Record<string, unknown>;\n const project = payload.project;\n const app = payload.app;\n const env = payload.env;\n const envName = payload.envName;\n if (\n typeof project !== \"string\" ||\n typeof app !== \"string\" ||\n typeof env !== \"string\" ||\n typeof envName !== \"string\"\n ) {\n return null;\n }\n return {\n projectId: project,\n applicationId: app,\n environmentId: env,\n environmentName: envName,\n };\n } catch {\n return null;\n }\n}\n\nfunction base64UrlDecode(segment: string): string {\n const padded = segment.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const pad = padded.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (padded.length % 4));\n if (typeof atob === \"function\") {\n return atob(padded + pad);\n }\n return Buffer.from(padded + pad, \"base64\").toString(\"utf8\");\n}\n","/**\n * Overlay grant postMessage payload — shared contract with admin grant page (M7-SSO-02).\n *\n * Intent: discriminated status + requestId so stale popup messages cannot satisfy a newer attempt.\n */\nexport const OVERLAY_GRANT_MESSAGE_TYPE = \"stringpush:overlay-grant\";\n\nexport type OverlayGrantGrantedMessage = {\n type: typeof OVERLAY_GRANT_MESSAGE_TYPE;\n status: \"granted\";\n editToken: string;\n requestId: string;\n};\n\nexport type OverlayGrantErrorMessage = {\n type: typeof OVERLAY_GRANT_MESSAGE_TYPE;\n status: \"error\";\n message: string;\n requestId: string;\n};\n\nexport type OverlayGrantMessage = OverlayGrantGrantedMessage | OverlayGrantErrorMessage;\n\n/** Legacy token-only payload from earlier SDK builds. */\nexport type LegacyOverlayGrantMessage = {\n type: typeof OVERLAY_GRANT_MESSAGE_TYPE;\n editToken: string;\n};\n\nfunction isRecord(data: unknown): data is Record<string, unknown> {\n return data !== null && typeof data === \"object\";\n}\n\nexport function parseOverlayGrantMessage(\n data: unknown,\n expectedRequestId: string | null,\n): OverlayGrantMessage | null {\n if (!isRecord(data) || data.type !== OVERLAY_GRANT_MESSAGE_TYPE) {\n return null;\n }\n\n if (data.status === \"granted\" && typeof data.editToken === \"string\") {\n const requestId = typeof data.requestId === \"string\" ? data.requestId : \"\";\n if (expectedRequestId && requestId !== expectedRequestId) {\n return null;\n }\n return {\n type: OVERLAY_GRANT_MESSAGE_TYPE,\n status: \"granted\",\n editToken: data.editToken,\n requestId,\n };\n }\n\n if (data.status === \"error\" && typeof data.message === \"string\") {\n const requestId = typeof data.requestId === \"string\" ? data.requestId : \"\";\n if (expectedRequestId && requestId !== expectedRequestId) {\n return null;\n }\n return {\n type: OVERLAY_GRANT_MESSAGE_TYPE,\n status: \"error\",\n message: data.message,\n requestId,\n };\n }\n\n // Intent: transitional support for legacy { type, editToken } messages without requestId.\n if (\n !expectedRequestId &&\n typeof data.editToken === \"string\" &&\n data.status === undefined\n ) {\n return {\n type: OVERLAY_GRANT_MESSAGE_TYPE,\n status: \"granted\",\n editToken: data.editToken,\n requestId: \"\",\n };\n }\n\n return null;\n}\n","/**\n * Popup sign-in for overlay grant — keeps the customer page open (M7-SSO-02).\n *\n * Intent: overlay-grant posts edit JWT to opener; falls back to full redirect when popup is blocked.\n */\nimport { parseOverlayGrantMessage, OVERLAY_GRANT_MESSAGE_TYPE } from \"./overlay-grant-message.js\";\n\nconst POPUP_NAME = \"stringpush-overlay-grant\";\nconst POPUP_FEATURES = \"width=520,height=720,menubar=no,toolbar=no,location=yes,status=no\";\nconst POPUP_CLOSED_POLL_MS = 500;\n\nexport type OverlayGrantMode = \"popup\" | \"redirect\";\n\nexport type OverlayGrantListener = {\n onGranted: (editToken: string) => void;\n onError: (message: string) => void;\n};\n\nexport function newRequestId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n return `sp-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;\n}\n\nexport function adminOriginFromSignInUrl(signInUrl: string): string {\n return new URL(signInUrl).origin;\n}\n\nexport function buildOverlayGrantUrl(\n signInUrl: string,\n params: {\n returnUrl: string;\n applicationId: string;\n environment: string;\n mode?: OverlayGrantMode;\n requestId?: string;\n },\n): string {\n const search = new URLSearchParams();\n search.set(\"returnUrl\", params.returnUrl);\n search.set(\"applicationId\", params.applicationId);\n search.set(\"environment\", params.environment);\n search.set(\"mode\", params.mode ?? \"popup\");\n if (params.requestId) {\n search.set(\"requestId\", params.requestId);\n }\n const separator = signInUrl.includes(\"?\") ? \"&\" : \"?\";\n return `${signInUrl}${separator}${search.toString()}`;\n}\n\nexport function openOverlayGrantPopup(url: string): Window | null {\n try {\n const popup = window.open(url, POPUP_NAME, POPUP_FEATURES);\n if (popup) {\n popup.focus();\n }\n return popup;\n } catch {\n return null;\n }\n}\n\nexport function watchPopupClosed(popup: Window, onClosed: () => void): () => void {\n const timer = window.setInterval(() => {\n if (popup.closed) {\n window.clearInterval(timer);\n onClosed();\n }\n }, POPUP_CLOSED_POLL_MS);\n return () => window.clearInterval(timer);\n}\n\nexport function listenForOverlayGrant(\n adminOrigin: string,\n getExpectedRequestId: () => string | null,\n listener: OverlayGrantListener,\n): () => void {\n const handler = (event: MessageEvent) => {\n if (event.origin !== adminOrigin) {\n return;\n }\n const parsed = parseOverlayGrantMessage(event.data, getExpectedRequestId());\n if (!parsed) {\n return;\n }\n if (parsed.status === \"granted\") {\n listener.onGranted(parsed.editToken);\n } else {\n listener.onError(parsed.message);\n }\n };\n window.addEventListener(\"message\", handler);\n return () => window.removeEventListener(\"message\", handler);\n}\n\nexport function redirectToOverlayGrant(url: string): void {\n window.location.href = url;\n}\n\n/** Re-export for admin parity when documenting the contract. */\nexport { OVERLAY_GRANT_MESSAGE_TYPE };\n"],"mappings":"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","names":[]}