@stringpush/sdk 0.2.0 → 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 +7 -6
- 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 +1 -1
- 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
|
@@ -58,14 +58,15 @@ Overlay edit mode is intended for **staging** only unless your organization expl
|
|
|
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
|
|
|
@@ -120,15 +121,15 @@ Report vulnerabilities to [security@stringpush.com](mailto:security@stringpush.c
|
|
|
120
121
|
|
|
121
122
|
| Symptom | What to check |
|
|
122
123
|
|---------|----------------|
|
|
123
|
-
| Manifest `401` / `403` | Runtime API key; `Origin` header vs allowlisted domains |
|
|
124
|
-
| 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 |
|
|
125
126
|
| `edit token required` | Pass `editToken`, or `?translation_edit=1&edit_token=` |
|
|
126
127
|
| `edit token is malformed` | JWT truncated in URL — use `encodeURIComponent` |
|
|
127
128
|
| `environment does not match` | `init({ environment })` must match the edit session environment |
|
|
128
129
|
| `401` on overlay save | Edit JWT expired — re-issue via `POST /v1/auth/edit-session` |
|
|
129
130
|
| `409` on save | Another editor saved first — use Reload in the panel |
|
|
130
131
|
| Live updates missing | Confirm staging overlay and realtime are enabled for your org; same project/environment as the edit session |
|
|
131
|
-
| CORS errors on manifest or overlay |
|
|
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) |
|
|
132
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 |
|
|
133
134
|
|
|
134
135
|
Full staging setup: [Staging overlay](https://docs.platform.stringpush.com/integration/overlay-staging).
|
|
@@ -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":[]}
|