@internetarchive/recaptcha-manager 0.0.1-alpha.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -1
- package/demo/app-root.ts +2 -2
- package/dist/demo/app-root.js +2 -2
- package/dist/demo/app-root.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/src/recaptcha-manager.d.ts +2 -2
- package/dist/src/recaptcha-manager.js +4 -4
- package/dist/src/recaptcha-manager.js.map +1 -1
- package/dist/src/recaptcha-widget.d.ts +8 -2
- package/dist/src/recaptcha-widget.js +11 -9
- package/dist/src/recaptcha-widget.js.map +1 -1
- package/dist/test/mock-grecaptcha.d.ts +24 -0
- package/dist/test/mock-grecaptcha.js +62 -0
- package/dist/test/mock-grecaptcha.js.map +1 -0
- package/dist/test/mock-lazy-loader.d.ts +15 -0
- package/dist/test/mock-lazy-loader.js +17 -0
- package/dist/test/mock-lazy-loader.js.map +1 -0
- package/dist/test/recaptcha-manager.test.d.ts +1 -0
- package/dist/test/recaptcha-manager.test.js +137 -2
- package/dist/test/recaptcha-manager.test.js.map +1 -1
- package/index.ts +1 -0
- package/package.json +4 -3
- package/src/recaptcha-manager.ts +6 -6
- package/src/recaptcha-widget.ts +23 -9
- package/test/mock-grecaptcha.ts +87 -0
- package/test/mock-lazy-loader.ts +38 -0
- package/test/recaptcha-manager.test.ts +151 -1
package/README.md
CHANGED
|
@@ -2,7 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
# Internet Archive reCaptcha Manager
|
|
4
4
|
|
|
5
|
-
A library to lazy load and
|
|
5
|
+
A library to lazy load and interact with reCaptcha
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```shell
|
|
10
|
+
> yarn add @internetarchive/recaptcha-manager
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
const recaptchaManager = new RecaptchaManager({
|
|
17
|
+
defaultSiteKey: 'your-site-key',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// will load recaptcha library if it's not loaded
|
|
21
|
+
const recaptchaWidget = await recaptchaManager.getRecaptchaWidget({
|
|
22
|
+
recaptchaParams: {
|
|
23
|
+
tabindex: 0,
|
|
24
|
+
theme: 'light',
|
|
25
|
+
type: 'image',
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const token = await recaptchaWidget.execute();
|
|
30
|
+
|
|
31
|
+
// submit token with your post and validate on the backend
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For more usage examples, see `demo/app-root.ts` and `test/recaptcha-manager.test.ts`.
|
|
6
35
|
|
|
7
36
|
## Local Demo with `web-dev-server`
|
|
8
37
|
```bash
|
package/demo/app-root.ts
CHANGED
|
@@ -13,7 +13,7 @@ export class AppRoot extends LitElement {
|
|
|
13
13
|
@state() result2?: string;
|
|
14
14
|
|
|
15
15
|
private recaptchaManager: RecaptchaManagerInterface = new RecaptchaManager({
|
|
16
|
-
defaultSiteKey: '
|
|
16
|
+
defaultSiteKey: '<your-site-key>',
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
private recaptcha1?: RecaptchaWidget;
|
|
@@ -68,7 +68,7 @@ export class AppRoot extends LitElement {
|
|
|
68
68
|
|
|
69
69
|
private async loadRecaptcha2() {
|
|
70
70
|
this.recaptcha2 = await this.recaptchaManager?.getRecaptchaWidget({
|
|
71
|
-
siteKey: '
|
|
71
|
+
siteKey: '<your-site-key>',
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
74
|
|
package/dist/demo/app-root.js
CHANGED
|
@@ -6,7 +6,7 @@ let AppRoot = class AppRoot extends LitElement {
|
|
|
6
6
|
constructor() {
|
|
7
7
|
super(...arguments);
|
|
8
8
|
this.recaptchaManager = new RecaptchaManager({
|
|
9
|
-
defaultSiteKey: '
|
|
9
|
+
defaultSiteKey: '<your-site-key>',
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
12
|
render() {
|
|
@@ -54,7 +54,7 @@ let AppRoot = class AppRoot extends LitElement {
|
|
|
54
54
|
async loadRecaptcha2() {
|
|
55
55
|
var _a;
|
|
56
56
|
this.recaptcha2 = await ((_a = this.recaptchaManager) === null || _a === void 0 ? void 0 : _a.getRecaptchaWidget({
|
|
57
|
-
siteKey: '
|
|
57
|
+
siteKey: '<your-site-key>',
|
|
58
58
|
}));
|
|
59
59
|
}
|
|
60
60
|
async executeRecaptcha2() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-root.js","sourceRoot":"","sources":["../../demo/app-root.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EACL,gBAAgB,GAEjB,MAAM,0BAA0B,CAAC;AAGlC,IAAa,OAAO,GAApB,MAAa,OAAQ,SAAQ,UAAU;IAAvC;;QAKU,qBAAgB,GAA8B,IAAI,gBAAgB,CAAC;YACzE,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"app-root.js","sourceRoot":"","sources":["../../demo/app-root.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EACL,gBAAgB,GAEjB,MAAM,0BAA0B,CAAC;AAGlC,IAAa,OAAO,GAApB,MAAa,OAAQ,SAAQ,UAAU;IAAvC;;QAKU,qBAAgB,GAA8B,IAAI,gBAAgB,CAAC;YACzE,cAAc,EAAE,iBAAiB;SAClC,CAAC,CAAC;IA4EL,CAAC;IAtEC,MAAM;QACJ,OAAO,IAAI,CAAA;;0BAEW,IAAI,CAAC,aAAa;2BACjB,IAAI,CAAC,gBAAgB;;QAExC,IAAI,CAAC,MAAM;YACX,CAAC,CAAC,IAAI,CAAA;iBACG,IAAI,CAAC,MAAM,MAAM;YAC1B,CAAC,CAAC,EAAE;;0BAEc,IAAI,CAAC,cAAc;2BAClB,IAAI,CAAC,iBAAiB;;;;QAIzC,IAAI,CAAC,OAAO;YACZ,CAAC,CAAC,IAAI,CAAA;iBACG,IAAI,CAAC,OAAO,MAAM;YAC3B,CAAC,CAAC,EAAE;KACP,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,aAAa;;QACzB,IAAI,CAAC,UAAU,GAAG,MAAM,CAAA,MAAA,IAAI,CAAC,gBAAgB,0CAAE,kBAAkB,CAAC;YAChE,eAAe,EAAE;gBACf,QAAQ,EAAE,CAAC;gBACX,KAAK,EAAE,OAAO;gBACd,IAAI,EAAE,OAAO;aACd;SACF,CAAC,CAAA,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YACpB,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;SAC5B;QAED,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YACpB,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;YACzC,OAAO;SACR;QAED,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;IAChD,CAAC;IAEO,KAAK,CAAC,cAAc;;QAC1B,IAAI,CAAC,UAAU,GAAG,MAAM,CAAA,MAAA,IAAI,CAAC,gBAAgB,0CAAE,kBAAkB,CAAC;YAChE,OAAO,EAAE,iBAAiB;SAC3B,CAAC,CAAA,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,iBAAiB;QAC7B,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YACpB,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;SAC5B;QAED,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YACpB,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;YACzC,OAAO;SACR;QAED,IAAI,CAAC,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;IACjD,CAAC;CAOF,CAAA;AALQ,cAAM,GAAG,GAAG,CAAA;;;;GAIlB,CAAC;AAjFO;IAAR,KAAK,EAAE;uCAAiB;AAEhB;IAAR,KAAK,EAAE;wCAAkB;AAHf,OAAO;IADnB,aAAa,CAAC,UAAU,CAAC;GACb,OAAO,CAmFnB;SAnFY,OAAO","sourcesContent":["import { html, css, LitElement } from 'lit';\nimport { customElement, state } from 'lit/decorators.js';\nimport { RecaptchaWidget } from '../src/recaptcha-widget';\nimport {\n RecaptchaManager,\n RecaptchaManagerInterface,\n} from '../src/recaptcha-manager';\n\n@customElement('app-root')\nexport class AppRoot extends LitElement {\n @state() result?: string;\n\n @state() result2?: string;\n\n private recaptchaManager: RecaptchaManagerInterface = new RecaptchaManager({\n defaultSiteKey: '<your-site-key>',\n });\n\n private recaptcha1?: RecaptchaWidget;\n\n private recaptcha2?: RecaptchaWidget;\n\n render() {\n return html`\n <p>\n <button @click=\"${this.loadRecaptcha}\">Load Recaptcha</button\n ><button @click=\"${this.executeRecaptcha}\">Execute Recaptcha</button>\n </p>\n ${this.result\n ? html`<p><strong>Token:</strong></p>\n <p>${this.result}</p>`\n : ''}\n <p>\n <button @click=\"${this.loadRecaptcha2}\">Load Another Recaptcha</button\n ><button @click=\"${this.executeRecaptcha2}\">\n Execute Another Recaptcha\n </button>\n </p>\n ${this.result2\n ? html`<p><strong>Token 2:</strong></p>\n <p>${this.result2}</p>`\n : ''}\n `;\n }\n\n private async loadRecaptcha() {\n this.recaptcha1 = await this.recaptchaManager?.getRecaptchaWidget({\n recaptchaParams: {\n tabindex: 0,\n theme: 'light',\n type: 'image',\n },\n });\n }\n\n private async executeRecaptcha() {\n if (!this.recaptcha1) {\n await this.loadRecaptcha();\n }\n\n if (!this.recaptcha1) {\n console.error('error loading recaptcha');\n return;\n }\n\n this.result = await this.recaptcha1.execute();\n }\n\n private async loadRecaptcha2() {\n this.recaptcha2 = await this.recaptchaManager?.getRecaptchaWidget({\n siteKey: '<your-site-key>',\n });\n }\n\n private async executeRecaptcha2() {\n if (!this.recaptcha2) {\n await this.loadRecaptcha();\n }\n\n if (!this.recaptcha2) {\n console.error('error loading recaptcha');\n return;\n }\n\n this.result2 = await this.recaptcha2.execute();\n }\n\n static styles = css`\n :host {\n display: block;\n }\n `;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { RecaptchaManager, RecaptchaManagerInterface, } from './src/recaptcha-manager';
|
|
2
|
-
export { RecaptchaWidget, RecaptchaWidgetInterface, } from './src/recaptcha-widget';
|
|
2
|
+
export { RecaptchaWidget, RecaptchaWidgetInterface, RecaptchaWidgetConfig, } from './src/recaptcha-widget';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,GAEjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,eAAe,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,GAEjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,eAAe,GAGhB,MAAM,wBAAwB,CAAC","sourcesContent":["export {\n RecaptchaManager,\n RecaptchaManagerInterface,\n} from './src/recaptcha-manager';\n\nexport {\n RecaptchaWidget,\n RecaptchaWidgetInterface,\n RecaptchaWidgetConfig,\n} from './src/recaptcha-widget';\n"]}
|
|
@@ -14,8 +14,8 @@ export declare class RecaptchaManager implements RecaptchaManagerInterface {
|
|
|
14
14
|
private lazyLoader;
|
|
15
15
|
private defaultSiteKey?;
|
|
16
16
|
private recaptchaCache;
|
|
17
|
-
constructor(options
|
|
18
|
-
defaultSiteKey
|
|
17
|
+
constructor(options?: {
|
|
18
|
+
defaultSiteKey?: string;
|
|
19
19
|
lazyLoader?: LazyLoaderServiceInterface;
|
|
20
20
|
grecaptchaLibrary?: ReCaptchaV2.ReCaptcha;
|
|
21
21
|
});
|
|
@@ -4,16 +4,16 @@ export class RecaptchaManager {
|
|
|
4
4
|
constructor(options) {
|
|
5
5
|
var _a;
|
|
6
6
|
this.recaptchaCache = {};
|
|
7
|
-
this.defaultSiteKey = options.defaultSiteKey;
|
|
8
|
-
this.lazyLoader = (_a = options.lazyLoader) !== null && _a !== void 0 ? _a : new LazyLoaderService();
|
|
9
|
-
this.grecaptchaLibraryCache = options.grecaptchaLibrary;
|
|
7
|
+
this.defaultSiteKey = options === null || options === void 0 ? void 0 : options.defaultSiteKey;
|
|
8
|
+
this.lazyLoader = (_a = options === null || options === void 0 ? void 0 : options.lazyLoader) !== null && _a !== void 0 ? _a : new LazyLoaderService();
|
|
9
|
+
this.grecaptchaLibraryCache = options === null || options === void 0 ? void 0 : options.grecaptchaLibrary;
|
|
10
10
|
}
|
|
11
11
|
/** @inheritdoc */
|
|
12
12
|
async getRecaptchaWidget(options) {
|
|
13
13
|
var _a;
|
|
14
14
|
const key = (_a = options === null || options === void 0 ? void 0 : options.siteKey) !== null && _a !== void 0 ? _a : this.defaultSiteKey;
|
|
15
15
|
if (!key) {
|
|
16
|
-
throw new Error('
|
|
16
|
+
throw new Error('The reCaptcha widget requires a site key');
|
|
17
17
|
}
|
|
18
18
|
const cached = this.recaptchaCache[key];
|
|
19
19
|
if (cached)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recaptcha-manager.js","sourceRoot":"","sources":["../../src/recaptcha-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,GAElB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAYrD,MAAM,OAAO,gBAAgB;IAO3B,YAAY,OAIX;;QANO,mBAAc,GAAoC,EAAE,CAAC;QAO3D,IAAI,CAAC,cAAc,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"recaptcha-manager.js","sourceRoot":"","sources":["../../src/recaptcha-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,GAElB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAYrD,MAAM,OAAO,gBAAgB;IAO3B,YAAY,OAIX;;QANO,mBAAc,GAAoC,EAAE,CAAC;QAO3D,IAAI,CAAC,cAAc,GAAG,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,cAAc,CAAC;QAC9C,IAAI,CAAC,UAAU,GAAG,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,UAAU,mCAAI,IAAI,iBAAiB,EAAE,CAAC;QACjE,IAAI,CAAC,sBAAsB,GAAG,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,iBAAiB,CAAC;IAC3D,CAAC;IAED,kBAAkB;IAClB,KAAK,CAAC,kBAAkB,CAAC,OAGxB;;QACC,MAAM,GAAG,GAAG,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,OAAO,mCAAI,IAAI,CAAC,cAAc,CAAC;QACpD,IAAI,CAAC,GAAG,EAAE;YACR,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;SAC7D;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAE1B,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3D,MAAM,SAAS,GAAG,IAAI,eAAe,CACnC;YACE,OAAO,EAAE,GAAG;YACZ,iBAAiB;SAClB,EACD,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,eAAe,CACzB,CAAC;QAEF,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;QACrC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,mBAAmB;QAC/B,IAAI,IAAI,CAAC,sBAAsB,EAAE;YAC/B,OAAO,IAAI,CAAC,sBAAsB,CAAC;SACpC;QACD,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;YAC1B,MAAc,CAAC,wBAAwB,GAAG,GAAS,EAAE;gBACpD,UAAU,CAAC,GAAG,EAAE;oBACd,8CAA8C;oBAC9C,OAAQ,MAAc,CAAC,wBAAwB,CAAC;gBAClD,CAAC,EAAE,EAAE,CAAC,CAAC;gBACP,IAAI,CAAC,sBAAsB,GAAG,MAAM,CAAC,UAAU,CAAC;gBAChD,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAC7B,CAAC,CAAC;YAEF,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;gBACzB,GAAG,EAAE,yFAAyF;aAC/F,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CAIF","sourcesContent":["import {\n LazyLoaderService,\n LazyLoaderServiceInterface,\n} from '@internetarchive/lazy-loader-service';\nimport { RecaptchaWidget } from './recaptcha-widget';\n\nexport interface RecaptchaManagerInterface {\n /**\n * Load a recaptcha widget for a given site key or the default site key.\n */\n getRecaptchaWidget(options?: {\n siteKey?: string;\n recaptchaParams?: ReCaptchaV2.Parameters;\n }): Promise<RecaptchaWidget>;\n}\n\nexport class RecaptchaManager implements RecaptchaManagerInterface {\n private lazyLoader: LazyLoaderServiceInterface;\n\n private defaultSiteKey?: string;\n\n private recaptchaCache: Record<string, RecaptchaWidget> = {};\n\n constructor(options?: {\n defaultSiteKey?: string;\n lazyLoader?: LazyLoaderServiceInterface;\n grecaptchaLibrary?: ReCaptchaV2.ReCaptcha; // allows dependency injection or will be lazy loaded\n }) {\n this.defaultSiteKey = options?.defaultSiteKey;\n this.lazyLoader = options?.lazyLoader ?? new LazyLoaderService();\n this.grecaptchaLibraryCache = options?.grecaptchaLibrary;\n }\n\n /** @inheritdoc */\n async getRecaptchaWidget(options?: {\n siteKey?: string;\n recaptchaParams?: ReCaptchaV2.Parameters;\n }): Promise<RecaptchaWidget> {\n const key = options?.siteKey ?? this.defaultSiteKey;\n if (!key) {\n throw new Error('The reCaptcha widget requires a site key');\n }\n\n const cached = this.recaptchaCache[key];\n if (cached) return cached;\n\n const grecaptchaLibrary = await this.getRecaptchaLibrary();\n const recaptcha = new RecaptchaWidget(\n {\n siteKey: key,\n grecaptchaLibrary,\n },\n options?.recaptchaParams\n );\n\n this.recaptchaCache[key] = recaptcha;\n return recaptcha;\n }\n\n /**\n * Load the Recaptch library from Google.\n *\n * @returns Promise<ReCaptchaV2.ReCaptcha>\n */\n private async getRecaptchaLibrary(): Promise<ReCaptchaV2.ReCaptcha> {\n if (this.grecaptchaLibraryCache) {\n return this.grecaptchaLibraryCache;\n }\n return new Promise(resolve => {\n (window as any).grecaptchaLoadedCallback = (): void => {\n setTimeout(() => {\n // remove the callback when we're done with it\n delete (window as any).grecaptchaLoadedCallback;\n }, 10);\n this.grecaptchaLibraryCache = window.grecaptcha;\n resolve(window.grecaptcha);\n };\n\n this.lazyLoader.loadScript({\n src: 'https://www.google.com/recaptcha/api.js?onload=grecaptchaLoadedCallback&render=explicit',\n });\n });\n }\n\n /** don't use directly, use `getRecaptchaLibrary()` */\n private grecaptchaLibraryCache?: ReCaptchaV2.ReCaptcha;\n}\n"]}
|
|
@@ -6,6 +6,13 @@ export interface RecaptchaWidgetInterface {
|
|
|
6
6
|
/** execute the recaptcha request and returns a token */
|
|
7
7
|
execute(): Promise<string>;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* The combines parameters from the Recaptcha config and adds the zIndex, which
|
|
11
|
+
* allows us to control the z-index of the recaptcha widget.
|
|
12
|
+
*/
|
|
13
|
+
export declare type RecaptchaWidgetConfig = Pick<ReCaptchaV2.Parameters, 'tabindex' | 'theme' | 'type' | 'size' | 'badge'> & {
|
|
14
|
+
zIndex?: number;
|
|
15
|
+
};
|
|
9
16
|
/**
|
|
10
17
|
* This encompasses a widget for a given site key.
|
|
11
18
|
*/
|
|
@@ -20,11 +27,10 @@ export declare class RecaptchaWidget implements RecaptchaWidgetInterface {
|
|
|
20
27
|
constructor(config: {
|
|
21
28
|
siteKey: string;
|
|
22
29
|
grecaptchaLibrary: ReCaptchaV2.ReCaptcha;
|
|
23
|
-
},
|
|
30
|
+
}, widgetConfig?: RecaptchaWidgetConfig);
|
|
24
31
|
/** @inheritdoc */
|
|
25
32
|
execute(): Promise<string>;
|
|
26
33
|
private finishExecution;
|
|
27
|
-
/** @inheritdoc */
|
|
28
34
|
private setup;
|
|
29
35
|
private createContainer;
|
|
30
36
|
private responseHandler;
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* This encompasses a widget for a given site key.
|
|
3
3
|
*/
|
|
4
4
|
export class RecaptchaWidget {
|
|
5
|
-
constructor(config,
|
|
5
|
+
constructor(config, widgetConfig) {
|
|
6
6
|
this.widgetId = null;
|
|
7
7
|
this.isExecuting = false;
|
|
8
8
|
this.siteKey = config.siteKey;
|
|
9
9
|
this.grecaptchaLibrary = config.grecaptchaLibrary;
|
|
10
10
|
const container = this.createContainer();
|
|
11
|
-
this.setup(container,
|
|
11
|
+
this.setup(container, widgetConfig);
|
|
12
12
|
}
|
|
13
13
|
/** @inheritdoc */
|
|
14
14
|
async execute() {
|
|
@@ -22,6 +22,7 @@ export class RecaptchaWidget {
|
|
|
22
22
|
// just reset it and execute it again
|
|
23
23
|
this.finishExecution();
|
|
24
24
|
}
|
|
25
|
+
this.isExecuting = true;
|
|
25
26
|
return new Promise((resolve, reject) => {
|
|
26
27
|
this.executionSuccessBlock = (token) => {
|
|
27
28
|
this.finishExecution();
|
|
@@ -45,21 +46,21 @@ export class RecaptchaWidget {
|
|
|
45
46
|
return;
|
|
46
47
|
this.grecaptchaLibrary.reset(widgetId);
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
-
setup(container, recaptchaParams) {
|
|
49
|
+
setup(container, widgetConfig) {
|
|
50
50
|
var _a;
|
|
51
51
|
this.widgetId = this.grecaptchaLibrary.render(container, {
|
|
52
52
|
callback: this.responseHandler.bind(this),
|
|
53
53
|
'expired-callback': this.expiredHandler.bind(this),
|
|
54
54
|
'error-callback': this.errorHandler.bind(this),
|
|
55
55
|
sitekey: this.siteKey,
|
|
56
|
-
tabindex:
|
|
57
|
-
theme:
|
|
58
|
-
type:
|
|
59
|
-
size: (_a =
|
|
56
|
+
tabindex: widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.tabindex,
|
|
57
|
+
theme: widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.theme,
|
|
58
|
+
type: widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.type,
|
|
59
|
+
size: (_a = widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.size) !== null && _a !== void 0 ? _a : 'invisible',
|
|
60
|
+
badge: widgetConfig === null || widgetConfig === void 0 ? void 0 : widgetConfig.badge,
|
|
60
61
|
});
|
|
61
62
|
}
|
|
62
|
-
createContainer() {
|
|
63
|
+
createContainer(zIndex) {
|
|
63
64
|
const elementId = `recaptchaManager-${this.siteKey}`;
|
|
64
65
|
let element = document.getElementById(elementId);
|
|
65
66
|
if (!element) {
|
|
@@ -68,6 +69,7 @@ export class RecaptchaWidget {
|
|
|
68
69
|
element.style.position = 'fixed';
|
|
69
70
|
element.style.top = '50%';
|
|
70
71
|
element.style.left = '50%';
|
|
72
|
+
element.style.zIndex = zIndex ? `${zIndex}` : '10';
|
|
71
73
|
document.body.appendChild(element);
|
|
72
74
|
}
|
|
73
75
|
return element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recaptcha-widget.js","sourceRoot":"","sources":["../../src/recaptcha-widget.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"recaptcha-widget.js","sourceRoot":"","sources":["../../src/recaptcha-widget.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * This encompasses a widget for a given site key.\n */\nexport interface RecaptchaWidgetInterface {\n /** execute the recaptcha request and returns a token */\n execute(): Promise<string>;\n}\n\n/**\n * The combines parameters from the Recaptcha config and adds the zIndex, which\n * allows us to control the z-index of the recaptcha widget.\n */\nexport type RecaptchaWidgetConfig = Pick<\n ReCaptchaV2.Parameters,\n 'tabindex' | 'theme' | 'type' | 'size' | 'badge'\n> & {\n zIndex?: number;\n};\n\n/**\n * This encompasses a widget for a given site key.\n */\nexport class RecaptchaWidget implements RecaptchaWidgetInterface {\n private executionSuccessBlock?: (token: string) => void;\n\n private executionExpiredBlock?: () => void;\n\n private executionErrorBlock?: () => void;\n\n private grecaptchaLibrary: ReCaptchaV2.ReCaptcha;\n\n private siteKey: string;\n\n private widgetId: number | null = null;\n\n private isExecuting = false;\n\n constructor(\n config: {\n siteKey: string;\n grecaptchaLibrary: ReCaptchaV2.ReCaptcha;\n },\n widgetConfig?: RecaptchaWidgetConfig\n ) {\n this.siteKey = config.siteKey;\n this.grecaptchaLibrary = config.grecaptchaLibrary;\n\n const container = this.createContainer();\n this.setup(container, widgetConfig);\n }\n\n /** @inheritdoc */\n async execute(): Promise<string> {\n const { widgetId } = this;\n\n if (widgetId === null) {\n throw new Error('Recaptcha is not setup');\n }\n\n if (this.isExecuting) {\n // there is no way to know when users have dismissed a previous recaptcha\n // so if we're still in the middle of execution when we call execute again,\n // just reset it and execute it again\n this.finishExecution();\n }\n\n this.isExecuting = true;\n\n return new Promise((resolve, reject) => {\n this.executionSuccessBlock = (token: string): void => {\n this.finishExecution();\n resolve(token);\n };\n\n this.executionExpiredBlock = (): void => {\n this.finishExecution();\n reject(new Error('expired'));\n };\n\n this.executionErrorBlock = (): void => {\n this.finishExecution();\n reject(new Error('error'));\n };\n\n this.grecaptchaLibrary.execute(widgetId);\n });\n }\n\n private finishExecution() {\n this.isExecuting = false;\n const { widgetId } = this;\n if (widgetId === null) return;\n this.grecaptchaLibrary.reset(widgetId);\n }\n\n private setup(\n container: HTMLElement,\n widgetConfig?: ReCaptchaV2.Parameters\n ): void {\n this.widgetId = this.grecaptchaLibrary.render(container, {\n callback: this.responseHandler.bind(this),\n 'expired-callback': this.expiredHandler.bind(this),\n 'error-callback': this.errorHandler.bind(this),\n sitekey: this.siteKey,\n tabindex: widgetConfig?.tabindex,\n theme: widgetConfig?.theme,\n type: widgetConfig?.type,\n size: widgetConfig?.size ?? 'invisible',\n badge: widgetConfig?.badge,\n });\n }\n\n private createContainer(zIndex?: number): HTMLElement {\n const elementId = `recaptchaManager-${this.siteKey}`;\n let element: HTMLElement | null = document.getElementById(elementId);\n if (!element) {\n element = document.createElement('div');\n element.id = elementId;\n element.style.position = 'fixed';\n element.style.top = '50%';\n element.style.left = '50%';\n element.style.zIndex = zIndex ? `${zIndex}` : '10';\n document.body.appendChild(element);\n }\n return element;\n }\n\n private responseHandler(response: any): void {\n if (this.executionSuccessBlock) {\n this.executionSuccessBlock(response);\n this.executionSuccessBlock = undefined;\n }\n }\n\n private expiredHandler(): void {\n if (this.executionExpiredBlock) {\n this.executionExpiredBlock();\n this.executionExpiredBlock = undefined;\n }\n }\n\n private errorHandler(): void {\n if (this.executionErrorBlock) {\n this.executionErrorBlock();\n this.executionErrorBlock = undefined;\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/// <reference types="grecaptcha" />
|
|
2
|
+
export declare type MockGrecaptchaMode = 'success' | 'expired' | 'error';
|
|
3
|
+
export declare class MockGrecaptcha implements ReCaptchaV2.ReCaptcha {
|
|
4
|
+
response: string;
|
|
5
|
+
renderCalled: boolean;
|
|
6
|
+
executeCalled: boolean;
|
|
7
|
+
resetCallCount: number;
|
|
8
|
+
getResponseCalled: boolean;
|
|
9
|
+
mode: MockGrecaptchaMode;
|
|
10
|
+
addDelay: boolean;
|
|
11
|
+
callback?: (response: string) => void;
|
|
12
|
+
'expired-callback'?: () => void;
|
|
13
|
+
'error-callback'?: () => void;
|
|
14
|
+
render(container: string | HTMLElement, parameters?: ReCaptchaV2.Parameters | undefined, inherit?: boolean | undefined): number;
|
|
15
|
+
reset(opt_widget_id?: number | undefined): void;
|
|
16
|
+
getResponse(opt_widget_id?: number | undefined): string;
|
|
17
|
+
execute(opt_widget_id?: number): void;
|
|
18
|
+
ready(callback: () => void): void;
|
|
19
|
+
private callCallback;
|
|
20
|
+
constructor(options: {
|
|
21
|
+
mode: MockGrecaptchaMode;
|
|
22
|
+
addDelay?: boolean;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
4
|
+
export class MockGrecaptcha {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
var _a;
|
|
7
|
+
this.response = 'foo';
|
|
8
|
+
this.renderCalled = false;
|
|
9
|
+
this.executeCalled = false;
|
|
10
|
+
this.resetCallCount = 0;
|
|
11
|
+
this.getResponseCalled = false;
|
|
12
|
+
this.mode = options.mode;
|
|
13
|
+
this.addDelay = (_a = options.addDelay) !== null && _a !== void 0 ? _a : false;
|
|
14
|
+
}
|
|
15
|
+
render(container, parameters, inherit) {
|
|
16
|
+
var _a, _b, _c;
|
|
17
|
+
this.callback = (_a = parameters === null || parameters === void 0 ? void 0 : parameters.callback) === null || _a === void 0 ? void 0 : _a.bind(this);
|
|
18
|
+
this['error-callback'] = (_b = parameters === null || parameters === void 0 ? void 0 : parameters['error-callback']) === null || _b === void 0 ? void 0 : _b.bind(this);
|
|
19
|
+
this['expired-callback'] = (_c = parameters === null || parameters === void 0 ? void 0 : parameters['expired-callback']) === null || _c === void 0 ? void 0 : _c.bind(this);
|
|
20
|
+
this.renderCalled = true;
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
reset(opt_widget_id) {
|
|
24
|
+
this.resetCallCount += 1;
|
|
25
|
+
}
|
|
26
|
+
getResponse(opt_widget_id) {
|
|
27
|
+
this.getResponseCalled = true;
|
|
28
|
+
return 'foo';
|
|
29
|
+
}
|
|
30
|
+
execute(opt_widget_id) {
|
|
31
|
+
this.executeCalled = true;
|
|
32
|
+
if (this.addDelay) {
|
|
33
|
+
setTimeout(this.callCallback.bind(this), 100);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
this.callCallback();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
ready(callback) { }
|
|
40
|
+
callCallback() {
|
|
41
|
+
switch (this.mode) {
|
|
42
|
+
case 'success':
|
|
43
|
+
if (this.callback) {
|
|
44
|
+
this.callback('foo');
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
case 'error':
|
|
48
|
+
if (this['error-callback']) {
|
|
49
|
+
this['error-callback']();
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
case 'expired':
|
|
53
|
+
if (this['expired-callback']) {
|
|
54
|
+
this['expired-callback']();
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
default:
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=mock-grecaptcha.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-grecaptcha.js","sourceRoot":"","sources":["../../test/mock-grecaptcha.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,uDAAuD;AACvD,sDAAsD;AAItD,MAAM,OAAO,cAAc;IA4EzB,YAAY,OAAyD;;QA3ErE,aAAQ,GAAG,KAAK,CAAC;QAEjB,iBAAY,GAAG,KAAK,CAAC;QAErB,kBAAa,GAAG,KAAK,CAAC;QAEtB,mBAAc,GAAG,CAAC,CAAC;QAEnB,sBAAiB,GAAG,KAAK,CAAC;QAoExB,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,MAAA,OAAO,CAAC,QAAQ,mCAAI,KAAK,CAAC;IAC5C,CAAC;IA1DD,MAAM,CACJ,SAA+B,EAC/B,UAA+C,EAC/C,OAA6B;;QAE7B,IAAI,CAAC,QAAQ,GAAG,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,QAAQ,0CAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC,gBAAgB,CAAC,GAAG,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAG,gBAAgB,CAAC,0CAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACpE,IAAI,CAAC,kBAAkB,CAAC,GAAG,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAG,kBAAkB,CAAC,0CAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACxE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,OAAO,CAAC,CAAC;IACX,CAAC;IAED,KAAK,CAAC,aAAkC;QACtC,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,WAAW,CAAC,aAAkC;QAC5C,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,CAAC,aAAsB;QAC5B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAE1B,IAAI,IAAI,CAAC,QAAQ,EAAE;YACjB,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;SAC/C;aAAM;YACL,IAAI,CAAC,YAAY,EAAE,CAAC;SACrB;IACH,CAAC;IAED,KAAK,CAAC,QAAoB,IAAS,CAAC;IAE5B,YAAY;QAClB,QAAQ,IAAI,CAAC,IAAI,EAAE;YACjB,KAAK,SAAS;gBACZ,IAAI,IAAI,CAAC,QAAQ,EAAE;oBACjB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;iBACtB;gBACD,MAAM;YACR,KAAK,OAAO;gBACV,IAAI,IAAI,CAAC,gBAAgB,CAAC,EAAE;oBAC1B,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;iBAC1B;gBACD,MAAM;YACR,KAAK,SAAS;gBACZ,IAAI,IAAI,CAAC,kBAAkB,CAAC,EAAE;oBAC5B,IAAI,CAAC,kBAAkB,CAAC,EAAE,CAAC;iBAC5B;gBACD,MAAM;YACR;gBACE,MAAM;SACT;IACH,CAAC;CAMF","sourcesContent":["/* eslint-disable camelcase */\n/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable @typescript-eslint/no-unused-vars */\n\nexport type MockGrecaptchaMode = 'success' | 'expired' | 'error';\n\nexport class MockGrecaptcha implements ReCaptchaV2.ReCaptcha {\n response = 'foo';\n\n renderCalled = false;\n\n executeCalled = false;\n\n resetCallCount = 0;\n\n getResponseCalled = false;\n\n mode: MockGrecaptchaMode;\n\n addDelay: boolean;\n\n callback?: (response: string) => void;\n\n 'expired-callback'?: () => void;\n\n 'error-callback'?: () => void;\n\n render(\n container: string | HTMLElement,\n parameters?: ReCaptchaV2.Parameters | undefined,\n inherit?: boolean | undefined\n ): number {\n this.callback = parameters?.callback?.bind(this);\n this['error-callback'] = parameters?.['error-callback']?.bind(this);\n this['expired-callback'] = parameters?.['expired-callback']?.bind(this);\n this.renderCalled = true;\n return 1;\n }\n\n reset(opt_widget_id?: number | undefined): void {\n this.resetCallCount += 1;\n }\n\n getResponse(opt_widget_id?: number | undefined): string {\n this.getResponseCalled = true;\n return 'foo';\n }\n\n execute(opt_widget_id?: number): void {\n this.executeCalled = true;\n\n if (this.addDelay) {\n setTimeout(this.callCallback.bind(this), 100);\n } else {\n this.callCallback();\n }\n }\n\n ready(callback: () => void): void {}\n\n private callCallback(): void {\n switch (this.mode) {\n case 'success':\n if (this.callback) {\n this.callback('foo');\n }\n break;\n case 'error':\n if (this['error-callback']) {\n this['error-callback']();\n }\n break;\n case 'expired':\n if (this['expired-callback']) {\n this['expired-callback']();\n }\n break;\n default:\n break;\n }\n }\n\n constructor(options: { mode: MockGrecaptchaMode; addDelay?: boolean }) {\n this.mode = options.mode;\n this.addDelay = options.addDelay ?? false;\n }\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BundleType, LazyLoaderServiceEvents, LazyLoaderServiceInterface } from '@internetarchive/lazy-loader-service';
|
|
2
|
+
import { Unsubscribe } from 'nanoevents';
|
|
3
|
+
export declare class MockLazyLoaderService implements LazyLoaderServiceInterface {
|
|
4
|
+
loadScriptSrc?: string;
|
|
5
|
+
on<E extends keyof LazyLoaderServiceEvents>(event: E, callback: LazyLoaderServiceEvents[E]): Unsubscribe;
|
|
6
|
+
loadBundle(bundle: {
|
|
7
|
+
module?: string;
|
|
8
|
+
nomodule?: string;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
loadScript(options: {
|
|
11
|
+
src: string;
|
|
12
|
+
bundleType?: BundleType;
|
|
13
|
+
attributes?: Record<string, string>;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MockGrecaptcha } from './mock-grecaptcha';
|
|
2
|
+
export class MockLazyLoaderService {
|
|
3
|
+
on(event, callback) {
|
|
4
|
+
throw new Error('Method not implemented.');
|
|
5
|
+
}
|
|
6
|
+
async loadBundle(bundle) {
|
|
7
|
+
console.debug('loadBundle', bundle);
|
|
8
|
+
}
|
|
9
|
+
async loadScript(options) {
|
|
10
|
+
this.loadScriptSrc = options.src;
|
|
11
|
+
window.grecaptcha = new MockGrecaptcha({
|
|
12
|
+
mode: 'success',
|
|
13
|
+
});
|
|
14
|
+
window.grecaptchaLoadedCallback();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=mock-lazy-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-lazy-loader.js","sourceRoot":"","sources":["../../test/mock-lazy-loader.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,OAAO,qBAAqB;IAGhC,EAAE,CACA,KAAQ,EACR,QAAoC;QAEpC,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAGhB;QACC,OAAO,CAAC,KAAK,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,OAIhB;QACC,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC;QACjC,MAAM,CAAC,UAAU,GAAG,IAAI,cAAc,CAAC;YACrC,IAAI,EAAE,SAAS;SAChB,CAAC,CAAC;QACF,MAAc,CAAC,wBAAwB,EAAE,CAAC;IAC7C,CAAC;CACF","sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\nimport {\n BundleType,\n LazyLoaderServiceEvents,\n LazyLoaderServiceInterface,\n} from '@internetarchive/lazy-loader-service';\nimport { Unsubscribe } from 'nanoevents';\nimport { MockGrecaptcha } from './mock-grecaptcha';\n\nexport class MockLazyLoaderService implements LazyLoaderServiceInterface {\n loadScriptSrc?: string;\n\n on<E extends keyof LazyLoaderServiceEvents>(\n event: E,\n callback: LazyLoaderServiceEvents[E]\n ): Unsubscribe {\n throw new Error('Method not implemented.');\n }\n\n async loadBundle(bundle: {\n module?: string;\n nomodule?: string;\n }): Promise<void> {\n console.debug('loadBundle', bundle);\n }\n\n async loadScript(options: {\n src: string;\n bundleType?: BundleType;\n attributes?: Record<string, string>;\n }): Promise<void> {\n this.loadScriptSrc = options.src;\n window.grecaptcha = new MockGrecaptcha({\n mode: 'success',\n });\n (window as any).grecaptchaLoadedCallback();\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,3 +1,138 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { MockGrecaptcha } from './mock-grecaptcha';
|
|
3
|
+
import { RecaptchaManager } from '../src/recaptcha-manager';
|
|
4
|
+
import { MockLazyLoaderService } from './mock-lazy-loader';
|
|
5
|
+
const mockLazyLoader = new MockLazyLoaderService();
|
|
6
|
+
describe('ReCaptcha Management', () => {
|
|
7
|
+
describe('ReCaptcha Manager', () => {
|
|
8
|
+
it('can return recaptchas', async () => {
|
|
9
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
10
|
+
mode: 'success',
|
|
11
|
+
});
|
|
12
|
+
mockGrecaptcha.response = 'foo';
|
|
13
|
+
const recaptchaManager = new RecaptchaManager({
|
|
14
|
+
lazyLoader: mockLazyLoader,
|
|
15
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
16
|
+
defaultSiteKey: '123',
|
|
17
|
+
});
|
|
18
|
+
const recaptcha = await recaptchaManager.getRecaptchaWidget();
|
|
19
|
+
const result = await recaptcha.execute();
|
|
20
|
+
expect(result).to.equal('foo');
|
|
21
|
+
});
|
|
22
|
+
it('throws an error if no site key found', async () => {
|
|
23
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
24
|
+
mode: 'success',
|
|
25
|
+
});
|
|
26
|
+
mockGrecaptcha.response = 'foo';
|
|
27
|
+
const recaptchaManager = new RecaptchaManager({
|
|
28
|
+
lazyLoader: mockLazyLoader,
|
|
29
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
30
|
+
});
|
|
31
|
+
try {
|
|
32
|
+
await recaptchaManager.getRecaptchaWidget();
|
|
33
|
+
expect.fail('should not get here');
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
expect(e.message).to.equal('The reCaptcha widget requires a site key');
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
it('returns a cached version if one already exists for a given site key', async () => {
|
|
40
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
41
|
+
mode: 'success',
|
|
42
|
+
});
|
|
43
|
+
mockGrecaptcha.response = 'foo';
|
|
44
|
+
const recaptchaManager = new RecaptchaManager({
|
|
45
|
+
lazyLoader: mockLazyLoader,
|
|
46
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
47
|
+
defaultSiteKey: '123',
|
|
48
|
+
});
|
|
49
|
+
const recaptcha1 = await recaptchaManager.getRecaptchaWidget();
|
|
50
|
+
const recaptcha2 = await recaptchaManager.getRecaptchaWidget();
|
|
51
|
+
expect(recaptcha1).to.equal(recaptcha2);
|
|
52
|
+
});
|
|
53
|
+
it('loads the recaptcha library if needed', async () => {
|
|
54
|
+
const recaptchaManager = new RecaptchaManager({
|
|
55
|
+
lazyLoader: mockLazyLoader,
|
|
56
|
+
defaultSiteKey: '123',
|
|
57
|
+
});
|
|
58
|
+
await recaptchaManager.getRecaptchaWidget();
|
|
59
|
+
const loadUrl = mockLazyLoader.loadScriptSrc;
|
|
60
|
+
expect(loadUrl === null || loadUrl === void 0 ? void 0 : loadUrl.includes('recaptcha/api.js?onload=grecaptchaLoadedCallback')).to.be.true;
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('ReCaptcha Widget', () => {
|
|
64
|
+
it('can error properly like a Promise', async () => {
|
|
65
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
66
|
+
mode: 'error',
|
|
67
|
+
});
|
|
68
|
+
const recaptchaManager = new RecaptchaManager({
|
|
69
|
+
lazyLoader: mockLazyLoader,
|
|
70
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
71
|
+
defaultSiteKey: '123',
|
|
72
|
+
});
|
|
73
|
+
const recaptcha = await recaptchaManager.getRecaptchaWidget();
|
|
74
|
+
try {
|
|
75
|
+
await recaptcha.execute();
|
|
76
|
+
expect.fail('should not get here');
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
expect(error.message).to.equal('error');
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
it('can expire properly like a Promise', async () => {
|
|
83
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
84
|
+
mode: 'expired',
|
|
85
|
+
});
|
|
86
|
+
const recaptchaManager = new RecaptchaManager({
|
|
87
|
+
lazyLoader: mockLazyLoader,
|
|
88
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
89
|
+
defaultSiteKey: '123',
|
|
90
|
+
});
|
|
91
|
+
const recaptcha = await recaptchaManager.getRecaptchaWidget();
|
|
92
|
+
try {
|
|
93
|
+
await recaptcha.execute();
|
|
94
|
+
expect.fail('should not get here');
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
expect(error.message).to.equal('expired');
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
it('resets the captcha after execution', async () => {
|
|
101
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
102
|
+
mode: 'success',
|
|
103
|
+
addDelay: true,
|
|
104
|
+
});
|
|
105
|
+
const recaptchaManager = new RecaptchaManager({
|
|
106
|
+
lazyLoader: mockLazyLoader,
|
|
107
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
108
|
+
defaultSiteKey: '123',
|
|
109
|
+
});
|
|
110
|
+
const recaptcha = await recaptchaManager.getRecaptchaWidget();
|
|
111
|
+
expect(mockGrecaptcha.resetCallCount).to.equal(0);
|
|
112
|
+
await recaptcha.execute();
|
|
113
|
+
expect(mockGrecaptcha.resetCallCount).to.equal(1);
|
|
114
|
+
await recaptcha.execute();
|
|
115
|
+
expect(mockGrecaptcha.resetCallCount).to.equal(2);
|
|
116
|
+
});
|
|
117
|
+
// there's no way to know if the user has dismissed the captcha so if another execution
|
|
118
|
+
// comes through while the captcha is still active, it will be reset
|
|
119
|
+
it('resets the captcha if another execution gets called', async () => {
|
|
120
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
121
|
+
mode: 'success',
|
|
122
|
+
addDelay: true,
|
|
123
|
+
});
|
|
124
|
+
const recaptchaManager = new RecaptchaManager({
|
|
125
|
+
lazyLoader: mockLazyLoader,
|
|
126
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
127
|
+
defaultSiteKey: '123',
|
|
128
|
+
});
|
|
129
|
+
const recaptcha = await recaptchaManager.getRecaptchaWidget();
|
|
130
|
+
expect(mockGrecaptcha.resetCallCount).to.equal(0);
|
|
131
|
+
recaptcha.execute();
|
|
132
|
+
expect(mockGrecaptcha.resetCallCount).to.equal(0);
|
|
133
|
+
await recaptcha.execute();
|
|
134
|
+
expect(mockGrecaptcha.resetCallCount).to.equal(2);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
3
138
|
//# sourceMappingURL=recaptcha-manager.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recaptcha-manager.test.js","sourceRoot":"","sources":["../../test/recaptcha-manager.test.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"recaptcha-manager.test.js","sourceRoot":"","sources":["../../test/recaptcha-manager.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,cAAc,GAAG,IAAI,qBAAqB,EAAE,CAAC;AAEnD,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QACjC,EAAE,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;YACrC,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC;gBACxC,IAAI,EAAE,SAAS;aAChB,CAAC,CAAC;YACH,cAAc,CAAC,QAAQ,GAAG,KAAK,CAAC;YAChC,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAAC;gBAC5C,UAAU,EAAE,cAAc;gBAC1B,iBAAiB,EAAE,cAAc;gBACjC,cAAc,EAAE,KAAK;aACtB,CAAC,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,kBAAkB,EAAE,CAAC;YAC9D,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,CAAC;YACzC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;YACpD,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC;gBACxC,IAAI,EAAE,SAAS;aAChB,CAAC,CAAC;YACH,cAAc,CAAC,QAAQ,GAAG,KAAK,CAAC;YAChC,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAAC;gBAC5C,UAAU,EAAE,cAAc;gBAC1B,iBAAiB,EAAE,cAAc;aAClC,CAAC,CAAC;YACH,IAAI;gBACF,MAAM,gBAAgB,CAAC,kBAAkB,EAAE,CAAC;gBAC5C,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;aACpC;YAAC,OAAO,CAAC,EAAE;gBACV,MAAM,CAAE,CAAW,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,KAAK,CACnC,0CAA0C,CAC3C,CAAC;aACH;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;YACnF,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC;gBACxC,IAAI,EAAE,SAAS;aAChB,CAAC,CAAC;YACH,cAAc,CAAC,QAAQ,GAAG,KAAK,CAAC;YAChC,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAAC;gBAC5C,UAAU,EAAE,cAAc;gBAC1B,iBAAiB,EAAE,cAAc;gBACjC,cAAc,EAAE,KAAK;aACtB,CAAC,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,gBAAgB,CAAC,kBAAkB,EAAE,CAAC;YAC/D,MAAM,UAAU,GAAG,MAAM,gBAAgB,CAAC,kBAAkB,EAAE,CAAC;YAC/D,MAAM,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;YACrD,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAAC;gBAC5C,UAAU,EAAE,cAAc;gBAC1B,cAAc,EAAE,KAAK;aACtB,CAAC,CAAC;YACH,MAAM,gBAAgB,CAAC,kBAAkB,EAAE,CAAC;YAC5C,MAAM,OAAO,GAAG,cAAc,CAAC,aAAa,CAAC;YAC7C,MAAM,CACJ,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ,CAAC,kDAAkD,CAAC,CACtE,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChC,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;YACjD,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC;gBACxC,IAAI,EAAE,OAAO;aACd,CAAC,CAAC;YACH,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAAC;gBAC5C,UAAU,EAAE,cAAc;gBAC1B,iBAAiB,EAAE,cAAc;gBACjC,cAAc,EAAE,KAAK;aACtB,CAAC,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,kBAAkB,EAAE,CAAC;YAE9D,IAAI;gBACF,MAAM,SAAS,CAAC,OAAO,EAAE,CAAC;gBAC1B,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;aACpC;YAAC,OAAO,KAAK,EAAE;gBACd,MAAM,CAAE,KAAe,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;aACpD;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;YAClD,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC;gBACxC,IAAI,EAAE,SAAS;aAChB,CAAC,CAAC;YACH,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAAC;gBAC5C,UAAU,EAAE,cAAc;gBAC1B,iBAAiB,EAAE,cAAc;gBACjC,cAAc,EAAE,KAAK;aACtB,CAAC,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,kBAAkB,EAAE,CAAC;YAE9D,IAAI;gBACF,MAAM,SAAS,CAAC,OAAO,EAAE,CAAC;gBAC1B,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;aACpC;YAAC,OAAO,KAAK,EAAE;gBACd,MAAM,CAAE,KAAe,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;aACtD;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;YAClD,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC;gBACxC,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,IAAI;aACf,CAAC,CAAC;YACH,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAAC;gBAC5C,UAAU,EAAE,cAAc;gBAC1B,iBAAiB,EAAE,cAAc;gBACjC,cAAc,EAAE,KAAK;aACtB,CAAC,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,kBAAkB,EAAE,CAAC;YAE9D,MAAM,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAClD,MAAM,SAAS,CAAC,OAAO,EAAE,CAAC;YAC1B,MAAM,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAClD,MAAM,SAAS,CAAC,OAAO,EAAE,CAAC;YAC1B,MAAM,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,uFAAuF;QACvF,oEAAoE;QACpE,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;YACnE,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC;gBACxC,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,IAAI;aACf,CAAC,CAAC;YACH,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAAC;gBAC5C,UAAU,EAAE,cAAc;gBAC1B,iBAAiB,EAAE,cAAc;gBACjC,cAAc,EAAE,KAAK;aACtB,CAAC,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,kBAAkB,EAAE,CAAC;YAE9D,MAAM,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAClD,SAAS,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAClD,MAAM,SAAS,CAAC,OAAO,EAAE,CAAC;YAC1B,MAAM,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { expect } from '@open-wc/testing';\nimport { MockGrecaptcha } from './mock-grecaptcha';\nimport { RecaptchaManager } from '../src/recaptcha-manager';\nimport { MockLazyLoaderService } from './mock-lazy-loader';\n\nconst mockLazyLoader = new MockLazyLoaderService();\n\ndescribe('ReCaptcha Management', () => {\n describe('ReCaptcha Manager', () => {\n it('can return recaptchas', async () => {\n const mockGrecaptcha = new MockGrecaptcha({\n mode: 'success',\n });\n mockGrecaptcha.response = 'foo';\n const recaptchaManager = new RecaptchaManager({\n lazyLoader: mockLazyLoader,\n grecaptchaLibrary: mockGrecaptcha,\n defaultSiteKey: '123',\n });\n const recaptcha = await recaptchaManager.getRecaptchaWidget();\n const result = await recaptcha.execute();\n expect(result).to.equal('foo');\n });\n\n it('throws an error if no site key found', async () => {\n const mockGrecaptcha = new MockGrecaptcha({\n mode: 'success',\n });\n mockGrecaptcha.response = 'foo';\n const recaptchaManager = new RecaptchaManager({\n lazyLoader: mockLazyLoader,\n grecaptchaLibrary: mockGrecaptcha,\n });\n try {\n await recaptchaManager.getRecaptchaWidget();\n expect.fail('should not get here');\n } catch (e) {\n expect((e as Error).message).to.equal(\n 'The reCaptcha widget requires a site key'\n );\n }\n });\n\n it('returns a cached version if one already exists for a given site key', async () => {\n const mockGrecaptcha = new MockGrecaptcha({\n mode: 'success',\n });\n mockGrecaptcha.response = 'foo';\n const recaptchaManager = new RecaptchaManager({\n lazyLoader: mockLazyLoader,\n grecaptchaLibrary: mockGrecaptcha,\n defaultSiteKey: '123',\n });\n const recaptcha1 = await recaptchaManager.getRecaptchaWidget();\n const recaptcha2 = await recaptchaManager.getRecaptchaWidget();\n expect(recaptcha1).to.equal(recaptcha2);\n });\n\n it('loads the recaptcha library if needed', async () => {\n const recaptchaManager = new RecaptchaManager({\n lazyLoader: mockLazyLoader,\n defaultSiteKey: '123',\n });\n await recaptchaManager.getRecaptchaWidget();\n const loadUrl = mockLazyLoader.loadScriptSrc;\n expect(\n loadUrl?.includes('recaptcha/api.js?onload=grecaptchaLoadedCallback')\n ).to.be.true;\n });\n });\n\n describe('ReCaptcha Widget', () => {\n it('can error properly like a Promise', async () => {\n const mockGrecaptcha = new MockGrecaptcha({\n mode: 'error',\n });\n const recaptchaManager = new RecaptchaManager({\n lazyLoader: mockLazyLoader,\n grecaptchaLibrary: mockGrecaptcha,\n defaultSiteKey: '123',\n });\n const recaptcha = await recaptchaManager.getRecaptchaWidget();\n\n try {\n await recaptcha.execute();\n expect.fail('should not get here');\n } catch (error) {\n expect((error as Error).message).to.equal('error');\n }\n });\n\n it('can expire properly like a Promise', async () => {\n const mockGrecaptcha = new MockGrecaptcha({\n mode: 'expired',\n });\n const recaptchaManager = new RecaptchaManager({\n lazyLoader: mockLazyLoader,\n grecaptchaLibrary: mockGrecaptcha,\n defaultSiteKey: '123',\n });\n const recaptcha = await recaptchaManager.getRecaptchaWidget();\n\n try {\n await recaptcha.execute();\n expect.fail('should not get here');\n } catch (error) {\n expect((error as Error).message).to.equal('expired');\n }\n });\n\n it('resets the captcha after execution', async () => {\n const mockGrecaptcha = new MockGrecaptcha({\n mode: 'success',\n addDelay: true,\n });\n const recaptchaManager = new RecaptchaManager({\n lazyLoader: mockLazyLoader,\n grecaptchaLibrary: mockGrecaptcha,\n defaultSiteKey: '123',\n });\n const recaptcha = await recaptchaManager.getRecaptchaWidget();\n\n expect(mockGrecaptcha.resetCallCount).to.equal(0);\n await recaptcha.execute();\n expect(mockGrecaptcha.resetCallCount).to.equal(1);\n await recaptcha.execute();\n expect(mockGrecaptcha.resetCallCount).to.equal(2);\n });\n\n // there's no way to know if the user has dismissed the captcha so if another execution\n // comes through while the captcha is still active, it will be reset\n it('resets the captcha if another execution gets called', async () => {\n const mockGrecaptcha = new MockGrecaptcha({\n mode: 'success',\n addDelay: true,\n });\n const recaptchaManager = new RecaptchaManager({\n lazyLoader: mockLazyLoader,\n grecaptchaLibrary: mockGrecaptcha,\n defaultSiteKey: '123',\n });\n const recaptcha = await recaptchaManager.getRecaptchaWidget();\n\n expect(mockGrecaptcha.resetCallCount).to.equal(0);\n recaptcha.execute();\n expect(mockGrecaptcha.resetCallCount).to.equal(0);\n await recaptcha.execute();\n expect(mockGrecaptcha.resetCallCount).to.equal(2);\n });\n });\n});\n"]}
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@internetarchive/recaptcha-manager",
|
|
3
|
-
"description": "A library that lets you lazy load and interact with reCaptcha
|
|
3
|
+
"description": "A library that lets you lazy load and interact with reCaptcha.",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/internetarchive/iaux-recaptcha-manager.git"
|
|
7
7
|
},
|
|
8
8
|
"license": "AGPL-3.0-only",
|
|
9
9
|
"author": "Internet Archive",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.1.0",
|
|
11
11
|
"main": "dist/index.js",
|
|
12
12
|
"module": "dist/index.js",
|
|
13
13
|
"scripts": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"types": "dist/index.d.ts",
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@internetarchive/lazy-loader-service": "^0.2.0",
|
|
26
|
-
"@types/grecaptcha": "^
|
|
26
|
+
"@types/grecaptcha": "^2"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@open-wc/eslint-config": "^7.0.0",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"husky": "^7.0.0",
|
|
42
42
|
"lit": "^2.2.0",
|
|
43
43
|
"madge": "^5.0.1",
|
|
44
|
+
"nanoevents": "^6.0.2",
|
|
44
45
|
"prettier": "^2.4.1",
|
|
45
46
|
"sinon": "^12.0.1",
|
|
46
47
|
"tslib": "^2.3.1",
|
package/src/recaptcha-manager.ts
CHANGED
|
@@ -21,14 +21,14 @@ export class RecaptchaManager implements RecaptchaManagerInterface {
|
|
|
21
21
|
|
|
22
22
|
private recaptchaCache: Record<string, RecaptchaWidget> = {};
|
|
23
23
|
|
|
24
|
-
constructor(options
|
|
25
|
-
defaultSiteKey
|
|
24
|
+
constructor(options?: {
|
|
25
|
+
defaultSiteKey?: string;
|
|
26
26
|
lazyLoader?: LazyLoaderServiceInterface;
|
|
27
27
|
grecaptchaLibrary?: ReCaptchaV2.ReCaptcha; // allows dependency injection or will be lazy loaded
|
|
28
28
|
}) {
|
|
29
|
-
this.defaultSiteKey = options
|
|
30
|
-
this.lazyLoader = options
|
|
31
|
-
this.grecaptchaLibraryCache = options
|
|
29
|
+
this.defaultSiteKey = options?.defaultSiteKey;
|
|
30
|
+
this.lazyLoader = options?.lazyLoader ?? new LazyLoaderService();
|
|
31
|
+
this.grecaptchaLibraryCache = options?.grecaptchaLibrary;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** @inheritdoc */
|
|
@@ -38,7 +38,7 @@ export class RecaptchaManager implements RecaptchaManagerInterface {
|
|
|
38
38
|
}): Promise<RecaptchaWidget> {
|
|
39
39
|
const key = options?.siteKey ?? this.defaultSiteKey;
|
|
40
40
|
if (!key) {
|
|
41
|
-
throw new Error('
|
|
41
|
+
throw new Error('The reCaptcha widget requires a site key');
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const cached = this.recaptchaCache[key];
|
package/src/recaptcha-widget.ts
CHANGED
|
@@ -6,6 +6,17 @@ export interface RecaptchaWidgetInterface {
|
|
|
6
6
|
execute(): Promise<string>;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* The combines parameters from the Recaptcha config and adds the zIndex, which
|
|
11
|
+
* allows us to control the z-index of the recaptcha widget.
|
|
12
|
+
*/
|
|
13
|
+
export type RecaptchaWidgetConfig = Pick<
|
|
14
|
+
ReCaptchaV2.Parameters,
|
|
15
|
+
'tabindex' | 'theme' | 'type' | 'size' | 'badge'
|
|
16
|
+
> & {
|
|
17
|
+
zIndex?: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
9
20
|
/**
|
|
10
21
|
* This encompasses a widget for a given site key.
|
|
11
22
|
*/
|
|
@@ -29,13 +40,13 @@ export class RecaptchaWidget implements RecaptchaWidgetInterface {
|
|
|
29
40
|
siteKey: string;
|
|
30
41
|
grecaptchaLibrary: ReCaptchaV2.ReCaptcha;
|
|
31
42
|
},
|
|
32
|
-
|
|
43
|
+
widgetConfig?: RecaptchaWidgetConfig
|
|
33
44
|
) {
|
|
34
45
|
this.siteKey = config.siteKey;
|
|
35
46
|
this.grecaptchaLibrary = config.grecaptchaLibrary;
|
|
36
47
|
|
|
37
48
|
const container = this.createContainer();
|
|
38
|
-
this.setup(container,
|
|
49
|
+
this.setup(container, widgetConfig);
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
/** @inheritdoc */
|
|
@@ -53,6 +64,8 @@ export class RecaptchaWidget implements RecaptchaWidgetInterface {
|
|
|
53
64
|
this.finishExecution();
|
|
54
65
|
}
|
|
55
66
|
|
|
67
|
+
this.isExecuting = true;
|
|
68
|
+
|
|
56
69
|
return new Promise((resolve, reject) => {
|
|
57
70
|
this.executionSuccessBlock = (token: string): void => {
|
|
58
71
|
this.finishExecution();
|
|
@@ -80,24 +93,24 @@ export class RecaptchaWidget implements RecaptchaWidgetInterface {
|
|
|
80
93
|
this.grecaptchaLibrary.reset(widgetId);
|
|
81
94
|
}
|
|
82
95
|
|
|
83
|
-
/** @inheritdoc */
|
|
84
96
|
private setup(
|
|
85
97
|
container: HTMLElement,
|
|
86
|
-
|
|
98
|
+
widgetConfig?: ReCaptchaV2.Parameters
|
|
87
99
|
): void {
|
|
88
100
|
this.widgetId = this.grecaptchaLibrary.render(container, {
|
|
89
101
|
callback: this.responseHandler.bind(this),
|
|
90
102
|
'expired-callback': this.expiredHandler.bind(this),
|
|
91
103
|
'error-callback': this.errorHandler.bind(this),
|
|
92
104
|
sitekey: this.siteKey,
|
|
93
|
-
tabindex:
|
|
94
|
-
theme:
|
|
95
|
-
type:
|
|
96
|
-
size:
|
|
105
|
+
tabindex: widgetConfig?.tabindex,
|
|
106
|
+
theme: widgetConfig?.theme,
|
|
107
|
+
type: widgetConfig?.type,
|
|
108
|
+
size: widgetConfig?.size ?? 'invisible',
|
|
109
|
+
badge: widgetConfig?.badge,
|
|
97
110
|
});
|
|
98
111
|
}
|
|
99
112
|
|
|
100
|
-
private createContainer(): HTMLElement {
|
|
113
|
+
private createContainer(zIndex?: number): HTMLElement {
|
|
101
114
|
const elementId = `recaptchaManager-${this.siteKey}`;
|
|
102
115
|
let element: HTMLElement | null = document.getElementById(elementId);
|
|
103
116
|
if (!element) {
|
|
@@ -106,6 +119,7 @@ export class RecaptchaWidget implements RecaptchaWidgetInterface {
|
|
|
106
119
|
element.style.position = 'fixed';
|
|
107
120
|
element.style.top = '50%';
|
|
108
121
|
element.style.left = '50%';
|
|
122
|
+
element.style.zIndex = zIndex ? `${zIndex}` : '10';
|
|
109
123
|
document.body.appendChild(element);
|
|
110
124
|
}
|
|
111
125
|
return element;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
4
|
+
|
|
5
|
+
export type MockGrecaptchaMode = 'success' | 'expired' | 'error';
|
|
6
|
+
|
|
7
|
+
export class MockGrecaptcha implements ReCaptchaV2.ReCaptcha {
|
|
8
|
+
response = 'foo';
|
|
9
|
+
|
|
10
|
+
renderCalled = false;
|
|
11
|
+
|
|
12
|
+
executeCalled = false;
|
|
13
|
+
|
|
14
|
+
resetCallCount = 0;
|
|
15
|
+
|
|
16
|
+
getResponseCalled = false;
|
|
17
|
+
|
|
18
|
+
mode: MockGrecaptchaMode;
|
|
19
|
+
|
|
20
|
+
addDelay: boolean;
|
|
21
|
+
|
|
22
|
+
callback?: (response: string) => void;
|
|
23
|
+
|
|
24
|
+
'expired-callback'?: () => void;
|
|
25
|
+
|
|
26
|
+
'error-callback'?: () => void;
|
|
27
|
+
|
|
28
|
+
render(
|
|
29
|
+
container: string | HTMLElement,
|
|
30
|
+
parameters?: ReCaptchaV2.Parameters | undefined,
|
|
31
|
+
inherit?: boolean | undefined
|
|
32
|
+
): number {
|
|
33
|
+
this.callback = parameters?.callback?.bind(this);
|
|
34
|
+
this['error-callback'] = parameters?.['error-callback']?.bind(this);
|
|
35
|
+
this['expired-callback'] = parameters?.['expired-callback']?.bind(this);
|
|
36
|
+
this.renderCalled = true;
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
reset(opt_widget_id?: number | undefined): void {
|
|
41
|
+
this.resetCallCount += 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getResponse(opt_widget_id?: number | undefined): string {
|
|
45
|
+
this.getResponseCalled = true;
|
|
46
|
+
return 'foo';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
execute(opt_widget_id?: number): void {
|
|
50
|
+
this.executeCalled = true;
|
|
51
|
+
|
|
52
|
+
if (this.addDelay) {
|
|
53
|
+
setTimeout(this.callCallback.bind(this), 100);
|
|
54
|
+
} else {
|
|
55
|
+
this.callCallback();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
ready(callback: () => void): void {}
|
|
60
|
+
|
|
61
|
+
private callCallback(): void {
|
|
62
|
+
switch (this.mode) {
|
|
63
|
+
case 'success':
|
|
64
|
+
if (this.callback) {
|
|
65
|
+
this.callback('foo');
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
case 'error':
|
|
69
|
+
if (this['error-callback']) {
|
|
70
|
+
this['error-callback']();
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
case 'expired':
|
|
74
|
+
if (this['expired-callback']) {
|
|
75
|
+
this['expired-callback']();
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
constructor(options: { mode: MockGrecaptchaMode; addDelay?: boolean }) {
|
|
84
|
+
this.mode = options.mode;
|
|
85
|
+
this.addDelay = options.addDelay ?? false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
import {
|
|
3
|
+
BundleType,
|
|
4
|
+
LazyLoaderServiceEvents,
|
|
5
|
+
LazyLoaderServiceInterface,
|
|
6
|
+
} from '@internetarchive/lazy-loader-service';
|
|
7
|
+
import { Unsubscribe } from 'nanoevents';
|
|
8
|
+
import { MockGrecaptcha } from './mock-grecaptcha';
|
|
9
|
+
|
|
10
|
+
export class MockLazyLoaderService implements LazyLoaderServiceInterface {
|
|
11
|
+
loadScriptSrc?: string;
|
|
12
|
+
|
|
13
|
+
on<E extends keyof LazyLoaderServiceEvents>(
|
|
14
|
+
event: E,
|
|
15
|
+
callback: LazyLoaderServiceEvents[E]
|
|
16
|
+
): Unsubscribe {
|
|
17
|
+
throw new Error('Method not implemented.');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async loadBundle(bundle: {
|
|
21
|
+
module?: string;
|
|
22
|
+
nomodule?: string;
|
|
23
|
+
}): Promise<void> {
|
|
24
|
+
console.debug('loadBundle', bundle);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async loadScript(options: {
|
|
28
|
+
src: string;
|
|
29
|
+
bundleType?: BundleType;
|
|
30
|
+
attributes?: Record<string, string>;
|
|
31
|
+
}): Promise<void> {
|
|
32
|
+
this.loadScriptSrc = options.src;
|
|
33
|
+
window.grecaptcha = new MockGrecaptcha({
|
|
34
|
+
mode: 'success',
|
|
35
|
+
});
|
|
36
|
+
(window as any).grecaptchaLoadedCallback();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -1 +1,151 @@
|
|
|
1
|
-
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { MockGrecaptcha } from './mock-grecaptcha';
|
|
3
|
+
import { RecaptchaManager } from '../src/recaptcha-manager';
|
|
4
|
+
import { MockLazyLoaderService } from './mock-lazy-loader';
|
|
5
|
+
|
|
6
|
+
const mockLazyLoader = new MockLazyLoaderService();
|
|
7
|
+
|
|
8
|
+
describe('ReCaptcha Management', () => {
|
|
9
|
+
describe('ReCaptcha Manager', () => {
|
|
10
|
+
it('can return recaptchas', async () => {
|
|
11
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
12
|
+
mode: 'success',
|
|
13
|
+
});
|
|
14
|
+
mockGrecaptcha.response = 'foo';
|
|
15
|
+
const recaptchaManager = new RecaptchaManager({
|
|
16
|
+
lazyLoader: mockLazyLoader,
|
|
17
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
18
|
+
defaultSiteKey: '123',
|
|
19
|
+
});
|
|
20
|
+
const recaptcha = await recaptchaManager.getRecaptchaWidget();
|
|
21
|
+
const result = await recaptcha.execute();
|
|
22
|
+
expect(result).to.equal('foo');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('throws an error if no site key found', async () => {
|
|
26
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
27
|
+
mode: 'success',
|
|
28
|
+
});
|
|
29
|
+
mockGrecaptcha.response = 'foo';
|
|
30
|
+
const recaptchaManager = new RecaptchaManager({
|
|
31
|
+
lazyLoader: mockLazyLoader,
|
|
32
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
await recaptchaManager.getRecaptchaWidget();
|
|
36
|
+
expect.fail('should not get here');
|
|
37
|
+
} catch (e) {
|
|
38
|
+
expect((e as Error).message).to.equal(
|
|
39
|
+
'The reCaptcha widget requires a site key'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('returns a cached version if one already exists for a given site key', async () => {
|
|
45
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
46
|
+
mode: 'success',
|
|
47
|
+
});
|
|
48
|
+
mockGrecaptcha.response = 'foo';
|
|
49
|
+
const recaptchaManager = new RecaptchaManager({
|
|
50
|
+
lazyLoader: mockLazyLoader,
|
|
51
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
52
|
+
defaultSiteKey: '123',
|
|
53
|
+
});
|
|
54
|
+
const recaptcha1 = await recaptchaManager.getRecaptchaWidget();
|
|
55
|
+
const recaptcha2 = await recaptchaManager.getRecaptchaWidget();
|
|
56
|
+
expect(recaptcha1).to.equal(recaptcha2);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('loads the recaptcha library if needed', async () => {
|
|
60
|
+
const recaptchaManager = new RecaptchaManager({
|
|
61
|
+
lazyLoader: mockLazyLoader,
|
|
62
|
+
defaultSiteKey: '123',
|
|
63
|
+
});
|
|
64
|
+
await recaptchaManager.getRecaptchaWidget();
|
|
65
|
+
const loadUrl = mockLazyLoader.loadScriptSrc;
|
|
66
|
+
expect(
|
|
67
|
+
loadUrl?.includes('recaptcha/api.js?onload=grecaptchaLoadedCallback')
|
|
68
|
+
).to.be.true;
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('ReCaptcha Widget', () => {
|
|
73
|
+
it('can error properly like a Promise', async () => {
|
|
74
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
75
|
+
mode: 'error',
|
|
76
|
+
});
|
|
77
|
+
const recaptchaManager = new RecaptchaManager({
|
|
78
|
+
lazyLoader: mockLazyLoader,
|
|
79
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
80
|
+
defaultSiteKey: '123',
|
|
81
|
+
});
|
|
82
|
+
const recaptcha = await recaptchaManager.getRecaptchaWidget();
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await recaptcha.execute();
|
|
86
|
+
expect.fail('should not get here');
|
|
87
|
+
} catch (error) {
|
|
88
|
+
expect((error as Error).message).to.equal('error');
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('can expire properly like a Promise', async () => {
|
|
93
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
94
|
+
mode: 'expired',
|
|
95
|
+
});
|
|
96
|
+
const recaptchaManager = new RecaptchaManager({
|
|
97
|
+
lazyLoader: mockLazyLoader,
|
|
98
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
99
|
+
defaultSiteKey: '123',
|
|
100
|
+
});
|
|
101
|
+
const recaptcha = await recaptchaManager.getRecaptchaWidget();
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
await recaptcha.execute();
|
|
105
|
+
expect.fail('should not get here');
|
|
106
|
+
} catch (error) {
|
|
107
|
+
expect((error as Error).message).to.equal('expired');
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('resets the captcha after execution', async () => {
|
|
112
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
113
|
+
mode: 'success',
|
|
114
|
+
addDelay: true,
|
|
115
|
+
});
|
|
116
|
+
const recaptchaManager = new RecaptchaManager({
|
|
117
|
+
lazyLoader: mockLazyLoader,
|
|
118
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
119
|
+
defaultSiteKey: '123',
|
|
120
|
+
});
|
|
121
|
+
const recaptcha = await recaptchaManager.getRecaptchaWidget();
|
|
122
|
+
|
|
123
|
+
expect(mockGrecaptcha.resetCallCount).to.equal(0);
|
|
124
|
+
await recaptcha.execute();
|
|
125
|
+
expect(mockGrecaptcha.resetCallCount).to.equal(1);
|
|
126
|
+
await recaptcha.execute();
|
|
127
|
+
expect(mockGrecaptcha.resetCallCount).to.equal(2);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// there's no way to know if the user has dismissed the captcha so if another execution
|
|
131
|
+
// comes through while the captcha is still active, it will be reset
|
|
132
|
+
it('resets the captcha if another execution gets called', async () => {
|
|
133
|
+
const mockGrecaptcha = new MockGrecaptcha({
|
|
134
|
+
mode: 'success',
|
|
135
|
+
addDelay: true,
|
|
136
|
+
});
|
|
137
|
+
const recaptchaManager = new RecaptchaManager({
|
|
138
|
+
lazyLoader: mockLazyLoader,
|
|
139
|
+
grecaptchaLibrary: mockGrecaptcha,
|
|
140
|
+
defaultSiteKey: '123',
|
|
141
|
+
});
|
|
142
|
+
const recaptcha = await recaptchaManager.getRecaptchaWidget();
|
|
143
|
+
|
|
144
|
+
expect(mockGrecaptcha.resetCallCount).to.equal(0);
|
|
145
|
+
recaptcha.execute();
|
|
146
|
+
expect(mockGrecaptcha.resetCallCount).to.equal(0);
|
|
147
|
+
await recaptcha.execute();
|
|
148
|
+
expect(mockGrecaptcha.resetCallCount).to.equal(2);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|