@internetarchive/recaptcha-manager 1.0.0 → 2.0.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.
Files changed (45) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/.editorconfig +29 -0
  3. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/.github/workflows/ci.yml +27 -0
  4. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/.github/workflows/gh-pages-main.yml +39 -0
  5. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/.github/workflows/pr-preview.yml +63 -0
  6. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/.husky/pre-commit +1 -0
  7. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/.prettierignore +1 -0
  8. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/LICENSE +661 -0
  9. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/README.md +63 -0
  10. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/coverage/.gitignore +3 -0
  11. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/coverage/.placeholder +0 -0
  12. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/demo/app-root.ts +111 -0
  13. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/demo/index.html +21 -0
  14. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/eslint.config.mjs +53 -0
  15. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/index.ts +10 -0
  16. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/package-lock.json +11336 -0
  17. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/package.json +76 -0
  18. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/renovate.json +6 -0
  19. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/src/recaptcha-manager.ts +114 -0
  20. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/src/recaptcha-widget.ts +148 -0
  21. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/ssl/server.crt +22 -0
  22. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/ssl/server.key +28 -0
  23. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/test/mock-grecaptcha.ts +85 -0
  24. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/test/mock-lazy-loader.ts +36 -0
  25. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/test/recaptcha-manager.test.ts +201 -0
  26. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/tsconfig.json +31 -0
  27. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/vite.config.ts +25 -0
  28. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/web-dev-server.config.mjs +32 -0
  29. package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/web-test-runner.config.mjs +41 -0
  30. package/demo/app-root.ts +19 -1
  31. package/dist/demo/app-root.d.ts +2 -0
  32. package/dist/demo/app-root.js +19 -1
  33. package/dist/demo/app-root.js.map +1 -1
  34. package/dist/src/recaptcha-manager.d.ts +14 -1
  35. package/dist/src/recaptcha-manager.js +16 -4
  36. package/dist/src/recaptcha-manager.js.map +1 -1
  37. package/dist/test/mock-lazy-loader.d.ts +2 -0
  38. package/dist/test/mock-lazy-loader.js +4 -6
  39. package/dist/test/mock-lazy-loader.js.map +1 -1
  40. package/dist/test/recaptcha-manager.test.js +42 -3
  41. package/dist/test/recaptcha-manager.test.js.map +1 -1
  42. package/package.json +3 -3
  43. package/src/recaptcha-manager.ts +28 -3
  44. package/test/mock-lazy-loader.ts +3 -6
  45. package/test/recaptcha-manager.test.ts +53 -3
