@internetarchive/recaptcha-manager 2.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 (58) 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/.editorconfig +29 -29
  31. package/.github/workflows/ci.yml +27 -27
  32. package/.github/workflows/gh-pages-main.yml +39 -39
  33. package/.github/workflows/pr-preview.yml +63 -63
  34. package/.husky/pre-commit +1 -1
  35. package/.prettierignore +1 -1
  36. package/LICENSE +661 -661
  37. package/README.md +63 -63
  38. package/coverage/.gitignore +2 -2
  39. package/demo/app-root.ts +111 -111
  40. package/demo/index.html +21 -21
  41. package/dist/demo/app-root.js +31 -31
  42. package/dist/demo/app-root.js.map +1 -1
  43. package/dist/src/recaptcha-manager.d.ts +2 -1
  44. package/dist/src/recaptcha-manager.js +3 -2
  45. package/dist/src/recaptcha-manager.js.map +1 -1
  46. package/dist/test/mock-lazy-loader.js.map +1 -1
  47. package/dist/test/recaptcha-manager.test.js +1 -1
  48. package/dist/test/recaptcha-manager.test.js.map +1 -1
  49. package/eslint.config.mjs +53 -53
  50. package/package.json +76 -76
  51. package/renovate.json +6 -6
  52. package/src/recaptcha-manager.ts +114 -113
  53. package/ssl/server.key +28 -28
  54. package/test/mock-lazy-loader.ts +36 -36
  55. package/test/recaptcha-manager.test.ts +201 -199
  56. package/tsconfig.json +31 -31
  57. package/web-dev-server.config.mjs +32 -32
  58. package/web-test-runner.config.mjs +41 -41
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@internetarchive/recaptcha-manager",
3
+ "description": "A library that lets you lazy load and interact with reCaptcha.",
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": "2.0.1",
11
+ "main": "dist/index.js",
12
+ "module": "dist/index.js",
13
+ "scripts": {
14
+ "start": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wds\"",
15
+ "prepare": "rimraf dist && tsc && husky install",
16
+ "build": "tsc",
17
+ "lint": "eslint . && prettier \"**/*.ts\" --check",
18
+ "format": "eslint . --fix && prettier \"**/*.ts\" --write",
19
+ "circular": "madge --circular --extensions ts .",
20
+ "test": "tsc && npm run lint && npm run circular && wtr --coverage",
21
+ "test:watch": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wtr --watch\"",
22
+ "ghpages:build": "rimraf ghpages && npm run prepare && vite build",
23
+ "ghpages:publish": "npm run ghpages:prepare -e $(git branch --show-current)",
24
+ "ghpages:prepare": "npm run ghpages:build && touch ghpages/.nojekyll && npm run ghpages:generate",
25
+ "ghpages:generate": "gh-pages -t -d ghpages -m \"Build for $(git log --pretty=format:\"%h %an %ai %s\" -n1) [skip ci]\""
26
+ },
27
+ "types": "dist/index.d.ts",
28
+ "devDependencies": {
29
+ "@open-wc/eslint-config": "^12.0.3",
30
+ "@open-wc/testing": "^4.0.0",
31
+ "@types/grecaptcha": "^2",
32
+ "@types/mocha": "^10.0.10",
33
+ "@typescript-eslint/eslint-plugin": "^8.19.1",
34
+ "@typescript-eslint/parser": "^8.19.1",
35
+ "@web/dev-server": "^0.4.6",
36
+ "@web/test-runner": "^0.20.0",
37
+ "concurrently": "^9.1.2",
38
+ "eslint": "^9.17.0",
39
+ "eslint-config-prettier": "^10.1.1",
40
+ "eslint-plugin-html": "^8.1.2",
41
+ "eslint-plugin-import": "^2.31.0",
42
+ "eslint-plugin-lit": "^1.15.0",
43
+ "eslint-plugin-lit-a11y": "^4.1.4",
44
+ "eslint-plugin-no-only-tests": "^3.3.0",
45
+ "eslint-plugin-wc": "^2.2.0",
46
+ "gh-pages": "^6.3.0",
47
+ "husky": "^9.1.7",
48
+ "madge": "^8.0.0",
49
+ "nanoevents": "^6.0.2",
50
+ "prettier": "^3.4.2",
51
+ "rimraf": "^6.0.1",
52
+ "sinon": "^19.0.2",
53
+ "ts-lit-plugin": "^2.0.2",
54
+ "tslib": "^2.8.1",
55
+ "typescript": "^5.7.2",
56
+ "vite": "^6.0.7"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public"
60
+ },
61
+ "prettier": {
62
+ "singleQuote": true,
63
+ "arrowParens": "avoid"
64
+ },
65
+ "lint-staged": {
66
+ "*.ts": [
67
+ "eslint --fix",
68
+ "prettier --write",
69
+ "git add"
70
+ ]
71
+ },
72
+ "dependencies": {
73
+ "lit": "^2.8.0",
74
+ "@internetarchive/lazy-loader-service": "^0.2.0"
75
+ }
76
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": [
3
+ "config:base",
4
+ ":preserveSemverRanges"
5
+ ]
6
+ }
@@ -0,0 +1,114 @@
1
+ import {
2
+ LazyLoaderService,
3
+ LazyLoaderServiceInterface,
4
+ } from '@internetarchive/lazy-loader-service';
5
+ import { RecaptchaWidget, RecaptchaWidgetInterface } from './recaptcha-widget';
6
+
7
+ export interface RecaptchaManagerInterface {
8
+ /**
9
+ * Changes the manager's timeout delay to the given number of milliseconds.
10
+ */
11
+ setTimeoutDelay(delay: number): void;
12
+
13
+ /**
14
+ * Load a recaptcha widget for a given site key or the default site key.
15
+ */
16
+ getRecaptchaWidget(options?: {
17
+ siteKey?: string;
18
+ recaptchaParams?: ReCaptchaV2.Parameters;
19
+ }): Promise<RecaptchaWidgetInterface>;
20
+ }
21
+
22
+ export class RecaptchaManager implements RecaptchaManagerInterface {
23
+ private static readonly DEFAULT_TIMEOUT = 10000;
24
+
25
+ private lazyLoader: LazyLoaderServiceInterface;
26
+
27
+ private defaultSiteKey?: string;
28
+
29
+ private recaptchaCache: Record<string, RecaptchaWidget> = {};
30
+
31
+ /**
32
+ * How long in milliseconds to wait for the recaptcha library to load before timing out.
33
+ */
34
+ private timeout = RecaptchaManager.DEFAULT_TIMEOUT;
35
+
36
+ constructor(options?: {
37
+ defaultSiteKey?: string;
38
+ lazyLoader?: LazyLoaderServiceInterface;
39
+ grecaptchaLibrary?: ReCaptchaV2.ReCaptcha; // allows dependency injection or will be lazy loaded
40
+ timeout?: number;
41
+ }) {
42
+ this.defaultSiteKey = options?.defaultSiteKey;
43
+ this.lazyLoader = options?.lazyLoader ?? new LazyLoaderService();
44
+ this.grecaptchaLibraryCache = options?.grecaptchaLibrary;
45
+ this.timeout = options?.timeout ?? RecaptchaManager.DEFAULT_TIMEOUT;
46
+ }
47
+
48
+ /** @inheritdoc */
49
+ setTimeoutDelay(delay: number): void {
50
+ this.timeout = delay;
51
+ }
52
+
53
+ /** @inheritdoc */
54
+ async getRecaptchaWidget(options?: {
55
+ siteKey?: string;
56
+ recaptchaParams?: ReCaptchaV2.Parameters;
57
+ }): Promise<RecaptchaWidgetInterface> {
58
+ const key = options?.siteKey ?? this.defaultSiteKey;
59
+ if (!key) {
60
+ throw new Error('The reCaptcha widget requires a site key');
61
+ }
62
+
63
+ const cached = this.recaptchaCache[key];
64
+ if (cached) return cached;
65
+
66
+ const grecaptchaLibrary = await this.getRecaptchaLibrary();
67
+ const recaptcha = new RecaptchaWidget(
68
+ {
69
+ siteKey: key,
70
+ grecaptchaLibrary,
71
+ },
72
+ options?.recaptchaParams,
73
+ );
74
+
75
+ this.recaptchaCache[key] = recaptcha;
76
+ return recaptcha;
77
+ }
78
+
79
+ /**
80
+ * Load the Recaptcha library from Google's www.recaptcha.net mirror, which
81
+ * is reachable in regions where www.google.com is blocked.
82
+ *
83
+ * @returns Promise<ReCaptchaV2.ReCaptcha>
84
+ */
85
+ private async getRecaptchaLibrary(): Promise<ReCaptchaV2.ReCaptcha> {
86
+ if (this.grecaptchaLibraryCache) {
87
+ return this.grecaptchaLibraryCache;
88
+ }
89
+ return new Promise((resolve, reject) => {
90
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
+ (window as any).grecaptchaLoadedCallback = (): void => {
92
+ setTimeout(() => {
93
+ // remove the callback when we're done with it
94
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
+ delete (window as any).grecaptchaLoadedCallback;
96
+ }, 10);
97
+ this.grecaptchaLibraryCache = window.grecaptcha;
98
+ resolve(window.grecaptcha);
99
+ };
100
+
101
+ setTimeout(
102
+ () => reject(new Error('grecaptcha failed to execute callback')),
103
+ this.timeout,
104
+ );
105
+
106
+ this.lazyLoader.loadScript({
107
+ src: 'https://www.recaptcha.net/recaptcha/api.js?onload=grecaptchaLoadedCallback&render=explicit',
108
+ });
109
+ });
110
+ }
111
+
112
+ /** don't use directly, use `getRecaptchaLibrary()` */
113
+ private grecaptchaLibraryCache?: ReCaptchaV2.ReCaptcha;
114
+ }
@@ -0,0 +1,148 @@
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
+ this.isExecuting = true;
68
+
69
+ return new Promise((resolve, reject) => {
70
+ this.executionSuccessBlock = (token: string): void => {
71
+ this.finishExecution();
72
+ resolve(token);
73
+ };
74
+
75
+ this.executionExpiredBlock = (): void => {
76
+ this.finishExecution();
77
+ reject(new Error('expired'));
78
+ };
79
+
80
+ this.executionErrorBlock = (): void => {
81
+ this.finishExecution();
82
+ reject(new Error('error'));
83
+ };
84
+
85
+ this.grecaptchaLibrary.execute(widgetId);
86
+ });
87
+ }
88
+
89
+ private finishExecution() {
90
+ this.isExecuting = false;
91
+ const { widgetId } = this;
92
+ if (widgetId === null) return;
93
+ this.grecaptchaLibrary.reset(widgetId);
94
+ }
95
+
96
+ private setup(
97
+ container: HTMLElement,
98
+ widgetConfig?: ReCaptchaV2.Parameters,
99
+ ): void {
100
+ this.widgetId = this.grecaptchaLibrary.render(container, {
101
+ callback: this.responseHandler.bind(this),
102
+ 'expired-callback': this.expiredHandler.bind(this),
103
+ 'error-callback': this.errorHandler.bind(this),
104
+ sitekey: this.siteKey,
105
+ tabindex: widgetConfig?.tabindex,
106
+ theme: widgetConfig?.theme,
107
+ type: widgetConfig?.type,
108
+ size: widgetConfig?.size ?? 'invisible',
109
+ badge: widgetConfig?.badge,
110
+ });
111
+ }
112
+
113
+ private createContainer(zIndex?: number): HTMLElement {
114
+ const elementId = `recaptchaManager-${this.siteKey}`;
115
+ let element: HTMLElement | null = document.getElementById(elementId);
116
+ if (!element) {
117
+ element = document.createElement('div');
118
+ element.id = elementId;
119
+ element.style.position = 'fixed';
120
+ element.style.top = '50%';
121
+ element.style.left = '50%';
122
+ element.style.zIndex = zIndex ? `${zIndex}` : '10';
123
+ document.body.appendChild(element);
124
+ }
125
+ return element;
126
+ }
127
+
128
+ private responseHandler(response: string): void {
129
+ if (this.executionSuccessBlock) {
130
+ this.executionSuccessBlock(response);
131
+ this.executionSuccessBlock = undefined;
132
+ }
133
+ }
134
+
135
+ private expiredHandler(): void {
136
+ if (this.executionExpiredBlock) {
137
+ this.executionExpiredBlock();
138
+ this.executionExpiredBlock = undefined;
139
+ }
140
+ }
141
+
142
+ private errorHandler(): void {
143
+ if (this.executionErrorBlock) {
144
+ this.executionErrorBlock();
145
+ this.executionErrorBlock = undefined;
146
+ }
147
+ }
148
+ }
@@ -0,0 +1,22 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDszCCApugAwIBAgIUYr5csS2ntaMTww0b5QoFI0xlOpUwDQYJKoZIhvcNAQEL
3
+ BQAwaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh
4
+ bmNpc2NvMRkwFwYDVQQKDBBJbnRlcm5ldCBBcmNoaXZlMRowGAYDVQQDDBFsb2Nh
5
+ bC5hcmNoaXZlLm9yZzAeFw0yNTAzMDcxOTM1MzRaFw0yNTA0MDYxOTM1MzRaMGkx
6
+ CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj
7
+ bzEZMBcGA1UECgwQSW50ZXJuZXQgQXJjaGl2ZTEaMBgGA1UEAwwRbG9jYWwuYXJj
8
+ aGl2ZS5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZqbS9hNS3
9
+ 8AtcA7Uszq7EA7RavuHc8AcpbURa6Y7NORs0SmfDD0oLptQ7DX5pSuHyOeDHzQ3U
10
+ XRCUDAhAD1XlaYQ6PPdiJPRAe4ACELzgDzepxVz3pdKPcfXd5/mHV3ghUZya2XW5
11
+ DSF1CRYUX2j10a+JORJIG4bhvTg4HxTJZZDuaNfBMC6ZSn7d9YLQoAf1MLLJm43Y
12
+ cj1WlOnyG4DHxNEqEk260BlbBywJH+SJA6GA0GR2bnzYZaDnZ0wqea0Zamx8ijpd
13
+ JxBUxYOqLfzs2KREVkkGOmsFEu82W6G3D4bKsY1UNaWXb/jcl3UkXIJJij6CdbbM
14
+ QkUJc8dow3sVAgMBAAGjUzBRMB0GA1UdDgQWBBSla0L0Yhpx9/DEV4uJ6OoHH92Q
15
+ bTAfBgNVHSMEGDAWgBSla0L0Yhpx9/DEV4uJ6OoHH92QbTAPBgNVHRMBAf8EBTAD
16
+ AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBEzt9sgWtwjLI7AVrejDSE6F/01ng4bTni
17
+ 0PJXa7IpwjhAApVWcjxDKp8SkDUAcZNULqBGvpPicqjnKf+wxk3xTMc7pvLqzOor
18
+ pm+plDUBfCEbrv1ouLDMgOtxQxbwbBUPySsLoNmDUGAGnL2zENX7qidOOmmSQEN4
19
+ jZuPtiEkgTqmtkfHaRX/7jhmGVfxMxj0Krlar1/cSbvwaVs4kRSjiL9/avT+AX59
20
+ 0QoNYTLGcBP0RKia8NF2A3cgUivIMFZQDvdsiAvqY+DbC7Mnyv4Sj/ZHHwVa9+8k
21
+ iRU56DrOt7sgaa/oQfzWnvRtL0F5eXC3veqM4qZrXs9hHeGlgPFo
22
+ -----END CERTIFICATE-----
@@ -0,0 +1,28 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDZqbS9hNS38Atc
3
+ A7Uszq7EA7RavuHc8AcpbURa6Y7NORs0SmfDD0oLptQ7DX5pSuHyOeDHzQ3UXRCU
4
+ DAhAD1XlaYQ6PPdiJPRAe4ACELzgDzepxVz3pdKPcfXd5/mHV3ghUZya2XW5DSF1
5
+ CRYUX2j10a+JORJIG4bhvTg4HxTJZZDuaNfBMC6ZSn7d9YLQoAf1MLLJm43Ycj1W
6
+ lOnyG4DHxNEqEk260BlbBywJH+SJA6GA0GR2bnzYZaDnZ0wqea0Zamx8ijpdJxBU
7
+ xYOqLfzs2KREVkkGOmsFEu82W6G3D4bKsY1UNaWXb/jcl3UkXIJJij6CdbbMQkUJ
8
+ c8dow3sVAgMBAAECggEANgLpITAhcuVDhFk9L3m4H1bF/dCpDmCXfl2pXR/guicm
9
+ C4M9HUeheaOzvVWbXThiOe/HyfylpmFTmFEmCPNlPrDAyYzQXE/MNmYO/TQ3Eihk
10
+ iSG68I764XKHbsG+Byoa2rW8NSaqEjniZ/7Rtkt4qasXMmdxlGgUP9bq6O45g8HV
11
+ kHxneFlA2KbrvmWnBi7NTea9+tp61NWiq5n97UgHacQR6KkYIpbxd7uNSnCSdmXt
12
+ pcwzO4ZPabJ2/DKRjePhU5OggPh+9AhJ3jsBo99GwYPgSZDh8E3vh2OWOtLBMUH3
13
+ rmAYwlRT2aZ5hy5yi+4QD98WoUQtsr+9n757F8V6MQKBgQDzZycdgaKwWd5d34NN
14
+ 0TkFtPQPwxxJTCCs3I3q02uWcS3svQzBK0cWb4nISO04TnJ3MnXo83dgGhCMF+uZ
15
+ FCXxAA53z8F92iZo+ELlXFDFwNeNYih/afDA42tWZ6TlVsDnZ4zQgRjHS7jEF/JV
16
+ 0ZgwGpw5725JQt64dic8wTOcDQKBgQDk7YYACQcWTnmjDhZQ3PZSX4fcTtzPZKZj
17
+ fa1GrXEaUH1hSyc9VmY6qJpUmXexpvtr194nXE+O5wbThOHcBjVlo2Qv+vswmUX3
18
+ WEcVzTVN4/fODCLCqFcMNIr+BzwZfwpT+p0u8g9FxDy1gGmvkxwIu8DCpnUT12Xj
19
+ hm2wO+UxKQKBgFxCSDBF9+2SUtgQJYv0dwGzwiLLWMhro6MCAoT02D3w7nBihBgg
20
+ GFTnuDkDc285ROfrZ4gB6MizeHwxgOrIGU2NMO62/+d9LbvyBiE76Z3bZ5i+kQ0i
21
+ kc/7I69fn8ASLxJHTLenh0XbbNBfJ0riJCZvn7HSEGKShysyFdNQhAhtAoGAFYVi
22
+ 0IQIv4cXFkYPwQBUw7+pVQOw7GpI3heFf5x0goXIk6nuAW0q5R7Oi192CiRphGTh
23
+ xI+ABy4ezSmz1exbfreShpQwowv1sOACpsEI3s6skBlB90y+Ci6yVlk1xCvWO7jW
24
+ qAAngWaGUoXE6bWJsCR+ZY4ieYAJWw9bJnMrA6kCgYAzV4Xeoh5ofENZM21wKW3W
25
+ iCzRibPObv6Vb/j9A9yT57yzI3BdfvsX5zmuSvOJm1DimgGY9nCJUzUEYG0a1Dhh
26
+ /rqObPoVIFGesmvwflVYFktmVHk7ycEDVreSTz23cvmraPz1fnpdKeuEs4sRQJV7
27
+ iJhLoxX5SJlJc0RXMhgHGQ==
28
+ -----END PRIVATE KEY-----
@@ -0,0 +1,85 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+
3
+ export type MockGrecaptchaMode = 'success' | 'expired' | 'error';
4
+
5
+ export class MockGrecaptcha implements ReCaptchaV2.ReCaptcha {
6
+ response = 'foo';
7
+
8
+ renderCalled = false;
9
+
10
+ executeCalled = false;
11
+
12
+ resetCallCount = 0;
13
+
14
+ getResponseCalled = false;
15
+
16
+ mode: MockGrecaptchaMode;
17
+
18
+ addDelay: boolean;
19
+
20
+ callback?: (response: string) => void;
21
+
22
+ 'expired-callback'?: () => void;
23
+
24
+ 'error-callback'?: () => void;
25
+
26
+ render(
27
+ container: string | HTMLElement,
28
+ parameters?: ReCaptchaV2.Parameters | undefined,
29
+ inherit?: boolean | undefined,
30
+ ): number {
31
+ this.callback = parameters?.callback?.bind(this);
32
+ this['error-callback'] = parameters?.['error-callback']?.bind(this);
33
+ this['expired-callback'] = parameters?.['expired-callback']?.bind(this);
34
+ this.renderCalled = true;
35
+ return 1;
36
+ }
37
+
38
+ reset(opt_widget_id?: number | undefined): void {
39
+ this.resetCallCount += 1;
40
+ }
41
+
42
+ getResponse(opt_widget_id?: number | undefined): string {
43
+ this.getResponseCalled = true;
44
+ return 'foo';
45
+ }
46
+
47
+ execute(opt_widget_id?: number): void {
48
+ this.executeCalled = true;
49
+
50
+ if (this.addDelay) {
51
+ setTimeout(this.callCallback.bind(this), 100);
52
+ } else {
53
+ this.callCallback();
54
+ }
55
+ }
56
+
57
+ ready(callback: () => void): void {}
58
+
59
+ private callCallback(): void {
60
+ switch (this.mode) {
61
+ case 'success':
62
+ if (this.callback) {
63
+ this.callback('foo');
64
+ }
65
+ break;
66
+ case 'error':
67
+ if (this['error-callback']) {
68
+ this['error-callback']();
69
+ }
70
+ break;
71
+ case 'expired':
72
+ if (this['expired-callback']) {
73
+ this['expired-callback']();
74
+ }
75
+ break;
76
+ default:
77
+ break;
78
+ }
79
+ }
80
+
81
+ constructor(options: { mode: MockGrecaptchaMode; addDelay?: boolean }) {
82
+ this.mode = options.mode;
83
+ this.addDelay = options.addDelay ?? false;
84
+ }
85
+ }
@@ -0,0 +1,36 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import {
3
+ BundleType,
4
+ LazyLoaderServiceEvents,
5
+ LazyLoaderServiceInterface,
6
+ } from '@internetarchive/lazy-loader-service';
7
+ import { Unsubscribe } from 'nanoevents';
8
+
9
+ export class MockLazyLoaderService implements LazyLoaderServiceInterface {
10
+ loadScriptSrc?: string;
11
+
12
+ constructor(private onloadFn: () => unknown = () => {}) {}
13
+
14
+ on<E extends keyof LazyLoaderServiceEvents>(
15
+ event: E,
16
+ callback: LazyLoaderServiceEvents[E],
17
+ ): Unsubscribe {
18
+ throw new Error('Method not implemented.');
19
+ }
20
+
21
+ async loadBundle(bundle: {
22
+ module?: string;
23
+ nomodule?: string;
24
+ }): Promise<void> {
25
+ console.debug('loadBundle', bundle);
26
+ }
27
+
28
+ async loadScript(options: {
29
+ src: string;
30
+ bundleType?: BundleType;
31
+ attributes?: Record<string, string>;
32
+ }): Promise<void> {
33
+ this.loadScriptSrc = options.src;
34
+ this.onloadFn();
35
+ }
36
+ }