@internetarchive/recaptcha-manager 0.0.1-alpha.1 → 0.0.1-alpha.4

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.
@@ -0,0 +1,87 @@
1
+ /**
2
+ * This encompasses a widget for a given site key.
3
+ */
4
+ export class RecaptchaWidget {
5
+ constructor(config, recaptchaParams) {
6
+ this.widgetId = null;
7
+ this.siteKey = config.siteKey;
8
+ this.grecaptchaLibrary = config.grecaptchaLibrary;
9
+ const container = this.createContainer();
10
+ this.setup(container, recaptchaParams);
11
+ }
12
+ /** @inheritdoc */
13
+ async execute() {
14
+ const { widgetId } = this;
15
+ if (widgetId === null) {
16
+ throw new Error('Recaptcha is not setup');
17
+ }
18
+ // there is no way to know when users have dismissed a previous recaptcha
19
+ // so if we're still in the middle of execution when we call execute again,
20
+ // just reset it and execute it again
21
+ this.grecaptchaLibrary.reset(widgetId);
22
+ return new Promise((resolve, reject) => {
23
+ this.executionSuccessBlock = (token) => {
24
+ this.grecaptchaLibrary.reset(widgetId);
25
+ resolve(token);
26
+ };
27
+ this.executionExpiredBlock = () => {
28
+ this.grecaptchaLibrary.reset(widgetId);
29
+ reject(new Error('expired'));
30
+ };
31
+ this.executionErrorBlock = () => {
32
+ this.grecaptchaLibrary.reset(widgetId);
33
+ reject(new Error('error'));
34
+ };
35
+ this.grecaptchaLibrary.execute(widgetId);
36
+ });
37
+ }
38
+ /** @inheritdoc */
39
+ setup(container, recaptchaParams) {
40
+ var _a;
41
+ this.widgetId = this.grecaptchaLibrary.render(container, {
42
+ callback: this.responseHandler.bind(this),
43
+ 'expired-callback': this.expiredHandler.bind(this),
44
+ 'error-callback': this.errorHandler.bind(this),
45
+ sitekey: this.siteKey,
46
+ tabindex: recaptchaParams === null || recaptchaParams === void 0 ? void 0 : recaptchaParams.tabindex,
47
+ theme: recaptchaParams === null || recaptchaParams === void 0 ? void 0 : recaptchaParams.theme,
48
+ type: recaptchaParams === null || recaptchaParams === void 0 ? void 0 : recaptchaParams.type,
49
+ size: (_a = recaptchaParams === null || recaptchaParams === void 0 ? void 0 : recaptchaParams.size) !== null && _a !== void 0 ? _a : 'invisible',
50
+ });
51
+ }
52
+ createContainer() {
53
+ const elementId = `recaptchaManager-${this.siteKey}`;
54
+ let element = document.getElementById(elementId);
55
+ if (!element) {
56
+ element = document.createElement('div');
57
+ element.id = elementId;
58
+ element.style.position = 'fixed';
59
+ element.style.top = '50%';
60
+ element.style.left = '50%';
61
+ element.style.outline = '1px solid purple';
62
+ element.style.width = '10px';
63
+ element.style.height = '10px';
64
+ document.body.appendChild(element);
65
+ }
66
+ return element;
67
+ }
68
+ responseHandler(response) {
69
+ if (this.executionSuccessBlock) {
70
+ this.executionSuccessBlock(response);
71
+ this.executionSuccessBlock = undefined;
72
+ }
73
+ }
74
+ expiredHandler() {
75
+ if (this.executionExpiredBlock) {
76
+ this.executionExpiredBlock();
77
+ this.executionExpiredBlock = undefined;
78
+ }
79
+ }
80
+ errorHandler() {
81
+ if (this.executionErrorBlock) {
82
+ this.executionErrorBlock();
83
+ this.executionErrorBlock = undefined;
84
+ }
85
+ }
86
+ }
87
+ //# sourceMappingURL=recaptcha.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recaptcha.js","sourceRoot":"","sources":["../../src/recaptcha.ts"],"names":[],"mappings":"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","sourcesContent":["export interface RecaptchaWidgetInterface {\n /** execute the recaptcha request and returns a token */\n execute(): Promise<string>;\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 constructor(\n config: {\n siteKey: string;\n grecaptchaLibrary: ReCaptchaV2.ReCaptcha;\n },\n recaptchaParams?: ReCaptchaV2.Parameters\n ) {\n this.siteKey = config.siteKey;\n this.grecaptchaLibrary = config.grecaptchaLibrary;\n\n const container = this.createContainer();\n this.setup(container, recaptchaParams);\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 // 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.grecaptchaLibrary.reset(widgetId);\n\n return new Promise((resolve, reject) => {\n this.executionSuccessBlock = (token: string): void => {\n this.grecaptchaLibrary.reset(widgetId);\n resolve(token);\n };\n\n this.executionExpiredBlock = (): void => {\n this.grecaptchaLibrary.reset(widgetId);\n reject(new Error('expired'));\n };\n\n this.executionErrorBlock = (): void => {\n this.grecaptchaLibrary.reset(widgetId);\n reject(new Error('error'));\n };\n\n this.grecaptchaLibrary.execute(widgetId);\n });\n }\n\n /** @inheritdoc */\n private setup(\n container: HTMLElement,\n recaptchaParams?: 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: recaptchaParams?.tabindex,\n theme: recaptchaParams?.theme,\n type: recaptchaParams?.type,\n size: recaptchaParams?.size ?? 'invisible',\n });\n }\n\n private createContainer(): 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.outline = '1px solid purple';\n element.style.width = '10px';\n element.style.height = '10px';\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"]}
File without changes
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ // /* eslint-disable camelcase */
3
+ // /* eslint-disable @typescript-eslint/no-explicit-any */
4
+ // /* eslint-disable @typescript-eslint/no-unused-vars */
5
+ // export enum MockGrecaptchaMode {
6
+ // Success,
7
+ // Expired,
8
+ // Error,
9
+ // }
10
+ // export class MockGrecaptcha implements ReCaptchaV2.ReCaptcha {
11
+ // renderCalled = false;
12
+ // executeCalled = false;
13
+ // resetCalled = false;
14
+ // getResponseCalled = false;
15
+ // mode: MockGrecaptchaMode;
16
+ // addDelay: boolean;
17
+ // callback?: (response: string) => void;
18
+ // 'expired-callback'?: () => void;
19
+ // 'error-callback'?: () => void;
20
+ // render(
21
+ // container: string | HTMLElement,
22
+ // parameters?: ReCaptchaV2.Parameters | undefined,
23
+ // inherit?: boolean | undefined,
24
+ // ): number {
25
+ // this.callback = parameters?.callback?.bind(this);
26
+ // this['error-callback'] = parameters?.['error-callback']?.bind(this);
27
+ // this['expired-callback'] = parameters?.['expired-callback']?.bind(this);
28
+ // this.renderCalled = true;
29
+ // return 1;
30
+ // }
31
+ // reset(opt_widget_id?: number | undefined): void {
32
+ // this.resetCalled = true;
33
+ // }
34
+ // getResponse(opt_widget_id?: number | undefined): string {
35
+ // this.getResponseCalled = true;
36
+ // return 'foo';
37
+ // }
38
+ // // execute(opt_widget_id?: number): void {
39
+ // // this.executeCalled = true;
40
+ // // if (this.addDelay) {
41
+ // // setTimeout(this.callCallback, 100);
42
+ // // } else {
43
+ // // this.callCallback();
44
+ // // }
45
+ // // }
46
+ // // /**
47
+ // // * Programatically invoke the reCAPTCHA check. Used if the invisible reCAPTCHA is on a div instead of a button.
48
+ // // * @param siteKey the key of your site
49
+ // // * @param action the action
50
+ // // *
51
+ // // * @return a promise-like object containing the token
52
+ // // */
53
+ // // async execute(siteKey: string, action: ReCaptchaV2.Action): Promise<string> {
54
+ // // return '';
55
+ // // }
56
+ // // execute(opt_widget_id?: number): void {
57
+ // // this.executeCalled = true;
58
+ // // if (this.addDelay) {
59
+ // // setTimeout(this.callCallback, 100);
60
+ // // } else {
61
+ // // this.callCallback();
62
+ // // }
63
+ // // }
64
+ // // execute(opt_widget_id?: number): void {
65
+ // // }
66
+ // // async execute(siteKey: string, action: ReCaptchaV2.Action): PromiseLike<string> {
67
+ // // return '';
68
+ // // }
69
+ // // async execute(siteKey?: any, action?: any): void | PromiseLike<string> {
70
+ // // }
71
+ // ready(callback: () => void): void {
72
+ // }
73
+ // private callCallback(): void {
74
+ // switch (this.mode) {
75
+ // case MockGrecaptchaMode.Success:
76
+ // if (this.callback) {
77
+ // this.callback('foo');
78
+ // }
79
+ // break;
80
+ // case MockGrecaptchaMode.Error:
81
+ // if (this['error-callback']) {
82
+ // this['error-callback']();
83
+ // }
84
+ // break;
85
+ // case MockGrecaptchaMode.Expired:
86
+ // if (this['expired-callback']) {
87
+ // this['expired-callback']();
88
+ // }
89
+ // break;
90
+ // default:
91
+ // break;
92
+ // }
93
+ // }
94
+ // constructor(mode: MockGrecaptchaMode, addDelay = false) {
95
+ // this.mode = mode;
96
+ // this.addDelay = addDelay;
97
+ // }
98
+ // }
99
+ //# sourceMappingURL=mock-grecaptcha.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-grecaptcha.js","sourceRoot":"","sources":["../../test/mock-grecaptcha.ts"],"names":[],"mappings":";AAAA,iCAAiC;AACjC,0DAA0D;AAC1D,yDAAyD;AAEzD,mCAAmC;AACnC,aAAa;AACb,aAAa;AACb,WAAW;AACX,IAAI;AAEJ,iEAAiE;AACjE,0BAA0B;AAE1B,2BAA2B;AAE3B,yBAAyB;AAEzB,+BAA+B;AAE/B,8BAA8B;AAE9B,uBAAuB;AAEvB,2CAA2C;AAE3C,qCAAqC;AAErC,mCAAmC;AAEnC,YAAY;AACZ,uCAAuC;AACvC,uDAAuD;AACvD,qCAAqC;AACrC,gBAAgB;AAChB,wDAAwD;AACxD,2EAA2E;AAC3E,+EAA+E;AAC/E,gCAAgC;AAChC,gBAAgB;AAChB,MAAM;AAEN,sDAAsD;AACtD,+BAA+B;AAC/B,MAAM;AAEN,8DAA8D;AAC9D,qCAAqC;AACrC,oBAAoB;AACpB,MAAM;AAEN,+CAA+C;AAC/C,oCAAoC;AAEpC,8BAA8B;AAC9B,+CAA+C;AAC/C,kBAAkB;AAClB,gCAAgC;AAChC,WAAW;AACX,SAAS;AACT,WAAW;AACX,uHAAuH;AACvH,8CAA8C;AAC9C,mCAAmC;AACnC,UAAU;AACV,6DAA6D;AAC7D,WAAW;AAEX,qFAAqF;AACrF,oBAAoB;AACpB,SAAS;AAET,+CAA+C;AAC/C,oCAAoC;AAEpC,8BAA8B;AAC9B,+CAA+C;AAC/C,kBAAkB;AAClB,gCAAgC;AAChC,WAAW;AACX,SAAS;AAET,+CAA+C;AAE/C,SAAS;AAET,yFAAyF;AACzF,oBAAoB;AACpB,SAAS;AAET,gFAAgF;AAEhF,SAAS;AAET,wCAAwC;AAExC,MAAM;AAEN,mCAAmC;AACnC,2BAA2B;AAC3B,yCAAyC;AACzC,+BAA+B;AAC/B,kCAAkC;AAClC,YAAY;AACZ,iBAAiB;AACjB,uCAAuC;AACvC,wCAAwC;AACxC,sCAAsC;AACtC,YAAY;AACZ,iBAAiB;AACjB,yCAAyC;AACzC,0CAA0C;AAC1C,wCAAwC;AACxC,YAAY;AACZ,iBAAiB;AACjB,iBAAiB;AACjB,iBAAiB;AACjB,QAAQ;AACR,MAAM;AAEN,8DAA8D;AAC9D,wBAAwB;AACxB,gCAAgC;AAChC,MAAM;AACN,IAAI","sourcesContent":["// /* eslint-disable camelcase */\n// /* eslint-disable @typescript-eslint/no-explicit-any */\n// /* eslint-disable @typescript-eslint/no-unused-vars */\n\n// export enum MockGrecaptchaMode {\n// Success,\n// Expired,\n// Error,\n// }\n\n// export class MockGrecaptcha implements ReCaptchaV2.ReCaptcha {\n// renderCalled = false;\n\n// executeCalled = false;\n\n// resetCalled = false;\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.resetCalled = true;\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, 100);\n// // } else {\n// // this.callCallback();\n// // }\n// // }\n// // /**\n// // * Programatically invoke the reCAPTCHA check. Used if the invisible reCAPTCHA is on a div instead of a button.\n// // * @param siteKey the key of your site\n// // * @param action the action\n// // *\n// // * @return a promise-like object containing the token\n// // */\n\n// // async execute(siteKey: string, action: ReCaptchaV2.Action): Promise<string> {\n// // return '';\n// // }\n\n// // execute(opt_widget_id?: number): void {\n// // this.executeCalled = true;\n\n// // if (this.addDelay) {\n// // setTimeout(this.callCallback, 100);\n// // } else {\n// // this.callCallback();\n// // }\n// // }\n\n// // execute(opt_widget_id?: number): void {\n\n// // }\n\n// // async execute(siteKey: string, action: ReCaptchaV2.Action): PromiseLike<string> {\n// // return '';\n// // }\n\n// // async execute(siteKey?: any, action?: any): void | PromiseLike<string> {\n\n// // }\n\n// ready(callback: () => void): void {\n\n// }\n\n// private callCallback(): void {\n// switch (this.mode) {\n// case MockGrecaptchaMode.Success:\n// if (this.callback) {\n// this.callback('foo');\n// }\n// break;\n// case MockGrecaptchaMode.Error:\n// if (this['error-callback']) {\n// this['error-callback']();\n// }\n// break;\n// case MockGrecaptchaMode.Expired:\n// if (this['expired-callback']) {\n// this['expired-callback']();\n// }\n// break;\n// default:\n// break;\n// }\n// }\n\n// constructor(mode: MockGrecaptchaMode, addDelay = false) {\n// this.mode = mode;\n// this.addDelay = addDelay;\n// }\n// }\n"]}
@@ -1,4 +1,69 @@
1
1
  "use strict";
