@internetarchive/recaptcha-manager 0.0.1-alpha.1
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/.editorconfig +29 -0
- package/.github/workflows/ci.yml +26 -0
- package/.husky/pre-commit +4 -0
- package/LICENSE +661 -0
- package/README.md +56 -0
- package/demo/app-root.ts +53 -0
- package/demo/index.html +26 -0
- package/dist/demo/app-root.d.ts +9 -0
- package/dist/demo/app-root.js +49 -0
- package/dist/demo/app-root.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/src/recaptcha-manager.d.ts +68 -0
- package/dist/src/recaptcha-manager.js +126 -0
- package/dist/src/recaptcha-manager.js.map +1 -0
- package/dist/test/recaptcha-manager.test.d.ts +0 -0
- package/dist/test/recaptcha-manager.test.js +4 -0
- package/dist/test/recaptcha-manager.test.js.map +1 -0
- package/dist/test/your-webcomponent.test.d.ts +0 -0
- package/dist/test/your-webcomponent.test.js +4 -0
- package/dist/test/your-webcomponent.test.js.map +1 -0
- package/index.ts +1 -0
- package/package.json +96 -0
- package/src/recaptcha-manager.ts +174 -0
- package/test/recaptcha-manager.test.ts +3 -0
- package/tsconfig.json +20 -0
- package/web-dev-server.config.mjs +28 -0
- package/web-test-runner.config.mjs +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# Internet Archive reCaptcha Manager
|
|
4
|
+
|
|
5
|
+
A library to lazy load and more easily interact with reCaptcha
|
|
6
|
+
|
|
7
|
+
## Local Demo with `web-dev-server`
|
|
8
|
+
```bash
|
|
9
|
+
yarn start
|
|
10
|
+
```
|
|
11
|
+
To run a local development server that serves the basic demo located in `demo/index.html`
|
|
12
|
+
|
|
13
|
+
## Testing with Web Test Runner
|
|
14
|
+
To run the suite of Web Test Runner tests, run
|
|
15
|
+
```bash
|
|
16
|
+
yarn run test
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
To run the tests in watch mode (for <abbr title="test driven development">TDD</abbr>, for example), run
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
yarn run test:watch
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Linting with ESLint, Prettier, and Types
|
|
26
|
+
To scan the project for linting errors, run
|
|
27
|
+
```bash
|
|
28
|
+
yarn run lint
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
You can lint with ESLint and Prettier individually as well
|
|
32
|
+
```bash
|
|
33
|
+
yarn run lint:eslint
|
|
34
|
+
```
|
|
35
|
+
```bash
|
|
36
|
+
yarn run lint:prettier
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
To automatically fix many linting errors, run
|
|
40
|
+
```bash
|
|
41
|
+
yarn run format
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
You can format using ESLint and Prettier individually as well
|
|
45
|
+
```bash
|
|
46
|
+
yarn run format:eslint
|
|
47
|
+
```
|
|
48
|
+
```bash
|
|
49
|
+
yarn run format:prettier
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Tooling configs
|
|
53
|
+
|
|
54
|
+
For most of the tools, the configuration is in the `package.json` to reduce the amount of files in your project.
|
|
55
|
+
|
|
56
|
+
If you customize the configuration a lot, you can consider moving them to individual files.
|
package/demo/app-root.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { html, css, LitElement } from 'lit';
|
|
2
|
+
import { customElement, state } from 'lit/decorators.js';
|
|
3
|
+
import {
|
|
4
|
+
RecaptchaManager,
|
|
5
|
+
RecaptchaManagerInterface,
|
|
6
|
+
} from '../src/recaptcha-manager';
|
|
7
|
+
|
|
8
|
+
@customElement('app-root')
|
|
9
|
+
export class AppRoot extends LitElement {
|
|
10
|
+
@state() result?: string;
|
|
11
|
+
|
|
12
|
+
private recaptchaManager?: RecaptchaManagerInterface;
|
|
13
|
+
|
|
14
|
+
render() {
|
|
15
|
+
return html`
|
|
16
|
+
<button @click="${this.loadRecaptcha}">Load Recaptcha</button>
|
|
17
|
+
<button @click="${this.executeRecaptcha}">Execute Recaptcha</button>
|
|
18
|
+
<slot name="recaptcha"></slot>
|
|
19
|
+
${this.result
|
|
20
|
+
? html`<strong
|
|
21
|
+
>Token:<strong><p>${this.result}</p></strong></strong
|
|
22
|
+
>`
|
|
23
|
+
: ''}
|
|
24
|
+
`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private async loadRecaptcha() {
|
|
28
|
+
this.recaptchaManager = await RecaptchaManager.getRecaptchaManager({
|
|
29
|
+
siteKey: '6LeTUvYUAAAAAPTvW98MaXyS8c6vxk4-9n8DI1ve',
|
|
30
|
+
});
|
|
31
|
+
const element = document.querySelector('#recaptcha') as HTMLDivElement;
|
|
32
|
+
this.recaptchaManager?.setup(element, 0, 'light', 'image');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private async executeRecaptcha() {
|
|
36
|
+
if (!this.recaptchaManager) {
|
|
37
|
+
await this.loadRecaptcha();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!this.recaptchaManager) {
|
|
41
|
+
console.error('error loading recaptcha');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.result = await this.recaptchaManager.execute();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static styles = css`
|
|
49
|
+
:host {
|
|
50
|
+
display: block;
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
}
|
package/demo/index.html
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en-GB">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<style>
|
|
6
|
+
html {
|
|
7
|
+
font-size: 10px; /* This is to match petabox's base font size */
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
background: #fafafa;
|
|
12
|
+
}
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<app-root>
|
|
17
|
+
<!-- recaptcha does not work in the shadowDOM so must be slotted from the top -->
|
|
18
|
+
<div slot="recaptcha">
|
|
19
|
+
<div id="recaptcha"></div>
|
|
20
|
+
</div>
|
|
21
|
+
</app-root>
|
|
22
|
+
|
|
23
|
+
<script type="module" src="../dist/demo/app-root.js">
|
|
24
|
+
</script>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
export declare class AppRoot extends LitElement {
|
|
3
|
+
result?: string;
|
|
4
|
+
private recaptchaManager?;
|
|
5
|
+
render(): import("lit").TemplateResult<1>;
|
|
6
|
+
private loadRecaptcha;
|
|
7
|
+
private executeRecaptcha;
|
|
8
|
+
static styles: import("lit").CSSResult;
|
|
9
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { html, css, LitElement } from 'lit';
|
|
3
|
+
import { customElement, state } from 'lit/decorators.js';
|
|
4
|
+
import { RecaptchaManager, } from '../src/recaptcha-manager';
|
|
5
|
+
let AppRoot = class AppRoot extends LitElement {
|
|
6
|
+
render() {
|
|
7
|
+
return html `
|
|
8
|
+
<button @click="${this.loadRecaptcha}">Load Recaptcha</button>
|
|
9
|
+
<button @click="${this.executeRecaptcha}">Execute Recaptcha</button>
|
|
10
|
+
<slot name="recaptcha"></slot>
|
|
11
|
+
${this.result
|
|
12
|
+
? html `<strong
|
|
13
|
+
>Token:<strong><p>${this.result}</p></strong></strong
|
|
14
|
+
>`
|
|
15
|
+
: ''}
|
|
16
|
+
`;
|
|
17
|
+
}
|
|
18
|
+
async loadRecaptcha() {
|
|
19
|
+
var _a;
|
|
20
|
+
this.recaptchaManager = await RecaptchaManager.getRecaptchaManager({
|
|
21
|
+
siteKey: '6LeTUvYUAAAAAPTvW98MaXyS8c6vxk4-9n8DI1ve',
|
|
22
|
+
});
|
|
23
|
+
const element = document.querySelector('#recaptcha');
|
|
24
|
+
(_a = this.recaptchaManager) === null || _a === void 0 ? void 0 : _a.setup(element, 0, 'light', 'image');
|
|
25
|
+
}
|
|
26
|
+
async executeRecaptcha() {
|
|
27
|
+
if (!this.recaptchaManager) {
|
|
28
|
+
await this.loadRecaptcha();
|
|
29
|
+
}
|
|
30
|
+
if (!this.recaptchaManager) {
|
|
31
|
+
console.error('error loading recaptcha');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
this.result = await this.recaptchaManager.execute();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
AppRoot.styles = css `
|
|
38
|
+
:host {
|
|
39
|
+
display: block;
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
__decorate([
|
|
43
|
+
state()
|
|
44
|
+
], AppRoot.prototype, "result", void 0);
|
|
45
|
+
AppRoot = __decorate([
|
|
46
|
+
customElement('app-root')
|
|
47
|
+
], AppRoot);
|
|
48
|
+
export { AppRoot };
|
|
49
|
+
//# sourceMappingURL=app-root.js.map
|
|
@@ -0,0 +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;AACzD,OAAO,EACL,gBAAgB,GAEjB,MAAM,0BAA0B,CAAC;AAGlC,IAAa,OAAO,GAApB,MAAa,OAAQ,SAAQ,UAAU;IAKrC,MAAM;QACJ,OAAO,IAAI,CAAA;wBACS,IAAI,CAAC,aAAa;wBAClB,IAAI,CAAC,gBAAgB;;QAErC,IAAI,CAAC,MAAM;YACX,CAAC,CAAC,IAAI,CAAA;gCACkB,IAAI,CAAC,MAAM;YAC/B;YACJ,CAAC,CAAC,EAAE;KACP,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,aAAa;;QACzB,IAAI,CAAC,gBAAgB,GAAG,MAAM,gBAAgB,CAAC,mBAAmB,CAAC;YACjE,OAAO,EAAE,0CAA0C;SACpD,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,YAAY,CAAmB,CAAC;QACvE,MAAA,IAAI,CAAC,gBAAgB,0CAAE,KAAK,CAAC,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7D,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE;YAC1B,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;SAC5B;QAED,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE;YAC1B,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;YACzC,OAAO;SACR;QAED,IAAI,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC;IACtD,CAAC;CAOF,CAAA;AALQ,cAAM,GAAG,GAAG,CAAA;;;;GAIlB,CAAC;AA1CO;IAAR,KAAK,EAAE;uCAAiB;AADd,OAAO;IADnB,aAAa,CAAC,UAAU,CAAC;GACb,OAAO,CA4CnB;SA5CY,OAAO","sourcesContent":["import { html, css, LitElement } from 'lit';\nimport { customElement, state } from 'lit/decorators.js';\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 private recaptchaManager?: RecaptchaManagerInterface;\n\n render() {\n return html`\n <button @click=\"${this.loadRecaptcha}\">Load Recaptcha</button>\n <button @click=\"${this.executeRecaptcha}\">Execute Recaptcha</button>\n <slot name=\"recaptcha\"></slot>\n ${this.result\n ? html`<strong\n >Token:<strong><p>${this.result}</p></strong></strong\n >`\n : ''}\n `;\n }\n\n private async loadRecaptcha() {\n this.recaptchaManager = await RecaptchaManager.getRecaptchaManager({\n siteKey: '6LeTUvYUAAAAAPTvW98MaXyS8c6vxk4-9n8DI1ve',\n });\n const element = document.querySelector('#recaptcha') as HTMLDivElement;\n this.recaptchaManager?.setup(element, 0, 'light', 'image');\n }\n\n private async executeRecaptcha() {\n if (!this.recaptchaManager) {\n await this.loadRecaptcha();\n }\n\n if (!this.recaptchaManager) {\n console.error('error loading recaptcha');\n return;\n }\n\n this.result = await this.recaptchaManager.execute();\n }\n\n static styles = css`\n :host {\n display: block;\n }\n `;\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RecaptchaManager, RecaptchaManagerInterface } from './src/recaptcha-manager';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAA6B,MAAM,yBAAyB,CAAC","sourcesContent":["export { RecaptchaManager, RecaptchaManagerInterface } from './src/recaptcha-manager';\n"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/// <reference types="grecaptcha" />
|
|
2
|
+
import { LazyLoaderServiceInterface } from '@internetarchive/lazy-loader-service';
|
|
3
|
+
export interface RecaptchaManagerInterface {
|
|
4
|
+
execute(): Promise<string>;
|
|
5
|
+
setup(container: HTMLElement, tabIndex: number, theme: ReCaptchaV2.Theme, type: ReCaptchaV2.Type): void;
|
|
6
|
+
}
|
|
7
|
+
export declare class RecaptchaManager implements RecaptchaManagerInterface {
|
|
8
|
+
/**
|
|
9
|
+
* This is a convenience initializer that also lazily loads
|
|
10
|
+
* the recaptcha library from Google.
|
|
11
|
+
*
|
|
12
|
+
* @param options
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
static getRecaptchaManager(options: {
|
|
16
|
+
siteKey: string;
|
|
17
|
+
}): Promise<RecaptchaManagerInterface>;
|
|
18
|
+
/**
|
|
19
|
+
* Load the Recaptch library from Google.
|
|
20
|
+
*
|
|
21
|
+
* @param options
|
|
22
|
+
* @returns
|
|
23
|
+
*/
|
|
24
|
+
static loadRecaptchaLibrary(options?: {
|
|
25
|
+
lazyLoader?: LazyLoaderServiceInterface;
|
|
26
|
+
}): Promise<ReCaptchaV2.ReCaptcha>;
|
|
27
|
+
private grecaptchaLibrary;
|
|
28
|
+
private siteKey;
|
|
29
|
+
constructor(options: {
|
|
30
|
+
grecaptchaLibrary: ReCaptchaV2.ReCaptcha;
|
|
31
|
+
siteKey: string;
|
|
32
|
+
});
|
|
33
|
+
private executionSuccessBlock?;
|
|
34
|
+
private executionExpiredBlock?;
|
|
35
|
+
private executionErrorBlock?;
|
|
36
|
+
private isExecuting;
|
|
37
|
+
/**
|
|
38
|
+
* Execute Recaptcha and return a Promise containing the response token.
|
|
39
|
+
*
|
|
40
|
+
* This is an interesting flow.. we call `execute()` here, but have to wait for the
|
|
41
|
+
* response and expiration handlers that we bind during the inital `setup` call.
|
|
42
|
+
* For consumers, we want to be able to just call `execute()` and wait for a response.
|
|
43
|
+
* To allow this, we assign two callbacks:
|
|
44
|
+
* - `executionSuccessBlock`
|
|
45
|
+
* - `executionExpiredBlock`
|
|
46
|
+
*
|
|
47
|
+
* We then call those callbacks from inside `responseHandler` and `expiredHandler` to
|
|
48
|
+
* either resolve or reject the Promise.
|
|
49
|
+
*
|
|
50
|
+
* ie:
|
|
51
|
+
*
|
|
52
|
+
* try {
|
|
53
|
+
* const recaptchaResult = await recaptchaManager.execute();
|
|
54
|
+
* console.log('recaptcha token:', recaptchaResult);
|
|
55
|
+
* } catch {
|
|
56
|
+
* console.error('something happened')
|
|
57
|
+
* }
|
|
58
|
+
*
|
|
59
|
+
* @returns {Promise<string>}
|
|
60
|
+
* @memberof RecaptchaManager
|
|
61
|
+
*/
|
|
62
|
+
execute(): Promise<string>;
|
|
63
|
+
private finishExecution;
|
|
64
|
+
setup(container: HTMLElement, tabIndex: number, theme: ReCaptchaV2.Theme, type: ReCaptchaV2.Type): void;
|
|
65
|
+
private responseHandler;
|
|
66
|
+
private expiredHandler;
|
|
67
|
+
private errorHandler;
|
|
68
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { LazyLoaderService, } from '@internetarchive/lazy-loader-service';
|
|
2
|
+
export class RecaptchaManager {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this.isExecuting = false;
|
|
5
|
+
this.grecaptchaLibrary = options.grecaptchaLibrary;
|
|
6
|
+
this.siteKey = options.siteKey;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* This is a convenience initializer that also lazily loads
|
|
10
|
+
* the recaptcha library from Google.
|
|
11
|
+
*
|
|
12
|
+
* @param options
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
static async getRecaptchaManager(options) {
|
|
16
|
+
const grecaptchaLibrary = await RecaptchaManager.loadRecaptchaLibrary();
|
|
17
|
+
return new RecaptchaManager({
|
|
18
|
+
grecaptchaLibrary,
|
|
19
|
+
siteKey: options.siteKey,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Load the Recaptch library from Google.
|
|
24
|
+
*
|
|
25
|
+
* @param options
|
|
26
|
+
* @returns
|
|
27
|
+
*/
|
|
28
|
+
static async loadRecaptchaLibrary(options) {
|
|
29
|
+
var _a;
|
|
30
|
+
const service = (_a = options === null || options === void 0 ? void 0 : options.lazyLoader) !== null && _a !== void 0 ? _a : new LazyLoaderService();
|
|
31
|
+
return new Promise(resolve => {
|
|
32
|
+
window.grecaptchaLoadedCallback = () => {
|
|
33
|
+
setTimeout(() => {
|
|
34
|
+
// remove the callback when we're done with it
|
|
35
|
+
delete window.grecaptchaLoadedCallback;
|
|
36
|
+
}, 10);
|
|
37
|
+
resolve(window.grecaptcha);
|
|
38
|
+
};
|
|
39
|
+
service.loadScript({
|
|
40
|
+
src: 'https://www.google.com/recaptcha/api.js?onload=grecaptchaLoadedCallback&render=explicit',
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Execute Recaptcha and return a Promise containing the response token.
|
|
46
|
+
*
|
|
47
|
+
* This is an interesting flow.. we call `execute()` here, but have to wait for the
|
|
48
|
+
* response and expiration handlers that we bind during the inital `setup` call.
|
|
49
|
+
* For consumers, we want to be able to just call `execute()` and wait for a response.
|
|
50
|
+
* To allow this, we assign two callbacks:
|
|
51
|
+
* - `executionSuccessBlock`
|
|
52
|
+
* - `executionExpiredBlock`
|
|
53
|
+
*
|
|
54
|
+
* We then call those callbacks from inside `responseHandler` and `expiredHandler` to
|
|
55
|
+
* either resolve or reject the Promise.
|
|
56
|
+
*
|
|
57
|
+
* ie:
|
|
58
|
+
*
|
|
59
|
+
* try {
|
|
60
|
+
* const recaptchaResult = await recaptchaManager.execute();
|
|
61
|
+
* console.log('recaptcha token:', recaptchaResult);
|
|
62
|
+
* } catch {
|
|
63
|
+
* console.error('something happened')
|
|
64
|
+
* }
|
|
65
|
+
*
|
|
66
|
+
* @returns {Promise<string>}
|
|
67
|
+
* @memberof RecaptchaManager
|
|
68
|
+
*/
|
|
69
|
+
execute() {
|
|
70
|
+
if (this.isExecuting) {
|
|
71
|
+
this.finishExecution();
|
|
72
|
+
}
|
|
73
|
+
this.isExecuting = true;
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
this.executionSuccessBlock = (token) => {
|
|
76
|
+
this.finishExecution();
|
|
77
|
+
resolve(token);
|
|
78
|
+
};
|
|
79
|
+
this.executionExpiredBlock = () => {
|
|
80
|
+
this.finishExecution();
|
|
81
|
+
reject(new Error('expired'));
|
|
82
|
+
};
|
|
83
|
+
this.executionErrorBlock = () => {
|
|
84
|
+
this.finishExecution();
|
|
85
|
+
reject(new Error('error'));
|
|
86
|
+
};
|
|
87
|
+
this.grecaptchaLibrary.execute();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
finishExecution() {
|
|
91
|
+
this.isExecuting = false;
|
|
92
|
+
this.grecaptchaLibrary.reset();
|
|
93
|
+
}
|
|
94
|
+
setup(container, tabIndex, theme, type) {
|
|
95
|
+
this.grecaptchaLibrary.render(container, {
|
|
96
|
+
callback: this.responseHandler.bind(this),
|
|
97
|
+
'expired-callback': this.expiredHandler.bind(this),
|
|
98
|
+
'error-callback': this.errorHandler.bind(this),
|
|
99
|
+
sitekey: this.siteKey,
|
|
100
|
+
tabindex: tabIndex,
|
|
101
|
+
theme,
|
|
102
|
+
type,
|
|
103
|
+
size: 'invisible',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
107
|
+
responseHandler(response) {
|
|
108
|
+
if (this.executionSuccessBlock) {
|
|
109
|
+
this.executionSuccessBlock(response);
|
|
110
|
+
this.executionSuccessBlock = undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
expiredHandler() {
|
|
114
|
+
if (this.executionExpiredBlock) {
|
|
115
|
+
this.executionExpiredBlock();
|
|
116
|
+
this.executionExpiredBlock = undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
errorHandler() {
|
|
120
|
+
if (this.executionErrorBlock) {
|
|
121
|
+
this.executionErrorBlock();
|
|
122
|
+
this.executionErrorBlock = undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=recaptcha-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recaptcha-manager.js","sourceRoot":"","sources":["../../src/recaptcha-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n LazyLoaderService,\n LazyLoaderServiceInterface,\n} from '@internetarchive/lazy-loader-service';\n\nexport interface RecaptchaManagerInterface {\n execute(): Promise<string>;\n setup(\n container: HTMLElement,\n tabIndex: number,\n theme: ReCaptchaV2.Theme,\n type: ReCaptchaV2.Type\n ): void;\n}\n\nexport class RecaptchaManager implements RecaptchaManagerInterface {\n /**\n * This is a convenience initializer that also lazily loads\n * the recaptcha library from Google.\n *\n * @param options\n * @returns\n */\n static async getRecaptchaManager(options: {\n siteKey: string;\n }): Promise<RecaptchaManagerInterface> {\n const grecaptchaLibrary = await RecaptchaManager.loadRecaptchaLibrary();\n return new RecaptchaManager({\n grecaptchaLibrary,\n siteKey: options.siteKey,\n });\n }\n\n /**\n * Load the Recaptch library from Google.\n *\n * @param options\n * @returns\n */\n static async loadRecaptchaLibrary(options?: {\n lazyLoader?: LazyLoaderServiceInterface;\n }): Promise<ReCaptchaV2.ReCaptcha> {\n const service = options?.lazyLoader ?? new LazyLoaderService();\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 resolve(window.grecaptcha);\n };\n\n service.loadScript({\n src: 'https://www.google.com/recaptcha/api.js?onload=grecaptchaLoadedCallback&render=explicit',\n });\n });\n }\n\n private grecaptchaLibrary: ReCaptchaV2.ReCaptcha;\n\n private siteKey: string;\n\n constructor(options: {\n grecaptchaLibrary: ReCaptchaV2.ReCaptcha;\n siteKey: string;\n }) {\n this.grecaptchaLibrary = options.grecaptchaLibrary;\n this.siteKey = options.siteKey;\n }\n\n private executionSuccessBlock?: (token: string) => void;\n\n private executionExpiredBlock?: () => void;\n\n private executionErrorBlock?: () => void;\n\n private isExecuting = false;\n\n /**\n * Execute Recaptcha and return a Promise containing the response token.\n *\n * This is an interesting flow.. we call `execute()` here, but have to wait for the\n * response and expiration handlers that we bind during the inital `setup` call.\n * For consumers, we want to be able to just call `execute()` and wait for a response.\n * To allow this, we assign two callbacks:\n * - `executionSuccessBlock`\n * - `executionExpiredBlock`\n *\n * We then call those callbacks from inside `responseHandler` and `expiredHandler` to\n * either resolve or reject the Promise.\n *\n * ie:\n *\n * try {\n * const recaptchaResult = await recaptchaManager.execute();\n * console.log('recaptcha token:', recaptchaResult);\n * } catch {\n * console.error('something happened')\n * }\n *\n * @returns {Promise<string>}\n * @memberof RecaptchaManager\n */\n execute(): Promise<string> {\n if (this.isExecuting) {\n this.finishExecution();\n }\n this.isExecuting = true;\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();\n });\n }\n\n private finishExecution(): void {\n this.isExecuting = false;\n this.grecaptchaLibrary.reset();\n }\n\n setup(\n container: HTMLElement,\n tabIndex: number,\n theme: ReCaptchaV2.Theme,\n type: ReCaptchaV2.Type\n ): void {\n 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: tabIndex,\n theme,\n type,\n size: 'invisible',\n });\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\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"]}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recaptcha-manager.test.js","sourceRoot":"","sources":["../../test/recaptcha-manager.test.ts"],"names":[],"mappings":";AAAA,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;AAElC,CAAC,CAAC,CAAC","sourcesContent":["describe('YourWebComponent', () => {\n\n});\n"]}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"your-webcomponent.test.js","sourceRoot":"","sources":["../../test/your-webcomponent.test.ts"],"names":[],"mappings":";AAAA,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;AAElC,CAAC,CAAC,CAAC","sourcesContent":["describe('YourWebComponent', () => {\n\n});\n"]}
|
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RecaptchaManager, RecaptchaManagerInterface } from './src/recaptcha-manager';
|
package/package.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@internetarchive/recaptcha-manager",
|
|
3
|
+
"description": "A library that lets you lazy load and interact with reCaptcha more easily.",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/internetarchive/iaux-recaptcha-manager.git"
|
|
7
|
+
},
|
|
8
|
+
"license": "AGPL-3.0-only",
|
|
9
|
+
"author": "Internet Archive",
|
|
10
|
+
"version": "0.0.1-alpha.1",
|
|
11
|
+
"main": "dist/index.js",
|
|
12
|
+
"module": "dist/index.js",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wds\"",
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"prepare": "tsc && husky install",
|
|
17
|
+
"lint": "eslint --ext .ts,.html . --ignore-path .gitignore && prettier \"**/*.ts\" --check --ignore-path .gitignore",
|
|
18
|
+
"format": "eslint --ext .ts,.html . --fix --ignore-path .gitignore && prettier \"**/*.ts\" --write --ignore-path .gitignore",
|
|
19
|
+
"circular": "madge --circular --extensions ts .",
|
|
20
|
+
"test": "tsc && yarn run lint && yarn run circular && wtr --coverage",
|
|
21
|
+
"test:watch": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wtr --watch\""
|
|
22
|
+
},
|
|
23
|
+
"types": "dist/index.d.ts",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@internetarchive/lazy-loader-service": "^0.2.0",
|
|
26
|
+
"@types/grecaptcha": "^3.0.3"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@open-wc/eslint-config": "^7.0.0",
|
|
30
|
+
"@open-wc/testing": "^3.0.3",
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "^5.3.1",
|
|
32
|
+
"@typescript-eslint/parser": "^5.3.1",
|
|
33
|
+
"@web/dev-server": "^0.1.28",
|
|
34
|
+
"@web/test-runner": "^0.13.22",
|
|
35
|
+
"concurrently": "^6.3.0",
|
|
36
|
+
"eslint": "^8.2.0",
|
|
37
|
+
"eslint-config-prettier": "^8.3.0",
|
|
38
|
+
"eslint-plugin-import": "^2.25.3",
|
|
39
|
+
"eslint-plugin-lit-a11y": "^2.2.0",
|
|
40
|
+
"eslint-plugin-wc": "^1.3.2",
|
|
41
|
+
"husky": "^7.0.0",
|
|
42
|
+
"lit": "^2.2.0",
|
|
43
|
+
"madge": "^5.0.1",
|
|
44
|
+
"prettier": "^2.4.1",
|
|
45
|
+
"sinon": "^12.0.1",
|
|
46
|
+
"tslib": "^2.3.1",
|
|
47
|
+
"typescript": "^4.4.4"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"eslintConfig": {
|
|
53
|
+
"parser": "@typescript-eslint/parser",
|
|
54
|
+
"extends": [
|
|
55
|
+
"@open-wc",
|
|
56
|
+
"prettier"
|
|
57
|
+
],
|
|
58
|
+
"plugins": [
|
|
59
|
+
"@typescript-eslint"
|
|
60
|
+
],
|
|
61
|
+
"globals": {
|
|
62
|
+
"ReCaptchaV2": "readonly"
|
|
63
|
+
},
|
|
64
|
+
"rules": {
|
|
65
|
+
"no-unused-vars": "off",
|
|
66
|
+
"@typescript-eslint/no-unused-vars": [
|
|
67
|
+
"error"
|
|
68
|
+
],
|
|
69
|
+
"no-shadow": "off",
|
|
70
|
+
"@typescript-eslint/no-shadow": [
|
|
71
|
+
"error"
|
|
72
|
+
],
|
|
73
|
+
"class-methods-use-this": "off",
|
|
74
|
+
"import/no-unresolved": "off",
|
|
75
|
+
"import/extensions": [
|
|
76
|
+
"off",
|
|
77
|
+
"ignorePackages",
|
|
78
|
+
{
|
|
79
|
+
"js": "never",
|
|
80
|
+
"ts": "never"
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"prettier": {
|
|
86
|
+
"singleQuote": true,
|
|
87
|
+
"arrowParens": "avoid"
|
|
88
|
+
},
|
|
89
|
+
"lint-staged": {
|
|
90
|
+
"*.ts": [
|
|
91
|
+
"eslint --fix",
|
|
92
|
+
"prettier --write",
|
|
93
|
+
"git add"
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
}
|