@marsaude/devtools-shell 0.1.1 → 0.1.2
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.
|
@@ -1138,6 +1138,11 @@ class DevtoolsApiService {
|
|
|
1138
1138
|
this.config = inject(DEVTOOLS_CONFIG);
|
|
1139
1139
|
this.utils = inject(DevtoolsUtils);
|
|
1140
1140
|
this.typeform = inject(DevtoolsTypeform);
|
|
1141
|
+
// ---- reCAPTCHA (v2 Invisible) -----------------------------------------
|
|
1142
|
+
/** Cached invisible-widget id: rendered once, re-executed per request. */
|
|
1143
|
+
this.recaptchaWidgetId = null;
|
|
1144
|
+
/** Resolver for the in-flight execute() — the widget callback fires this. */
|
|
1145
|
+
this.recaptchaResolver = null;
|
|
1141
1146
|
}
|
|
1142
1147
|
get api() {
|
|
1143
1148
|
return this.config.apiBaseUrl;
|
|
@@ -1296,14 +1301,43 @@ class DevtoolsApiService {
|
|
|
1296
1301
|
return of([]);
|
|
1297
1302
|
return forkJoin(valid.map((id) => this.deleteAuthStatement(id)));
|
|
1298
1303
|
}
|
|
1299
|
-
// ---- reCAPTCHA --------------------------------------------------------
|
|
1300
1304
|
recaptcha() {
|
|
1301
1305
|
const key = this.config.recaptchaSiteKey;
|
|
1302
|
-
|
|
1303
|
-
|
|
1306
|
+
if (!key) {
|
|
1307
|
+
console.warn('[devtools] recaptchaSiteKey not configured — request will be sent without a reCAPTCHA token');
|
|
1304
1308
|
return of('');
|
|
1305
|
-
|
|
1306
|
-
|
|
1309
|
+
}
|
|
1310
|
+
return from(ensureRecaptchaScript()
|
|
1311
|
+
.then((grecaptcha) => new Promise((resolve) => {
|
|
1312
|
+
// v2 Invisible delivers the token via callback, not a promise.
|
|
1313
|
+
// Point the shared resolver at THIS request before executing.
|
|
1314
|
+
this.recaptchaResolver = (token) => {
|
|
1315
|
+
this.recaptchaResolver = null;
|
|
1316
|
+
try {
|
|
1317
|
+
grecaptcha.reset(this.recaptchaWidgetId);
|
|
1318
|
+
}
|
|
1319
|
+
catch {
|
|
1320
|
+
/* ignore */
|
|
1321
|
+
}
|
|
1322
|
+
resolve(token || '');
|
|
1323
|
+
};
|
|
1324
|
+
if (this.recaptchaWidgetId == null) {
|
|
1325
|
+
const host = document.createElement('div');
|
|
1326
|
+
host.style.display = 'none';
|
|
1327
|
+
document.body.appendChild(host);
|
|
1328
|
+
this.recaptchaWidgetId = grecaptcha.render(host, {
|
|
1329
|
+
sitekey: key,
|
|
1330
|
+
size: 'invisible',
|
|
1331
|
+
callback: (token) => this.recaptchaResolver?.(token),
|
|
1332
|
+
'error-callback': () => this.recaptchaResolver?.(''),
|
|
1333
|
+
'expired-callback': () => this.recaptchaResolver?.(''),
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
grecaptcha.execute(this.recaptchaWidgetId);
|
|
1337
|
+
}))
|
|
1338
|
+
.catch((e) => {
|
|
1339
|
+
console.error('[devtools] failed to load reCAPTCHA', e);
|
|
1340
|
+
return '';
|
|
1307
1341
|
}));
|
|
1308
1342
|
}
|
|
1309
1343
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DevtoolsApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -1312,6 +1346,42 @@ class DevtoolsApiService {
|
|
|
1312
1346
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DevtoolsApiService, decorators: [{
|
|
1313
1347
|
type: Injectable
|
|
1314
1348
|
}] });
|
|
1349
|
+
const RECAPTCHA_SCRIPT_ID = 'dc-recaptcha-v2-invisible';
|
|
1350
|
+
/**
|
|
1351
|
+
* Lazy-load the reCAPTCHA v2 (explicit render) script (once) and resolve with
|
|
1352
|
+
* the `grecaptcha` API. The host only passes `recaptchaSiteKey`; the package
|
|
1353
|
+
* injects the script itself so nothing depends on the host having it loaded.
|
|
1354
|
+
*/
|
|
1355
|
+
function ensureRecaptchaScript() {
|
|
1356
|
+
const w = window;
|
|
1357
|
+
if (w.grecaptcha?.render)
|
|
1358
|
+
return Promise.resolve(w.grecaptcha);
|
|
1359
|
+
return new Promise((resolve, reject) => {
|
|
1360
|
+
const onReady = () => {
|
|
1361
|
+
if (w.grecaptcha?.render)
|
|
1362
|
+
resolve(w.grecaptcha);
|
|
1363
|
+
else
|
|
1364
|
+
reject(new Error('grecaptcha unavailable after script load'));
|
|
1365
|
+
};
|
|
1366
|
+
const existing = document.getElementById(RECAPTCHA_SCRIPT_ID);
|
|
1367
|
+
if (existing) {
|
|
1368
|
+
if (w.grecaptcha?.render)
|
|
1369
|
+
return resolve(w.grecaptcha);
|
|
1370
|
+
existing.addEventListener('load', onReady, { once: true });
|
|
1371
|
+
existing.addEventListener('error', () => reject(new Error('reCAPTCHA script failed to load')), { once: true });
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
const script = document.createElement('script');
|
|
1375
|
+
script.id = RECAPTCHA_SCRIPT_ID;
|
|
1376
|
+
// `render=explicit` so we control the (invisible) widget creation ourselves.
|
|
1377
|
+
script.src = 'https://www.google.com/recaptcha/api.js?render=explicit';
|
|
1378
|
+
script.async = true;
|
|
1379
|
+
script.defer = true;
|
|
1380
|
+
script.onload = onReady;
|
|
1381
|
+
script.onerror = () => reject(new Error('reCAPTCHA script failed to load'));
|
|
1382
|
+
document.head.appendChild(script);
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1315
1385
|
function onlyDigits(v) {
|
|
1316
1386
|
return (v ?? '').replace(/\D/g, '');
|
|
1317
1387
|
}
|