2
- describe('YourWebComponent', () => {
2
+ // import { html, fixture, expect } from '@open-wc/testing';
3
+ // import { MockGrecaptcha, MockGrecaptchaMode } from './mock-grecaptcha';
4
+ // import { RecaptchaManager } from '../src/recaptcha-manager';
5
+ describe('ReCaptcha Manager', () => {
6
+ // it('can execute the recaptcha like a Promise', async () => {
7
+ // const recaptchaElement = (await fixture(html`
8
+ // <div></div>
9
+ // `)) as HTMLElement;
10
+ // const mockGrecaptcha = new MockGrecaptcha(MockGrecaptchaMode.Success);
11
+ // const recaptchaManager = new RecaptchaManager({
12
+ // grecaptchaLibrary: mockGrecaptcha,
13
+ // siteKey: '123',
14
+ // });
15
+ // recaptchaManager.setup(recaptchaElement, 1, 'dark', 'image');
16
+ // const result = await recaptchaManager.execute();
17
+ // expect(result).to.equal('foo');
18
+ // });
19
+ // it('can error properly like a Promise', async () => {
20
+ // const recaptchaElement = (await fixture(html`
21
+ // <div></div>
22
+ // `)) as HTMLElement;
23
+ // const mockGrecaptcha = new MockGrecaptcha(MockGrecaptchaMode.Error);
24
+ // const recaptchaManager = new RecaptchaManager({
25
+ // grecaptchaLibrary: mockGrecaptcha,
26
+ // siteKey: '123',
27
+ // });
28
+ // recaptchaManager.setup(recaptchaElement, 1, 'dark', 'image');
29
+ // try {
30
+ // await recaptchaManager.execute();
31
+ // expect.fail('should not get here');
32
+ // } catch (error) {
33
+ // expect(error).to.equal('error');
34
+ // }
35
+ // });
36
+ // it('can expire properly like a Promise', async () => {
37
+ // const recaptchaElement = (await fixture(html`
38
+ // <div></div>
39
+ // `)) as HTMLElement;
40
+ // const mockGrecaptcha = new MockGrecaptcha(MockGrecaptchaMode.Expired);
41
+ // const recaptchaManager = new RecaptchaManager({
42
+ // grecaptchaLibrary: mockGrecaptcha,
43
+ // siteKey: '123',
44
+ // });
45
+ // recaptchaManager.setup(recaptchaElement, 1, 'dark', 'image');
46
+ // try {
47
+ // await recaptchaManager.execute();
48
+ // expect.fail('should not get here');
49
+ // } catch (error) {
50
+ // expect(error).to.equal('expired');
51
+ // }
52
+ // });
53
+ // it('resets the captcha if execute() gets called more than once', async () => {
54
+ // const recaptchaElement = (await fixture(html`
55
+ // <div></div>
56
+ // `)) as HTMLElement;
57
+ // const mockGrecaptcha = new MockGrecaptcha(MockGrecaptchaMode.Success, true);
58
+ // const recaptchaManager = new RecaptchaManager({
59
+ // grecaptchaLibrary: mockGrecaptcha,
60
+ // siteKey: '123',
61
+ // });
62
+ // recaptchaManager.setup(recaptchaElement, 1, 'dark', 'image');
63
+ // recaptchaManager.execute();
64
+ // expect(mockGrecaptcha.resetCalled).to.be.false;
65
+ // recaptchaManager.execute();
66
+ // expect(mockGrecaptcha.resetCalled).to.be.true;
67
+ // });
3
68
  });
4
69
  //# 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":";AAAA,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;AAElC,CAAC,CAAC,CAAC","sourcesContent":["describe('YourWebComponent', () => {\n\n});\n"]}
1
+ {"version":3,"file":"recaptcha-manager.test.js","sourceRoot":"","sources":["../../test/recaptcha-manager.test.ts"],"names":[],"mappings":";AAAA,4DAA4D;AAC5D,0EAA0E;AAC1E,+DAA+D;AAE/D,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,+DAA+D;IAC/D,kDAAkD;IAClD,kBAAkB;IAClB,wBAAwB;IACxB,2EAA2E;IAC3E,oDAAoD;IACpD,yCAAyC;IACzC,sBAAsB;IACtB,QAAQ;IACR,kEAAkE;IAClE,qDAAqD;IACrD,oCAAoC;IACpC,MAAM;IACN,wDAAwD;IACxD,kDAAkD;IAClD,kBAAkB;IAClB,wBAAwB;IACxB,yEAAyE;IACzE,oDAAoD;IACpD,yCAAyC;IACzC,sBAAsB;IACtB,QAAQ;IACR,kEAAkE;IAClE,UAAU;IACV,wCAAwC;IACxC,0CAA0C;IAC1C,sBAAsB;IACtB,uCAAuC;IACvC,MAAM;IACN,MAAM;IACN,yDAAyD;IACzD,kDAAkD;IAClD,kBAAkB;IAClB,wBAAwB;IACxB,2EAA2E;IAC3E,oDAAoD;IACpD,yCAAyC;IACzC,sBAAsB;IACtB,QAAQ;IACR,kEAAkE;IAClE,UAAU;IACV,wCAAwC;IACxC,0CAA0C;IAC1C,sBAAsB;IACtB,yCAAyC;IACzC,MAAM;IACN,MAAM;IACN,iFAAiF;IACjF,kDAAkD;IAClD,kBAAkB;IAClB,wBAAwB;IACxB,iFAAiF;IACjF,oDAAoD;IACpD,yCAAyC;IACzC,sBAAsB;IACtB,QAAQ;IACR,kEAAkE;IAClE,gCAAgC;IAChC,oDAAoD;IACpD,gCAAgC;IAChC,mDAAmD;IACnD,MAAM;AACR,CAAC,CAAC,CAAC","sourcesContent":["// import { html, fixture, expect } from '@open-wc/testing';\n// import { MockGrecaptcha, MockGrecaptchaMode } from './mock-grecaptcha';\n// import { RecaptchaManager } from '../src/recaptcha-manager';\n\ndescribe('ReCaptcha Manager', () => {\n // it('can execute the recaptcha like a Promise', async () => {\n // const recaptchaElement = (await fixture(html`\n // <div></div>\n // `)) as HTMLElement;\n // const mockGrecaptcha = new MockGrecaptcha(MockGrecaptchaMode.Success);\n // const recaptchaManager = new RecaptchaManager({\n // grecaptchaLibrary: mockGrecaptcha,\n // siteKey: '123',\n // });\n // recaptchaManager.setup(recaptchaElement, 1, 'dark', 'image');\n // const result = await recaptchaManager.execute();\n // expect(result).to.equal('foo');\n // });\n // it('can error properly like a Promise', async () => {\n // const recaptchaElement = (await fixture(html`\n // <div></div>\n // `)) as HTMLElement;\n // const mockGrecaptcha = new MockGrecaptcha(MockGrecaptchaMode.Error);\n // const recaptchaManager = new RecaptchaManager({\n // grecaptchaLibrary: mockGrecaptcha,\n // siteKey: '123',\n // });\n // recaptchaManager.setup(recaptchaElement, 1, 'dark', 'image');\n // try {\n // await recaptchaManager.execute();\n // expect.fail('should not get here');\n // } catch (error) {\n // expect(error).to.equal('error');\n // }\n // });\n // it('can expire properly like a Promise', async () => {\n // const recaptchaElement = (await fixture(html`\n // <div></div>\n // `)) as HTMLElement;\n // const mockGrecaptcha = new MockGrecaptcha(MockGrecaptchaMode.Expired);\n // const recaptchaManager = new RecaptchaManager({\n // grecaptchaLibrary: mockGrecaptcha,\n // siteKey: '123',\n // });\n // recaptchaManager.setup(recaptchaElement, 1, 'dark', 'image');\n // try {\n // await recaptchaManager.execute();\n // expect.fail('should not get here');\n // } catch (error) {\n // expect(error).to.equal('expired');\n // }\n // });\n // it('resets the captcha if execute() gets called more than once', async () => {\n // const recaptchaElement = (await fixture(html`\n // <div></div>\n // `)) as HTMLElement;\n // const mockGrecaptcha = new MockGrecaptcha(MockGrecaptchaMode.Success, true);\n // const recaptchaManager = new RecaptchaManager({\n // grecaptchaLibrary: mockGrecaptcha,\n // siteKey: '123',\n // });\n // recaptchaManager.setup(recaptchaElement, 1, 'dark', 'image');\n // recaptchaManager.execute();\n // expect(mockGrecaptcha.resetCalled).to.be.false;\n // recaptchaManager.execute();\n // expect(mockGrecaptcha.resetCalled).to.be.true;\n // });\n});\n"]}
package/index.ts CHANGED
@@ -1 +1,9 @@
1
- export { RecaptchaManager, RecaptchaManagerInterface } from './src/recaptcha-manager';
1
+ export {
2
+ RecaptchaManager,
3
+ RecaptchaManagerInterface,
4
+ } from './src/recaptcha-manager';
5
+
6
+ export {
7
+ RecaptchaWidget,
8
+ RecaptchaWidgetInterface,
9
+ } from './src/recaptcha-widget';
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "license": "AGPL-3.0-only",
9
9
  "author": "Internet Archive",
10
- "version": "0.0.1-alpha.1",
10
+ "version": "0.0.1-alpha.4",
11
11
  "main": "dist/index.js",
12
12
  "module": "dist/index.js",
13
13
  "scripts": {
@@ -2,173 +2,86 @@ import {
2
2
  LazyLoaderService,
3
3
  LazyLoaderServiceInterface,
4
4
  } from '@internetarchive/lazy-loader-service';
5
+ import { RecaptchaWidget } from './recaptcha-widget';
5
6
 
6
7
  export interface RecaptchaManagerInterface {
7
- execute(): Promise<string>;
8
- setup(
9
- container: HTMLElement,
10
- tabIndex: number,
11
- theme: ReCaptchaV2.Theme,
12
- type: ReCaptchaV2.Type
13
- ): void;
8
+ /**
9
+ * Load a recaptcha widget for a given site key or the default site key.
10
+ */
11
+ getRecaptchaWidget(options?: {
12
+ siteKey?: string;
13
+ recaptchaParams?: ReCaptchaV2.Parameters;
14
+ }): Promise<RecaptchaWidget>;
14
15
  }
15
16
 
16
17
  export class RecaptchaManager implements RecaptchaManagerInterface {
17
- /**
18
- * This is a convenience initializer that also lazily loads
19
- * the recaptcha library from Google.
20
- *
21
- * @param options
22
- * @returns
23
- */
24
- static async getRecaptchaManager(options: {
25
- siteKey: string;
26
- }): Promise<RecaptchaManagerInterface> {
27
- const grecaptchaLibrary = await RecaptchaManager.loadRecaptchaLibrary();
28
- return new RecaptchaManager({
29
- grecaptchaLibrary,
30
- siteKey: options.siteKey,
31
- });
18
+ private lazyLoader: LazyLoaderServiceInterface;
19
+
20
+ private defaultSiteKey?: string;
21
+
22
+ private recaptchaCache: Record<string, RecaptchaWidget> = {};
23
+
24
+ constructor(options?: {
25
+ defaultSiteKey?: string;
26
+ lazyLoader?: LazyLoaderServiceInterface;
27
+ grecaptchaLibrary?: ReCaptchaV2.ReCaptcha; // allows dependency injection or will be lazy loaded
28
+ }) {
29
+ this.defaultSiteKey = options?.defaultSiteKey;
30
+ this.lazyLoader = options?.lazyLoader ?? new LazyLoaderService();
31
+ this.grecaptchaLibraryCache = options?.grecaptchaLibrary;
32
+ }
33
+
34
+ /** @inheritdoc */
35
+ async getRecaptchaWidget(options?: {
36
+ siteKey?: string;
37
+ recaptchaParams?: ReCaptchaV2.Parameters;
38
+ }): Promise<RecaptchaWidget> {
39
+ const key = options?.siteKey ?? this.defaultSiteKey;
40
+ if (!key) {
41
+ throw new Error('RecaptchaManager requires a site key');
42
+ }
43
+
44
+ const cached = this.recaptchaCache[key];
45
+ if (cached) return cached;
46
+
47
+ const grecaptchaLibrary = await this.getRecaptchaLibrary();
48
+ const recaptcha = new RecaptchaWidget(
49
+ {
50
+ siteKey: key,
51
+ grecaptchaLibrary,
52
+ },
53
+ options?.recaptchaParams
54
+ );
55
+
56
+ this.recaptchaCache[key] = recaptcha;
57
+ return recaptcha;
32
58
  }
33
59
 
34
60
  /**
35
61
  * Load the Recaptch library from Google.
36
62
  *
37
- * @param options
38
- * @returns
63
+ * @returns Promise<ReCaptchaV2.ReCaptcha>
39
64
  */
40
- static async loadRecaptchaLibrary(options?: {
41
- lazyLoader?: LazyLoaderServiceInterface;
42
- }): Promise<ReCaptchaV2.ReCaptcha> {
43
- const service = options?.lazyLoader ?? new LazyLoaderService();
44
-
65
+ private async getRecaptchaLibrary(): Promise<ReCaptchaV2.ReCaptcha> {
66
+ if (this.grecaptchaLibraryCache) {
67
+ return this.grecaptchaLibraryCache;
68
+ }
45
69
  return new Promise(resolve => {
46
70
  (window as any).grecaptchaLoadedCallback = (): void => {
47
71
  setTimeout(() => {
48
72
  // remove the callback when we're done with it
49
73
  delete (window as any).grecaptchaLoadedCallback;
50
74
  }, 10);
75
+ this.grecaptchaLibraryCache = window.grecaptcha;
51
76
  resolve(window.grecaptcha);
52
77
  };
53
78
 
54
- service.loadScript({
79
+ this.lazyLoader.loadScript({
55
80
  src: 'https://www.google.com/recaptcha/api.js?onload=grecaptchaLoadedCallback&render=explicit',
56
81
  });
57
82
  });
58
83
  }
59
84
 
60
- private grecaptchaLibrary: ReCaptchaV2.ReCaptcha;
61
-
62
- private siteKey: string;
63
-
64
- constructor(options: {
65
- grecaptchaLibrary: ReCaptchaV2.ReCaptcha;
66
- siteKey: string;
67
- }) {
68
- this.grecaptchaLibrary = options.grecaptchaLibrary;
69
- this.siteKey = options.siteKey;
70
- }
71
-
72
- private executionSuccessBlock?: (token: string) => void;
73
-
74
- private executionExpiredBlock?: () => void;
75
-
76
- private executionErrorBlock?: () => void;
77
-
78
- private isExecuting = false;
79
-
80
- /**
81
- * Execute Recaptcha and return a Promise containing the response token.
82
- *
83
- * This is an interesting flow.. we call `execute()` here, but have to wait for the
84
- * response and expiration handlers that we bind during the inital `setup` call.
85
- * For consumers, we want to be able to just call `execute()` and wait for a response.
86
- * To allow this, we assign two callbacks:
87
- * - `executionSuccessBlock`
88
- * - `executionExpiredBlock`
89
- *
90
- * We then call those callbacks from inside `responseHandler` and `expiredHandler` to
91
- * either resolve or reject the Promise.
92
- *
93
- * ie:
94
- *
95
- * try {
96
- * const recaptchaResult = await recaptchaManager.execute();
97
- * console.log('recaptcha token:', recaptchaResult);
98
- * } catch {
99
- * console.error('something happened')
100
- * }
101
- *
102
- * @returns {Promise<string>}
103
- * @memberof RecaptchaManager
104
- */
105
- execute(): Promise<string> {
106
- if (this.isExecuting) {
107
- this.finishExecution();
108
- }
109
- this.isExecuting = true;
110
- return new Promise((resolve, reject) => {
111
- this.executionSuccessBlock = (token: string): void => {
112
- this.finishExecution();
113
- resolve(token);
114
- };
115
-
116
- this.executionExpiredBlock = (): void => {
117
- this.finishExecution();
118
- reject(new Error('expired'));
119
- };
120
-
121
- this.executionErrorBlock = (): void => {
122
- this.finishExecution();
123
- reject(new Error('error'));
124
- };
125
-
126
- this.grecaptchaLibrary.execute();
127
- });
128
- }
129
-
130
- private finishExecution(): void {
131
- this.isExecuting = false;
132
- this.grecaptchaLibrary.reset();
133
- }
134
-
135
- setup(
136
- container: HTMLElement,
137
- tabIndex: number,
138
- theme: ReCaptchaV2.Theme,
139
- type: ReCaptchaV2.Type
140
- ): void {
141
- this.grecaptchaLibrary.render(container, {
142
- callback: this.responseHandler.bind(this),
143
- 'expired-callback': this.expiredHandler.bind(this),
144
- 'error-callback': this.errorHandler.bind(this),
145
- sitekey: this.siteKey,
146
- tabindex: tabIndex,
147
- theme,
148
- type,
149
- size: 'invisible',
150
- });
151
- }
152
-
153
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
154
- private responseHandler(response: any): void {
155
- if (this.executionSuccessBlock) {
156
- this.executionSuccessBlock(response);
157
- this.executionSuccessBlock = undefined;
158
- }
159
- }
160
-
161
- private expiredHandler(): void {
162
- if (this.executionExpiredBlock) {
163
- this.executionExpiredBlock();
164
- this.executionExpiredBlock = undefined;
165
- }
166
- }
167
-
168
- private errorHandler(): void {
169
- if (this.executionErrorBlock) {
170
- this.executionErrorBlock();
171
- this.executionErrorBlock = undefined;
172
- }
173
- }
85
+ /** don't use directly, use `getRecaptchaLibrary()` */
86
+ private grecaptchaLibraryCache?: ReCaptchaV2.ReCaptcha;
174
87
  }