@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.
- package/.claude/settings.local.json +7 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/.editorconfig +29 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/.github/workflows/ci.yml +27 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/.github/workflows/gh-pages-main.yml +39 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/.github/workflows/pr-preview.yml +63 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/.husky/pre-commit +1 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/.prettierignore +1 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/LICENSE +661 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/README.md +63 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/coverage/.gitignore +3 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/coverage/.placeholder +0 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/demo/app-root.ts +111 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/demo/index.html +21 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/eslint.config.mjs +53 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/index.ts +10 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/package-lock.json +11336 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/package.json +76 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/renovate.json +6 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/src/recaptcha-manager.ts +114 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/src/recaptcha-widget.ts +148 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/ssl/server.crt +22 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/ssl/server.key +28 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/test/mock-grecaptcha.ts +85 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/test/mock-lazy-loader.ts +36 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/test/recaptcha-manager.test.ts +201 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/tsconfig.json +31 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/vite.config.ts +25 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/web-dev-server.config.mjs +32 -0
- package/.claude/worktrees/recaptcha-google-url-hardcode-79a528/web-test-runner.config.mjs +41 -0
- package/.editorconfig +29 -29
- package/.github/workflows/ci.yml +27 -27
- package/.github/workflows/gh-pages-main.yml +39 -39
- package/.github/workflows/pr-preview.yml +63 -63
- package/.husky/pre-commit +1 -1
- package/.prettierignore +1 -1
- package/LICENSE +661 -661
- package/README.md +63 -63
- package/coverage/.gitignore +2 -2
- package/demo/app-root.ts +111 -111
- package/demo/index.html +21 -21
- package/dist/demo/app-root.js +31 -31
- package/dist/demo/app-root.js.map +1 -1
- package/dist/src/recaptcha-manager.d.ts +2 -1
- package/dist/src/recaptcha-manager.js +3 -2
- package/dist/src/recaptcha-manager.js.map +1 -1
- package/dist/test/mock-lazy-loader.js.map +1 -1
- package/dist/test/recaptcha-manager.test.js +1 -1
- package/dist/test/recaptcha-manager.test.js.map +1 -1
- package/eslint.config.mjs +53 -53
- package/package.json +76 -76
- package/renovate.json +6 -6
- package/src/recaptcha-manager.ts +114 -113
- package/ssl/server.key +28 -28
- package/test/mock-lazy-loader.ts +36 -36
- package/test/recaptcha-manager.test.ts +201 -199
- package/tsconfig.json +31 -31
- package/web-dev-server.config.mjs +32 -32
- 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,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
|
+
}
|