@@ -0,0 +1,201 @@
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
+ // Mock lazy loader that mimics the grecaptcha script calling the callback on load
61
+ const mockLazyLoaderWithCallback = new MockLazyLoaderService(() => {
62
+ window.grecaptcha = new MockGrecaptcha({
63
+ mode: 'success',
64
+ });
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ (window as any).grecaptchaLoadedCallback();
67
+ });
68
+
69
+ const recaptchaManager = new RecaptchaManager({
70
+ lazyLoader: mockLazyLoaderWithCallback,
71
+ defaultSiteKey: '123',
72
+ timeout: 100,
73
+ });
74
+
75
+ await recaptchaManager.getRecaptchaWidget();
76
+ const loadUrl = mockLazyLoaderWithCallback.loadScriptSrc;
77
+ expect(
78
+ loadUrl?.includes(
79
+ 'https://www.recaptcha.net/recaptcha/api.js?onload=grecaptchaLoadedCallback',
80
+ ),
81
+ ).to.be.true;
82
+ });
83
+
84
+ it('rejects after specified timeout if grecaptcha fails to execute callback', async () => {
85
+ const recaptchaManager = new RecaptchaManager({
86
+ lazyLoader: mockLazyLoader,
87
+ defaultSiteKey: '123',
88
+ timeout: 100,
89
+ });
90
+
91
+ try {
92
+ await recaptchaManager.getRecaptchaWidget();
93
+ expect.fail('recaptcha load did not time out as expected');
94
+ } catch (err) {
95
+ expect(err).to.be.instanceOf(Error);
96
+ expect((err as Error).message).to.equal(
97
+ 'grecaptcha failed to execute callback',
98
+ );
99
+ }
100
+ });
101
+
102
+ it('can set the timeout delay', async () => {
103
+ const recaptchaManager = new RecaptchaManager({
104
+ lazyLoader: mockLazyLoader,
105
+ defaultSiteKey: '123',
106
+ });
107
+
108
+ recaptchaManager.setTimeoutDelay(1);
109
+
110
+ try {
111
+ await recaptchaManager.getRecaptchaWidget();
112
+ expect.fail('recaptcha load did not time out as expected');
113
+ } catch (err) {
114
+ expect(err).to.be.instanceOf(Error);
115
+ expect((err as Error).message).to.equal(
116
+ 'grecaptcha failed to execute callback',
117
+ );
118
+ }
119
+ });
120
+ });
121
+
122
+ describe('ReCaptcha Widget', () => {
123
+ it('can error properly like a Promise', async () => {
124
+ const mockGrecaptcha = new MockGrecaptcha({
125
+ mode: 'error',
126
+ });
127
+ const recaptchaManager = new RecaptchaManager({
128
+ lazyLoader: mockLazyLoader,
129
+ grecaptchaLibrary: mockGrecaptcha,
130
+ defaultSiteKey: '123',
131
+ });
132
+ const recaptcha = await recaptchaManager.getRecaptchaWidget();
133
+
134
+ try {
135
+ await recaptcha.execute();
136
+ expect.fail('should not get here');
137
+ } catch (error) {
138
+ expect((error as Error).message).to.equal('error');
139
+ }
140
+ });
141
+
142
+ it('can expire properly like a Promise', async () => {
143
+ const mockGrecaptcha = new MockGrecaptcha({
144
+ mode: 'expired',
145
+ });
146
+ const recaptchaManager = new RecaptchaManager({
147
+ lazyLoader: mockLazyLoader,
148
+ grecaptchaLibrary: mockGrecaptcha,
149
+ defaultSiteKey: '123',
150
+ });
151
+ const recaptcha = await recaptchaManager.getRecaptchaWidget();
152
+
153
+ try {
154
+ await recaptcha.execute();
155
+ expect.fail('should not get here');
156
+ } catch (error) {
157
+ expect((error as Error).message).to.equal('expired');
158
+ }
159
+ });
160
+
161
+ it('resets the captcha after execution', async () => {
162
+ const mockGrecaptcha = new MockGrecaptcha({
163
+ mode: 'success',
164
+ addDelay: true,
165
+ });
166
+ const recaptchaManager = new RecaptchaManager({
167
+ lazyLoader: mockLazyLoader,
168
+ grecaptchaLibrary: mockGrecaptcha,
169
+ defaultSiteKey: '123',
170
+ });
171
+ const recaptcha = await recaptchaManager.getRecaptchaWidget();
172
+
173
+ expect(mockGrecaptcha.resetCallCount).to.equal(0);
174
+ await recaptcha.execute();
175
+ expect(mockGrecaptcha.resetCallCount).to.equal(1);
176
+ await recaptcha.execute();
177
+ expect(mockGrecaptcha.resetCallCount).to.equal(2);
178
+ });
179
+
180
+ // there's no way to know if the user has dismissed the captcha so if another execution
181
+ // comes through while the captcha is still active, it will be reset
182
+ it('resets the captcha if another execution gets called', async () => {
183
+ const mockGrecaptcha = new MockGrecaptcha({
184
+ mode: 'success',
185
+ addDelay: true,
186
+ });
187
+ const recaptchaManager = new RecaptchaManager({
188
+ lazyLoader: mockLazyLoader,
189
+ grecaptchaLibrary: mockGrecaptcha,
190
+ defaultSiteKey: '123',
191
+ });
192
+ const recaptcha = await recaptchaManager.getRecaptchaWidget();
193
+
194
+ expect(mockGrecaptcha.resetCallCount).to.equal(0);
195
+ recaptcha.execute();
196
+ expect(mockGrecaptcha.resetCallCount).to.equal(0);
197
+ await recaptcha.execute();
198
+ expect(mockGrecaptcha.resetCallCount).to.equal(2);
199
+ });
200
+ });
201
+ });
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2018",
4
+ "module": "esnext",
5
+ "moduleResolution": "node",
6
+ "noEmitOnError": true,
7
+ "lib": ["es2017", "dom"],
8
+ "strict": true,
9
+ "esModuleInterop": false,
10
+ "allowSyntheticDefaultImports": true,
11
+ "experimentalDecorators": true,
12
+ "importHelpers": true,
13
+ "outDir": "dist",
14
+ "sourceMap": true,
15
+ "inlineSources": true,
16
+ "rootDir": "./",
17
+ "declaration": true,
18
+ "plugins": [
19
+ {
20
+ "name": "ts-lit-plugin"
21
+ }
22
+ ],
23
+ "paths": {
24
+ // workaround for: https://github.com/vitest-dev/vitest/issues/4567
25
+ "rollup/parseAst": [
26
+ "./node_modules/rollup/dist/parseAst"
27
+ ]
28
+ }
29
+ },
30
+ "include": ["**/*.ts"]
31
+ }
@@ -0,0 +1,25 @@
1
+ import { defineConfig } from 'vite';
2
+ import { resolve } from 'path';
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ base: './',
7
+ root: resolve(__dirname, './demo'),
8
+ build: {
9
+ /**
10
+ * This is the directory where the built files will be placed
11
+ * that we upload to GitHub Pages.
12
+ */
13
+ outDir: '../ghpages/demo',
14
+ emptyOutDir: true,
15
+ manifest: true,
16
+ rollupOptions: {
17
+ input: {
18
+ main: resolve(__dirname, 'demo/index.html'),
19
+ },
20
+ output: {
21
+ entryFileNames: 'app-root.js',
22
+ },
23
+ },
24
+ },
25
+ });
@@ -0,0 +1,32 @@
1
+ // import { hmrPlugin, presets } from '@open-wc/dev-server-hmr';
2
+
3
+ /** Use Hot Module replacement by adding --hmr to the start command */
4
+ const hmr = process.argv.includes('--hmr');
5
+
6
+ export default /** @type {import('@web/dev-server').DevServerConfig} */ ({
7
+ nodeResolve: true,
8
+ open: '/demo',
9
+ watch: !hmr,
10
+ sslCert: './ssl/server.crt',
11
+ sslKey: './ssl/server.key',
12
+ hostname: 'local.archive.org',
13
+ http2: true,
14
+
15
+ /** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */
16
+ // esbuildTarget: 'auto'
17
+
18
+ /** Set appIndex to enable SPA routing */
19
+ // appIndex: 'demo/index.html',
20
+
21
+ /** Configure bare import resolve plugin */
22
+ // nodeResolve: {
23
+ // exportConditions: ['browser', 'development']
24
+ // },
25
+
26
+ plugins: [
27
+ /** Use Hot Module Replacement by uncommenting. Requires @open-wc/dev-server-hmr plugin */
28
+ // hmr && hmrPlugin({ exclude: ['**/*/node_modules/**/*'], presets: [presets.litElement] }),
29
+ ],
30
+
31
+ // See documentation for all available options
32
+ });
@@ -0,0 +1,41 @@
1
+ // import { playwrightLauncher } from '@web/test-runner-playwright';
2
+
3
+ const filteredLogs = ['Running in dev mode', 'lit-html is in dev mode'];
4
+
5
+ export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({
6
+ /** Test files to run */
7
+ files: 'dist/test/**/*.test.js',
8
+
9
+ /** Resolve bare module imports */
10
+ nodeResolve: {
11
+ exportConditions: ['browser', 'development'],
12
+ },
13
+
14
+ /** Filter out lit dev mode logs */
15
+ filterBrowserLogs(log) {
16
+ for (const arg of log.args) {
17
+ if (typeof arg === 'string' && filteredLogs.some(l => arg.includes(l))) {
18
+ return false;
19
+ }
20
+ }
21
+ return true;
22
+ },
23
+
24
+ /** Compile JS for older browsers. Requires @web/dev-server-esbuild plugin */
25
+ // esbuildTarget: 'auto',
26
+
27
+ /** Amount of browsers to run concurrently */
28
+ // concurrentBrowsers: 2,
29
+
30
+ /** Amount of test files per browser to test concurrently */
31
+ // concurrency: 1,
32
+
33
+ /** Browsers to run tests on */
34
+ // browsers: [
35
+ // playwrightLauncher({ product: 'chromium' }),
36
+ // playwrightLauncher({ product: 'firefox' }),
37
+ // playwrightLauncher({ product: 'webkit' }),
38
+ // ],
39
+
40
+ // See documentation for all available options
41
+ });
package/demo/app-root.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { html, css, LitElement } from 'lit';
2
- import { customElement, state } from 'lit/decorators.js';
2
+ import { customElement, state, query } from 'lit/decorators.js';
3
3
  import { RecaptchaWidgetInterface } from '../src/recaptcha-widget';
