@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 +18 -15
- package/dist/{chunk-FROJCNV7.umd.cjs → chunk-D2H2PTN2.umd.cjs} +134 -4
- package/dist/chunk-D2H2PTN2.umd.cjs.map +1 -0
- package/dist/{chunk-X3WTVBZ6.mjs → chunk-YYR2BH3L.mjs} +132 -2
- package/dist/chunk-YYR2BH3L.mjs.map +1 -0
- package/dist/{edit-launcher-PTZ5BIO2.mjs → edit-launcher-YJB2FBO7.mjs} +118 -74
- package/dist/edit-launcher-YJB2FBO7.mjs.map +1 -0
- package/dist/{edit-launcher-DB2DJSQJ.umd.cjs → edit-launcher-Z5AGCAQB.umd.cjs} +120 -76
- package/dist/edit-launcher-Z5AGCAQB.umd.cjs.map +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +7 -3
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.cjs +19 -15
- package/dist/index.umd.cjs.map +1 -1
- package/dist/{overlay-MLOXYRPA.umd.cjs → overlay-WO46AIRO.umd.cjs} +461 -143
- package/dist/overlay-WO46AIRO.umd.cjs.map +1 -0
- package/dist/{overlay-7KC2RRGB.mjs → overlay-Y3NQFQBI.mjs} +454 -136
- package/dist/overlay-Y3NQFQBI.mjs.map +1 -0
- package/package.json +6 -6
- package/dist/chunk-FROJCNV7.umd.cjs.map +0 -1
- package/dist/chunk-X3WTVBZ6.mjs.map +0 -1
- package/dist/edit-launcher-DB2DJSQJ.umd.cjs.map +0 -1
- package/dist/edit-launcher-PTZ5BIO2.mjs.map +0 -1
- package/dist/overlay-7KC2RRGB.mjs.map +0 -1
- package/dist/overlay-MLOXYRPA.umd.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @stringpush/sdk
|
|
2
2
|
|
|
3
|
-
Framework-agnostic plain JavaScript SDK for
|
|
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: "
|
|
22
|
-
origin: "
|
|
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
|
-
|
|
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
|
|
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).
|
|
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",
|
|
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
|
-
|
|
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
|
|
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
|
|
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 |
|
|
129
|
-
| CORS errors on manifest or overlay |
|
|
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: [
|
|
135
|
+
Full staging setup: [Staging overlay](https://docs.platform.stringpush.com/integration/overlay-staging).
|
|
133
136
|
|
|
134
|
-
See [
|
|
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 (
|
|
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 (
|
|
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-
|
|
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-
|
|
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":[]}
|