@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/README.md ADDED
@@ -0,0 +1,56 @@
1
+ ![Build Status](https://github.com/internetarchive/iaux-recaptcha-manager/actions/workflows/ci.yml/badge.svg)
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.
@@ -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
+ }
@@ -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"]}
@@ -0,0 +1 @@
1
+ export { RecaptchaManager, RecaptchaManagerInterface } from './src/recaptcha-manager';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { RecaptchaManager } from './src/recaptcha-manager';
2
+ //# sourceMappingURL=index.js.map
@@ -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,4 @@
1
+ "use strict";
2
+ describe('YourWebComponent', () => {
3
+ });
4
+ //# sourceMappingURL=recaptcha-manager.test.js.map
@@ -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,4 @@
1
+ "use strict";
2
+ describe('YourWebComponent', () => {
3
+ });
4
+ //# sourceMappingURL=your-webcomponent.test.js.map
@@ -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
+ }