4
4
  import {
5
5
  RecaptchaManager,
@@ -12,6 +12,8 @@ export class AppRoot extends LitElement {
12
12
 
13
13
  @state() result2?: string;
14
14
 
15
+ @query('#timeout') timeoutInput!: HTMLInputElement;
16
+
15
17
  private recaptchaManager: RecaptchaManagerInterface = new RecaptchaManager({
16
18
  defaultSiteKey: '6Ld64a8UAAAAAGbDwi1927ztGNw7YABQ-dqzvTN2',
17
19
  });
@@ -22,6 +24,18 @@ export class AppRoot extends LitElement {
22
24
 
23
25
  render() {
24
26
  return html`
27
+ <label for="timeout">
28
+ Timeout (ms):
29
+ <input
30
+ type="number"
31
+ id="timeout"
32
+ value="10000"
33
+ min="0"
34
+ max="99999"
35
+ step="100"
36
+ @change=${this.updateTimeout}
37
+ />
38
+ </label>
25
39
  <p>
26
40
  <button @click="${this.loadRecaptcha}">Load Recaptcha</button
27
41
  ><button @click="${this.executeRecaptcha}">Execute Recaptcha</button>
@@ -43,6 +57,10 @@ export class AppRoot extends LitElement {
43
57
  `;
44
58
  }
45
59
 
60
+ private updateTimeout() {
61
+ this.recaptchaManager.setTimeoutDelay(+this.timeoutInput.value);
62
+ }
63
+
46
64
  private async loadRecaptcha() {
47
65
  this.recaptcha1 = await this.recaptchaManager?.getRecaptchaWidget({
48
66
  recaptchaParams: {
@@ -2,10 +2,12 @@ import { LitElement } from 'lit';
2
2
  export declare class AppRoot extends LitElement {
3
3
  result?: string;
4
4
  result2?: string;
5
+ timeoutInput: HTMLInputElement;
5
6
  private recaptchaManager;
6
7
  private recaptcha1?;
7
8
  private recaptcha2?;
8
9
  render(): import("lit").TemplateResult<1>;
10
+ private updateTimeout;
9
11
  private loadRecaptcha;
10
12
  private executeRecaptcha;
11
13
  private loadRecaptcha2;
@@ -1,6 +1,6 @@
1
1
  import { __decorate } from "tslib";
2
2
  import { html, css, LitElement } from 'lit';
3
- import { customElement, state } from 'lit/decorators.js';
3
+ import { customElement, state, query } from 'lit/decorators.js';
4
4
  import { RecaptchaManager, } from '../src/recaptcha-manager';
5
5
  let AppRoot = class AppRoot extends LitElement {
6
6
  constructor() {
@@ -11,6 +11,18 @@ let AppRoot = class AppRoot extends LitElement {
11
11
  }
12
12
  render() {
13
13
  return html `
14
+ <label for="timeout">
15
+ Timeout (ms):
16
+ <input
17
+ type="number"
18
+ id="timeout"
19
+ value="10000"
20
+ min="0"
21
+ max="99999"
22
+ step="100"
23
+ @change=${this.updateTimeout}
24
+ />
25
+ </label>
14
26
  <p>
15
27
  <button @click="${this.loadRecaptcha}">Load Recaptcha</button
16
28
  ><button @click="${this.executeRecaptcha}">Execute Recaptcha</button>
@@ -31,6 +43,9 @@ let AppRoot = class AppRoot extends LitElement {
31
43
  : ''}
32
44
  `;
33
45
  }
46
+ updateTimeout() {
47
+ this.recaptchaManager.setTimeoutDelay(+this.timeoutInput.value);
48
+ }
34
49
  async loadRecaptcha() {
35
50
  var _a;
36
51
  this.recaptcha1 = await ((_a = this.recaptchaManager) === null || _a === void 0 ? void 0 : _a.getRecaptchaWidget({
@@ -79,6 +94,9 @@ __decorate([
79
94
  __decorate([
80
95
  state()
81
96
  ], AppRoot.prototype, "result2", void 0);
97
+ __decorate([
98
+ query('#timeout')
99
+ ], AppRoot.prototype, "timeoutInput", void 0);
82
100
  AppRoot = __decorate([
83
101
  customElement('app-root')
84
102
  ], AppRoot);
@@ -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;AAG3B,IAAM,OAAO,GAAb,MAAM,OAAQ,SAAQ,UAAU;IAAhC;;QAKG,qBAAgB,GAA8B,IAAI,gBAAgB,CAAC;YACzE,cAAc,EAAE,0CAA0C;SAC3D,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,CAAC;YACrB,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC7B,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;YACzC,OAAO;QACT,CAAC;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,CAAC;YACrB,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC7B,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;YACzC,OAAO;QACT,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;IACjD,CAAC;;AAEM,cAAM,GAAG,GAAG,CAAA;;;;GAIlB,AAJY,CAIX;AAjFO;IAAR,KAAK,EAAE;uCAAiB;AAEhB;IAAR,KAAK,EAAE;wCAAkB;AAHf,OAAO;IADnB,aAAa,CAAC,UAAU,CAAC;GACb,OAAO,CAmFnB","sourcesContent":["import { html, css, LitElement } from 'lit';\nimport { customElement, state } from 'lit/decorators.js';\nimport { RecaptchaWidgetInterface } 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: '6Ld64a8UAAAAAGbDwi1927ztGNw7YABQ-dqzvTN2',\n });\n\n private recaptcha1?: RecaptchaWidgetInterface;\n\n private recaptcha2?: RecaptchaWidgetInterface;\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"]}
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,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EACL,gBAAgB,GAEjB,MAAM,0BAA0B,CAAC;AAG3B,IAAM,OAAO,GAAb,MAAM,OAAQ,SAAQ,UAAU;IAAhC;;QAOG,qBAAgB,GAA8B,IAAI,gBAAgB,CAAC;YACzE,cAAc,EAAE,0CAA0C;SAC3D,CAAC,CAAC;IA4FL,CAAC;IAtFC,MAAM;QACJ,OAAO,IAAI,CAAA;;;;;;;;;;oBAUK,IAAI,CAAC,aAAa;;;;0BAIZ,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,aAAa;QACnB,IAAI,CAAC,gBAAgB,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IAClE,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,CAAC;YACrB,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC7B,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;YACzC,OAAO;QACT,CAAC;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,CAAC;YACrB,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC7B,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACrB,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;YACzC,OAAO;QACT,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;IACjD,CAAC;;AAEM,cAAM,GAAG,GAAG,CAAA;;;;GAIlB,AAJY,CAIX;AAnGO;IAAR,KAAK,EAAE;uCAAiB;AAEhB;IAAR,KAAK,EAAE;wCAAkB;AAEP;IAAlB,KAAK,CAAC,UAAU,CAAC;6CAAiC;AALxC,OAAO;IADnB,aAAa,CAAC,UAAU,CAAC;GACb,OAAO,CAqGnB","sourcesContent":["import { html, css, LitElement } from 'lit';\nimport { customElement, state, query } from 'lit/decorators.js';\nimport { RecaptchaWidgetInterface } 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 @query('#timeout') timeoutInput!: HTMLInputElement;\n\n private recaptchaManager: RecaptchaManagerInterface = new RecaptchaManager({\n defaultSiteKey: '6Ld64a8UAAAAAGbDwi1927ztGNw7YABQ-dqzvTN2',\n });\n\n private recaptcha1?: RecaptchaWidgetInterface;\n\n private recaptcha2?: RecaptchaWidgetInterface;\n\n render() {\n return html`\n <label for=\"timeout\">\n Timeout (ms):\n <input\n type=\"number\"\n id=\"timeout\"\n value=\"10000\"\n min=\"0\"\n max=\"99999\"\n step=\"100\"\n @change=${this.updateTimeout}\n />\n </label>\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 updateTimeout() {\n this.recaptchaManager.setTimeoutDelay(+this.timeoutInput.value);\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"]}
@@ -1,6 +1,10 @@
1
1
  import { LazyLoaderServiceInterface } from '@internetarchive/lazy-loader-service';
2
2
  import { RecaptchaWidgetInterface } from './recaptcha-widget';
3
3
  export interface RecaptchaManagerInterface {
4
+ /**
5
+ * Changes the manager's timeout delay to the given number of milliseconds.
6
+ */
7
+ setTimeoutDelay(delay: number): void;
4
8
  /**
5
9
  * Load a recaptcha widget for a given site key or the default site key.
6
10
  */
@@ -10,21 +14,30 @@ export interface RecaptchaManagerInterface {
10
14
  }): Promise<RecaptchaWidgetInterface>;
11
15
  }
12
16
  export declare class RecaptchaManager implements RecaptchaManagerInterface {
17
+ private static readonly DEFAULT_TIMEOUT;
13
18
  private lazyLoader;
14
19
  private defaultSiteKey?;
15
20
  private recaptchaCache;
21
+ /**
22
+ * How long in milliseconds to wait for the recaptcha library to load before timing out.
23
+ */
24
+ private timeout;
16
25
  constructor(options?: {
17
26
  defaultSiteKey?: string;
18
27
  lazyLoader?: LazyLoaderServiceInterface;
19
28
  grecaptchaLibrary?: ReCaptchaV2.ReCaptcha;
29
+ timeout?: number;
20
30
  });
21
31
  /** @inheritdoc */
32
+ setTimeoutDelay(delay: number): void;
33
+ /** @inheritdoc */
22
34
  getRecaptchaWidget(options?: {
23
35
  siteKey?: string;
24
36
  recaptchaParams?: ReCaptchaV2.Parameters;
25
37
  }): Promise<RecaptchaWidgetInterface>;
26
38
  /**
27
- * Load the Recaptch library from Google.
39
+ * Load the Recaptcha library from Google's www.recaptcha.net mirror, which
40
+ * is reachable in regions where www.google.com is blocked.
28
41
  *
29
42
  * @returns Promise<ReCaptchaV2.ReCaptcha>
30
43
  */
@@ -2,11 +2,20 @@ import { LazyLoaderService, } from '@internetarchive/lazy-loader-service';
2
2
  import { RecaptchaWidget } from './recaptcha-widget';
3
3
  export class RecaptchaManager {
4
4
  constructor(options) {
5
- var _a;
5
+ var _a, _b;
6
6
  this.recaptchaCache = {};
7
+ /**
8
+ * How long in milliseconds to wait for the recaptcha library to load before timing out.
9
+ */
10
+ this.timeout = RecaptchaManager.DEFAULT_TIMEOUT;
7
11
  this.defaultSiteKey = options === null || options === void 0 ? void 0 : options.defaultSiteKey;
8
12
  this.lazyLoader = (_a = options === null || options === void 0 ? void 0 : options.lazyLoader) !== null && _a !== void 0 ? _a : new LazyLoaderService();
9
13
  this.grecaptchaLibraryCache = options === null || options === void 0 ? void 0 : options.grecaptchaLibrary;
14
+ this.timeout = (_b = options === null || options === void 0 ? void 0 : options.timeout) !== null && _b !== void 0 ? _b : RecaptchaManager.DEFAULT_TIMEOUT;
15
+ }
16
+ /** @inheritdoc */
17
+ setTimeoutDelay(delay) {
18
+ this.timeout = delay;
10
19
  }
11
20
  /** @inheritdoc */
12
21
  async getRecaptchaWidget(options) {
@@ -27,7 +36,8 @@ export class RecaptchaManager {
27
36
  return recaptcha;
28
37
  }
29
38
  /**
30
- * Load the Recaptch library from Google.
39
+ * Load the Recaptcha library from Google's www.recaptcha.net mirror, which
40
+ * is reachable in regions where www.google.com is blocked.
31
41
  *
32
42
  * @returns Promise<ReCaptchaV2.ReCaptcha>
33
43
  */
@@ -35,7 +45,7 @@ export class RecaptchaManager {
35
45
  if (this.grecaptchaLibraryCache) {
36
46
  return this.grecaptchaLibraryCache;
37
47
  }
38
- return new Promise(resolve => {
48
+ return new Promise((resolve, reject) => {
39
49
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
50
  window.grecaptchaLoadedCallback = () => {
41
51
  setTimeout(() => {
@@ -46,10 +56,12 @@ export class RecaptchaManager {
46
56
  this.grecaptchaLibraryCache = window.grecaptcha;
47
57
  resolve(window.grecaptcha);
48
58
  };
59
+ setTimeout(() => reject(new Error('grecaptcha failed to execute callback')), this.timeout);
49
60
  this.lazyLoader.loadScript({
50
- src: 'https://www.google.com/recaptcha/api.js?onload=grecaptchaLoadedCallback&render=explicit',
61
+ src: 'https://www.recaptcha.net/recaptcha/api.js?onload=grecaptchaLoadedCallback&render=explicit',
51
62
  });
52
63
  });
53
64
  }
54
65
  }
66
+ RecaptchaManager.DEFAULT_TIMEOUT = 10000;
55
67
  //# sourceMappingURL=recaptcha-manager.js.map
@@ -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,EAA4B,MAAM,oBAAoB,CAAC;AAY/E,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,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9D,CAAC;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,CAAC;YAChC,OAAO,IAAI,CAAC,sBAAsB,CAAC;QACrC,CAAC;QACD,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;YAC3B,8DAA8D;YAC7D,MAAc,CAAC,wBAAwB,GAAG,GAAS,EAAE;gBACpD,UAAU,CAAC,GAAG,EAAE;oBACd,8CAA8C;oBAC9C,8DAA8D;oBAC9D,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, RecaptchaWidgetInterface } 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<RecaptchaWidgetInterface>;\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<RecaptchaWidgetInterface> {\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 // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (window as any).grecaptchaLoadedCallback = (): void => {\n setTimeout(() => {\n // remove the callback when we're done with it\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\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"]}
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,EAA4B,MAAM,oBAAoB,CAAC;AAiB/E,MAAM,OAAO,gBAAgB;IAc3B,YAAY,OAKX;;QAZO,mBAAc,GAAoC,EAAE,CAAC;QAE7D;;WAEG;QACK,YAAO,GAAG,gBAAgB,CAAC,eAAe,CAAC;QAQjD,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;QACzD,IAAI,CAAC,OAAO,GAAG,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,OAAO,mCAAI,gBAAgB,CAAC,eAAe,CAAC;IACtE,CAAC;IAED,kBAAkB;IAClB,eAAe,CAAC,KAAa;QAC3B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACvB,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,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9D,CAAC;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;;;;;OAKG;IACK,KAAK,CAAC,mBAAmB;QAC/B,IAAI,IAAI,CAAC,sBAAsB,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC,sBAAsB,CAAC;QACrC,CAAC;QACD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,8DAA8D;YAC7D,MAAc,CAAC,wBAAwB,GAAG,GAAS,EAAE;gBACpD,UAAU,CAAC,GAAG,EAAE;oBACd,8CAA8C;oBAC9C,8DAA8D;oBAC9D,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,UAAU,CACR,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC,EAChE,IAAI,CAAC,OAAO,CACb,CAAC;YAEF,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;gBACzB,GAAG,EAAE,4FAA4F;aAClG,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;;AAvFuB,gCAAe,GAAG,KAAK,AAAR,CAAS","sourcesContent":["import {\n LazyLoaderService,\n LazyLoaderServiceInterface,\n} from '@internetarchive/lazy-loader-service';\nimport { RecaptchaWidget, RecaptchaWidgetInterface } from './recaptcha-widget';\n\nexport interface RecaptchaManagerInterface {\n /**\n * Changes the manager's timeout delay to the given number of milliseconds.\n */\n setTimeoutDelay(delay: number): void;\n\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<RecaptchaWidgetInterface>;\n}\n\nexport class RecaptchaManager implements RecaptchaManagerInterface {\n private static readonly DEFAULT_TIMEOUT = 10000;\n\n private lazyLoader: LazyLoaderServiceInterface;\n\n private defaultSiteKey?: string;\n\n private recaptchaCache: Record<string, RecaptchaWidget> = {};\n\n /**\n * How long in milliseconds to wait for the recaptcha library to load before timing out.\n */\n private timeout = RecaptchaManager.DEFAULT_TIMEOUT;\n\n constructor(options?: {\n defaultSiteKey?: string;\n lazyLoader?: LazyLoaderServiceInterface;\n grecaptchaLibrary?: ReCaptchaV2.ReCaptcha; // allows dependency injection or will be lazy loaded\n timeout?: number;\n }) {\n this.defaultSiteKey = options?.defaultSiteKey;\n this.lazyLoader = options?.lazyLoader ?? new LazyLoaderService();\n this.grecaptchaLibraryCache = options?.grecaptchaLibrary;\n this.timeout = options?.timeout ?? RecaptchaManager.DEFAULT_TIMEOUT;\n }\n\n /** @inheritdoc */\n setTimeoutDelay(delay: number): void {\n this.timeout = delay;\n }\n\n /** @inheritdoc */\n async getRecaptchaWidget(options?: {\n siteKey?: string;\n recaptchaParams?: ReCaptchaV2.Parameters;\n }): Promise<RecaptchaWidgetInterface> {\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 Recaptcha library from Google's www.recaptcha.net mirror, which\n * is reachable in regions where www.google.com is blocked.\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, reject) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (window as any).grecaptchaLoadedCallback = (): void => {\n setTimeout(() => {\n // remove the callback when we're done with it\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n delete (window as any).grecaptchaLoadedCallback;\n }, 10);\n this.grecaptchaLibraryCache = window.grecaptcha;\n resolve(window.grecaptcha);\n };\n\n setTimeout(\n () => reject(new Error('grecaptcha failed to execute callback')),\n this.timeout,\n );\n\n this.lazyLoader.loadScript({\n src: 'https://www.recaptcha.net/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"]}
@@ -1,7 +1,9 @@
1
1
  import { BundleType, LazyLoaderServiceEvents, LazyLoaderServiceInterface } from '@internetarchive/lazy-loader-service';
2
2
  import { Unsubscribe } from 'nanoevents';
3
3
  export declare class MockLazyLoaderService implements LazyLoaderServiceInterface {
4
+ private onloadFn;
4
5
  loadScriptSrc?: string;
6
+ constructor(onloadFn?: () => unknown);
5
7
  on<E extends keyof LazyLoaderServiceEvents>(event: E, callback: LazyLoaderServiceEvents[E]): Unsubscribe;
6
8
  loadBundle(bundle: {
7
9
  module?: string;
@@ -1,5 +1,7 @@
1
- import { MockGrecaptcha } from './mock-grecaptcha';
2
1
  export class MockLazyLoaderService {
2
+ constructor(onloadFn = () => { }) {
3
+ this.onloadFn = onloadFn;
4
+ }
3
5
  on(event, callback) {
4
6
  throw new Error('Method not implemented.');
5
7
  }
@@ -8,11 +10,7 @@ export class MockLazyLoaderService {
8
10
  }
9
11
  async loadScript(options) {
10
12
  this.loadScriptSrc = options.src;
11
- window.grecaptcha = new MockGrecaptcha({
12
- mode: 'success',
13
- });
14
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
- window.grecaptchaLoadedCallback();
13
+ this.onloadFn();
16
14
  }
17
15
  }
18
16
  //# sourceMappingURL=mock-lazy-loader.js.map
@@ -1 +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;QACH,8DAA8D;QAC7D,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 // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (window as any).grecaptchaLoadedCallback();\n }\n}\n"]}
1
+ {"version":3,"file":"mock-lazy-loader.js","sourceRoot":"","sources":["../../test/mock-lazy-loader.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,qBAAqB;IAGhC,YAAoB,WAA0B,GAAG,EAAE,GAAE,CAAC;QAAlC,aAAQ,GAAR,QAAQ,CAA0B;IAAG,CAAC;IAE1D,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,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClB,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';\n\nexport class MockLazyLoaderService implements LazyLoaderServiceInterface {\n loadScriptSrc?: string;\n\n constructor(private onloadFn: () => unknown = () => {}) {}\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 this.onloadFn();\n }\n}\n"]}