@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,146 @@
1
+ /**
2
+ * This encompasses a widget for a given site key.
3
+ */
4
+ export interface RecaptchaWidgetInterface {
5
+ /** execute the recaptcha request and returns a token */
6
+ execute(): Promise<string>;
7
+ }
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
+
20
+ /**
21
+ * This encompasses a widget for a given site key.
22
+ */
23
+ export class RecaptchaWidget implements RecaptchaWidgetInterface {
24
+ private executionSuccessBlock?: (token: string) => void;
25
+
26
+ private executionExpiredBlock?: () => void;
27
+
28
+ private executionErrorBlock?: () => void;
29
+
30
+ private grecaptchaLibrary: ReCaptchaV2.ReCaptcha;
31
+
32
+ private siteKey: string;
33
+
34
+ private widgetId: number | null = null;
35
+
36
+ private isExecuting = false;
37
+
38
+ constructor(
39
+ config: {
40
+ siteKey: string;
41
+ grecaptchaLibrary: ReCaptchaV2.ReCaptcha;
42
+ },
43
+ widgetConfig?: RecaptchaWidgetConfig
44
+ ) {
45
+ this.siteKey = config.siteKey;
46
+ this.grecaptchaLibrary = config.grecaptchaLibrary;
47
+
48
+ const container = this.createContainer();
49
+ this.setup(container, widgetConfig);
50
+ }
51
+
52
+ /** @inheritdoc */
53
+ async execute(): Promise<string> {
54
+ const { widgetId } = this;
55
+
56
+ if (widgetId === null) {
57
+ throw new Error('Recaptcha is not setup');
58
+ }
59
+
60
+ if (this.isExecuting) {
61
+ // there is no way to know when users have dismissed a previous recaptcha
62
+ // so if we're still in the middle of execution when we call execute again,
63
+ // just reset it and execute it again
64
+ this.finishExecution();
65
+ }
66
+
67
+ return new Promise((resolve, reject) => {
68
+ this.executionSuccessBlock = (token: string): void => {
69
+ this.finishExecution();
70
+ resolve(token);
71
+ };
72
+
73
+ this.executionExpiredBlock = (): void => {
74
+ this.finishExecution();
75
+ reject(new Error('expired'));
76
+ };
77
+
78
+ this.executionErrorBlock = (): void => {
79
+ this.finishExecution();
80
+ reject(new Error('error'));
81
+ };
82
+
83
+ this.grecaptchaLibrary.execute(widgetId);
84
+ });
85
+ }
86
+
87
+ private finishExecution() {
88
+ this.isExecuting = false;
89
+ const { widgetId } = this;
90
+ if (widgetId === null) return;
91
+ this.grecaptchaLibrary.reset(widgetId);
92
+ }
93
+
94
+ private setup(
95
+ container: HTMLElement,
96
+ widgetConfig?: ReCaptchaV2.Parameters
97
+ ): void {
98
+ this.widgetId = this.grecaptchaLibrary.render(container, {
99
+ callback: this.responseHandler.bind(this),
100
+ 'expired-callback': this.expiredHandler.bind(this),
101
+ 'error-callback': this.errorHandler.bind(this),
102
+ sitekey: this.siteKey,
103
+ tabindex: widgetConfig?.tabindex,
104
+ theme: widgetConfig?.theme,
105
+ type: widgetConfig?.type,
106
+ size: widgetConfig?.size ?? 'invisible',
107
+ badge: widgetConfig?.badge,
108
+ });
109
+ }
110
+
111
+ private createContainer(zIndex?: number): HTMLElement {
112
+ const elementId = `recaptchaManager-${this.siteKey}`;
113
+ let element: HTMLElement | null = document.getElementById(elementId);
114
+ if (!element) {
115
+ element = document.createElement('div');
116
+ element.id = elementId;
117
+ element.style.position = 'fixed';
118
+ element.style.top = '50%';
119
+ element.style.left = '50%';
120
+ element.style.zIndex = zIndex ? `${zIndex}` : '10';
121
+ document.body.appendChild(element);
122
+ }
123
+ return element;
124
+ }
125
+
126
+ private responseHandler(response: any): void {
127
+ if (this.executionSuccessBlock) {
128
+ this.executionSuccessBlock(response);
129
+ this.executionSuccessBlock = undefined;
130
+ }
131
+ }
132
+
133
+ private expiredHandler(): void {
134
+ if (this.executionExpiredBlock) {
135
+ this.executionExpiredBlock();
136
+ this.executionExpiredBlock = undefined;
137
+ }
138
+ }
139
+
140
+ private errorHandler(): void {
141
+ if (this.executionErrorBlock) {
142
+ this.executionErrorBlock();
143
+ this.executionErrorBlock = undefined;
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,124 @@
1
+ // /* eslint-disable camelcase */
2
+ // /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ // /* eslint-disable @typescript-eslint/no-unused-vars */
4
+
5
+ // export enum MockGrecaptchaMode {
6
+ // Success,
7
+ // Expired,
8
+ // Error,
9
+ // }
10
+
11
+ // export class MockGrecaptcha implements ReCaptchaV2.ReCaptcha {
12
+ // renderCalled = false;
13
+
14
+ // executeCalled = false;
15
+
16
+ // resetCalled = false;
17
+
18
+ // getResponseCalled = false;
19
+
20
+ // mode: MockGrecaptchaMode;
21
+
22
+ // addDelay: boolean;
23
+
24
+ // callback?: (response: string) => void;
25
+
26
+ // 'expired-callback'?: () => void;
27
+
28
+ // 'error-callback'?: () => void;
29
+
30
+ // render(
31
+ // container: string | HTMLElement,
32
+ // parameters?: ReCaptchaV2.Parameters | undefined,
33
+ // inherit?: boolean | undefined,
34
+ // ): number {
35
+ // this.callback = parameters?.callback?.bind(this);
36
+ // this['error-callback'] = parameters?.['error-callback']?.bind(this);
37
+ // this['expired-callback'] = parameters?.['expired-callback']?.bind(this);
38
+ // this.renderCalled = true;
39
+ // return 1;
40
+ // }
41
+
42
+ // reset(opt_widget_id?: number | undefined): void {
43
+ // this.resetCalled = true;
44
+ // }
45
+
46
+ // getResponse(opt_widget_id?: number | undefined): string {
47
+ // this.getResponseCalled = true;
48
+ // return 'foo';
49
+ // }
50
+
51
+ // // execute(opt_widget_id?: number): void {
52
+ // // this.executeCalled = true;
53
+
54
+ // // if (this.addDelay) {
55
+ // // setTimeout(this.callCallback, 100);
56
+ // // } else {
57
+ // // this.callCallback();
58
+ // // }
59
+ // // }
60
+ // // /**
61
+ // // * Programatically invoke the reCAPTCHA check. Used if the invisible reCAPTCHA is on a div instead of a button.
62
+ // // * @param siteKey the key of your site
63
+ // // * @param action the action
64
+ // // *
65
+ // // * @return a promise-like object containing the token
66
+ // // */
67
+
68
+ // // async execute(siteKey: string, action: ReCaptchaV2.Action): Promise<string> {
69
+ // // return '';
70
+ // // }
71
+
72
+ // // execute(opt_widget_id?: number): void {
73
+ // // this.executeCalled = true;
74
+
75
+ // // if (this.addDelay) {
76
+ // // setTimeout(this.callCallback, 100);
77
+ // // } else {
78
+ // // this.callCallback();
79
+ // // }
80
+ // // }
81
+
82
+ // // execute(opt_widget_id?: number): void {
83
+
84
+ // // }
85
+
86
+ // // async execute(siteKey: string, action: ReCaptchaV2.Action): PromiseLike<string> {
87
+ // // return '';
88
+ // // }
89
+
90
+ // // async execute(siteKey?: any, action?: any): void | PromiseLike<string> {
91
+
92
+ // // }
93
+
94
+ // ready(callback: () => void): void {
95
+
96
+ // }
97
+
98
+ // private callCallback(): void {
99
+ // switch (this.mode) {
100
+ // case MockGrecaptchaMode.Success:
101
+ // if (this.callback) {
102
+ // this.callback('foo');
103
+ // }
104
+ // break;
105
+ // case MockGrecaptchaMode.Error:
106
+ // if (this['error-callback']) {
107
+ // this['error-callback']();
108
+ // }
109
+ // break;
110
+ // case MockGrecaptchaMode.Expired:
111
+ // if (this['expired-callback']) {
112
+ // this['expired-callback']();
113
+ // }
114
+ // break;
115
+ // default:
116
+ // break;
117
+ // }
118
+ // }
119
+
120
+ // constructor(mode: MockGrecaptchaMode, addDelay = false) {
121
+ // this.mode = mode;
122
+ // this.addDelay = addDelay;
123
+ // }
124
+ // }
@@ -1,3 +1,68 @@
1
- describe('YourWebComponent', () => {
1
+ // import { html, fixture, expect } from '@open-wc/testing';
2
+ // import { MockGrecaptcha, MockGrecaptchaMode } from './mock-grecaptcha';
3
+ // import { RecaptchaManager } from '../src/recaptcha-manager';
2
4
 
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
  });