Add support for OAuth login to GitHub via widget (including adding new installations) (#661)

* WIP

* Update vite

* Add oauth landing page

* Add API support for GitHub oauthing

* Remove console.logs

* Add support for logging and and out of GitHub

* Add bridge API methods

* Add base link styling

* Sugar syntax main get

* Update vite

* changelog

* Review changes

* Use instance to match UI

* lint

---------

Co-authored-by: Justin Carlson <justinc@element.io>
This commit is contained in:
Will Hunt 2023-03-14 10:50:46 +00:00 committed by GitHub
parent d602c895f3
commit 55529d7128
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 766 additions and 265 deletions

1
changelog.d/661.bugfix Normal file
View File

@ -0,0 +1 @@
Add support for logging into GitHub via OAuth from bridge widgets.

View File

@ -104,7 +104,7 @@
"sass": "^1.51.0", "sass": "^1.51.0",
"ts-node": "^10.4.0", "ts-node": "^10.4.0",
"typescript": "^4.5.2", "typescript": "^4.5.2",
"vite": "^2.9.13", "vite": "^4.1.4",
"vite-svg-loader": "^3.4.0" "vite-svg-loader": "^4.0.0"
} }
} }

View File

@ -742,6 +742,8 @@ export class Bridge {
this.connectionManager, this.connectionManager,
this.botUsersManager, this.botUsersManager,
this.as, this.as,
this.tokenStore,
this.github,
); );
} }

View File

@ -8,7 +8,7 @@ import { GitHubRepoConnectionOptions } from "../Connections/GithubRepo";
import { BridgeConfigActorPermission, BridgePermissions } from "../libRs"; import { BridgeConfigActorPermission, BridgePermissions } from "../libRs";
import { ConfigError } from "../errors"; import { ConfigError } from "../errors";
import { ApiError, ErrCode } from "../api"; import { ApiError, ErrCode } from "../api";
import { GITHUB_CLOUD_URL } from "../Github/GithubInstance"; import { GithubInstance, GITHUB_CLOUD_URL } from "../Github/GithubInstance";
import { Logger } from "matrix-appservice-bridge"; import { Logger } from "matrix-appservice-bridge";
const log = new Logger("Config"); const log = new Logger("Config");
@ -95,10 +95,10 @@ export class BridgeConfigGitHub {
this.baseUrl = yaml.enterpriseUrl ? new URL(yaml.enterpriseUrl) : GITHUB_CLOUD_URL; this.baseUrl = yaml.enterpriseUrl ? new URL(yaml.enterpriseUrl) : GITHUB_CLOUD_URL;
} }
@hideKey() public publicConfig(githubInstance?: GithubInstance) {
public get publicConfig() {
return { return {
userIdPrefix: this.userIdPrefix, userIdPrefix: this.userIdPrefix,
newInstallationUrl: githubInstance?.newInstallationUrl?.toString(),
} }
} }
} }
@ -705,7 +705,7 @@ For more details, see https://github.com/matrix-org/matrix-hookshot/issues/594.
config = this.generic?.publicConfig; config = this.generic?.publicConfig;
break; break;
case "github": case "github":
config = this.github?.publicConfig; config = this.github?.publicConfig();
break; break;
case "gitlab": case "gitlab":
config = this.gitlab?.publicConfig; config = this.gitlab?.publicConfig;

View File

@ -3,7 +3,6 @@ import { BotCommands, botCommand, compileBotCommands, HelpFunction } from "../Bo
import { CommandConnection } from "./CommandConnection"; import { CommandConnection } from "./CommandConnection";
import { GenericHookConnection, GitHubRepoConnection, JiraProjectConnection, JiraProjectConnectionState } from "."; import { GenericHookConnection, GitHubRepoConnection, JiraProjectConnection, JiraProjectConnectionState } from ".";
import { CommandError } from "../errors"; import { CommandError } from "../errors";
import { randomUUID } from 'node:crypto';
import { BridgePermissionLevel } from "../Config/Config"; import { BridgePermissionLevel } from "../Config/Config";
import markdown from "markdown-it"; import markdown from "markdown-it";
import { FigmaFileConnection } from "./FigmaFileConnection"; import { FigmaFileConnection } from "./FigmaFileConnection";

View File

@ -10,6 +10,7 @@ import UserAgent from "../UserAgent";
const log = new Logger("GithubInstance"); const log = new Logger("GithubInstance");
export const GITHUB_CLOUD_URL = new URL("https://api.github.com"); export const GITHUB_CLOUD_URL = new URL("https://api.github.com");
export const GITHUB_CLOUD_PUBLIC_URL = new URL("https://github.com");
export class GitHubOAuthError extends Error { export class GitHubOAuthError extends Error {
constructor(errorResponse: GitHubOAuthErrorResponse) { constructor(errorResponse: GitHubOAuthErrorResponse) {
@ -182,7 +183,7 @@ export class GithubInstance {
public get newInstallationUrl() { public get newInstallationUrl() {
if (this.baseUrl.hostname === GITHUB_CLOUD_URL.hostname) { if (this.baseUrl.hostname === GITHUB_CLOUD_URL.hostname) {
// Cloud // Cloud
return new URL(`/apps/${this.appSlug}/installations/new`, this.baseUrl); return new URL(`/apps/${this.appSlug}/installations/new`, GITHUB_CLOUD_PUBLIC_URL);
} }
// Enterprise (yes, i know right) // Enterprise (yes, i know right)
return new URL(`/github-apps/${this.appSlug}/installations/new`, this.baseUrl); return new URL(`/github-apps/${this.appSlug}/installations/new`, this.baseUrl);
@ -192,7 +193,7 @@ export class GithubInstance {
const q = new URLSearchParams(params as Record<string, string>); const q = new URLSearchParams(params as Record<string, string>);
if (baseUrl.hostname === GITHUB_CLOUD_URL.hostname) { if (baseUrl.hostname === GITHUB_CLOUD_URL.hostname) {
// Cloud doesn't use `api.` for oauth. // Cloud doesn't use `api.` for oauth.
baseUrl = new URL("https://github.com"); baseUrl = GITHUB_CLOUD_PUBLIC_URL;
} }
const rawUrl = baseUrl.toString(); const rawUrl = baseUrl.toString();
return rawUrl + `${rawUrl.endsWith('/') ? '' : '/'}` + `login/oauth/${action}?${q}`; return rawUrl + `${rawUrl.endsWith('/') ? '' : '/'}` + `login/oauth/${action}?${q}`;

View File

@ -1,4 +1,4 @@
import { Appservice, IAppserviceRegistration, Intent } from "matrix-bot-sdk"; import { Appservice, Intent } from "matrix-bot-sdk";
import { Logger } from "matrix-appservice-bridge"; import { Logger } from "matrix-appservice-bridge";
import { BridgeConfig } from "../Config/Config"; import { BridgeConfig } from "../Config/Config";

View File

@ -26,8 +26,8 @@ const LEGACY_ACCOUNT_DATA_TYPE = "uk.half-shot.matrix-github.password-store:";
const LEGACY_ACCOUNT_DATA_GITLAB_TYPE = "uk.half-shot.matrix-github.gitlab.password-store:"; const LEGACY_ACCOUNT_DATA_GITLAB_TYPE = "uk.half-shot.matrix-github.gitlab.password-store:";
const log = new Logger("UserTokenStore"); const log = new Logger("UserTokenStore");
type TokenType = "github"|"gitlab"|"jira"; export type TokenType = "github"|"gitlab"|"jira";
const AllowedTokenTypes = ["github", "gitlab", "jira"]; export const AllowedTokenTypes = ["github", "gitlab", "jira"];
interface StoredTokenData { interface StoredTokenData {
encrypted: string|string[]; encrypted: string|string[];
@ -105,6 +105,9 @@ export class UserTokenStore extends TypedEmitter<Emitter> {
} }
public async clearUserToken(type: TokenType, userId: string, instanceUrl?: string): Promise<boolean> { public async clearUserToken(type: TokenType, userId: string, instanceUrl?: string): Promise<boolean> {
if (!AllowedTokenTypes.includes(type)) {
throw Error('Unknown token type');
}
const key = tokenKey(type, userId, false, instanceUrl); const key = tokenKey(type, userId, false, instanceUrl);
const obj = await this.intent.underlyingClient.getSafeAccountData<StoredTokenData|DeletedTokenData>(key); const obj = await this.intent.underlyingClient.getSafeAccountData<StoredTokenData|DeletedTokenData>(key);
if (!obj || "deleted" in obj) { if (!obj || "deleted" in obj) {

View File

@ -1,8 +1,9 @@
/* eslint-disable camelcase */
import { BridgeConfig } from "./Config/Config"; import { BridgeConfig } from "./Config/Config";
import { Router, default as express, Request, Response } from "express"; import { Router, default as express, Request, Response } from "express";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { MessageQueue, createMessageQueue } from "./MessageQueue"; import { MessageQueue, createMessageQueue } from "./MessageQueue";
import { Logger } from "matrix-appservice-bridge"; import { ApiError, ErrCode, Logger } from "matrix-appservice-bridge";
import qs from "querystring"; import qs from "querystring";
import axios from "axios"; import axios from "axios";
import { IGitLabWebhookEvent, IGitLabWebhookIssueStateEvent, IGitLabWebhookMREvent, IGitLabWebhookReleaseEvent } from "./Gitlab/WebhookTypes"; import { IGitLabWebhookEvent, IGitLabWebhookIssueStateEvent, IGitLabWebhookMREvent, IGitLabWebhookReleaseEvent } from "./Gitlab/WebhookTypes";
@ -185,30 +186,37 @@ export class Webhooks extends EventEmitter {
} }
} }
public async onGitHubGetOauth(req: Request<unknown, unknown, unknown, {error?: string, error_description?: string, code?: string, state?: string}> , res: Response) { public async onGitHubGetOauth(req: Request<unknown, unknown, unknown, {error?: string, error_description?: string, code?: string, state?: string, setup_action?: 'install'}> , res: Response) {
log.info(`Got new oauth request`, { state: req.query.state }); const oauthUrl = this.config.widgets && new URL("oauth.html", this.config.widgets.parsedPublicUrl);
if (oauthUrl) {
oauthUrl.searchParams.set('service', 'github');
oauthUrl?.searchParams.set('oauth-kind', 'account');
}
const { setup_action, state } = req.query;
log.info("Got new oauth request", { state, setup_action });
try { try {
if (!this.config.github || !this.config.github.oauth) { if (!this.config.github || !this.config.github.oauth) {
return res.status(500).send(`<p>Bridge is not configured with OAuth support</p>`); throw new ApiError('Bridge is not configured with OAuth support', ErrCode.DisabledFeature);
} }
if (req.query.error) { if (req.query.error) {
return res.status(500).send(`<p><b>GitHub Error</b>: ${req.query.error} ${req.query.error_description}</p>`); throw new ApiError(`GitHub Error: ${req.query.error} ${req.query.error_description}`, ErrCode.Unknown);
} }
if (!req.query.state) { if (setup_action !== 'install') {
return res.status(400).send(`<p>Missing state</p>`); if (!state) {
throw new ApiError(`Missing state`, ErrCode.BadValue);
} }
if (!req.query.code) { if (!req.query.code) {
return res.status(400).send(`<p>Missing code</p>`); throw new ApiError(`Missing code`, ErrCode.BadValue);
} }
const exists = await this.queue.pushWait<OAuthRequest, boolean>({ const exists = await this.queue.pushWait<OAuthRequest, boolean>({
eventName: "github.oauth.response", eventName: "github.oauth.response",
sender: "GithubWebhooks", sender: "GithubWebhooks",
data: { data: {
state: req.query.state, state,
}, },
}); });
if (!exists) { if (!exists) {
return res.status(404).send(`<p>Could not find user which authorised this request. Has it timed out?</p>`); throw new ApiError(`Could not find user which authorised this request. Has it timed out?`, undefined, 404);
} }
const accessTokenUrl = GithubInstance.generateOAuthUrl(this.config.github.baseUrl, "access_token", { const accessTokenUrl = GithubInstance.generateOAuthUrl(this.config.github.baseUrl, "access_token", {
client_id: this.config.github.oauth.client_id, client_id: this.config.github.oauth.client_id,
@ -220,17 +228,35 @@ export class Webhooks extends EventEmitter {
const accessTokenRes = await axios.post(accessTokenUrl); const accessTokenRes = await axios.post(accessTokenUrl);
const result = qs.parse(accessTokenRes.data) as GitHubOAuthTokenResponse|{error: string, error_description: string, error_uri: string}; const result = qs.parse(accessTokenRes.data) as GitHubOAuthTokenResponse|{error: string, error_description: string, error_uri: string};
if ("error" in result) { if ("error" in result) {
return res.status(500).send(`<p><b>GitHub Error</b>: ${result.error} ${result.error_description}</p>`); throw new ApiError(`GitHub Error: ${result.error} ${result.error_description}`, ErrCode.Unknown);
} }
await this.queue.push<GitHubOAuthTokenResponse>({ await this.queue.push<GitHubOAuthTokenResponse>({
eventName: "github.oauth.tokens", eventName: "github.oauth.tokens",
sender: "GithubWebhooks", sender: "GithubWebhooks",
data: { ...result, state: req.query.state as string }, data: { ...result, state: req.query.state as string },
}); });
return res.send(`<p> Your account has been bridged </p>`); } else if (oauthUrl) {
// App install.
oauthUrl.searchParams.set('oauth-kind', 'organisation');
}
} catch (ex) { } catch (ex) {
if (ex instanceof ApiError) {
if (oauthUrl) {
oauthUrl?.searchParams.set('error', ex.error);
oauthUrl?.searchParams.set('errcode', ex.errcode);
} else {
return res.status(ex.statusCode).send(ex.message);
}
} else {
log.error("Failed to handle oauth request:", ex); log.error("Failed to handle oauth request:", ex);
return res.status(500).send(`<p>Encountered an error handing oauth request</p>`); return res.status(500).send('Failed to handle oauth request');
}
}
if (oauthUrl) {
// If we're serving widgets, do something prettier.
return res.redirect(oauthUrl.toString());
} else {
return res.send(`<p> Your account has been bridged </p>`);
} }
} }

View File

@ -3,13 +3,15 @@ import { AdminRoom } from "../AdminRoom";
import { Logger } from "matrix-appservice-bridge"; import { Logger } from "matrix-appservice-bridge";
import { ApiError, ErrCode } from "../api"; import { ApiError, ErrCode } from "../api";
import { BridgeConfig } from "../Config/Config"; import { BridgeConfig } from "../Config/Config";
import { GetConnectionsForServiceResponse } from "./BridgeWidgetInterface"; import { GetAuthPollResponse, GetAuthResponse, GetConnectionsForServiceResponse } from "./BridgeWidgetInterface";
import { ProvisioningApi, ProvisioningRequest } from "matrix-appservice-bridge"; import { ProvisioningApi, ProvisioningRequest } from "matrix-appservice-bridge";
import { IBridgeStorageProvider } from "../Stores/StorageProvider"; import { IBridgeStorageProvider } from "../Stores/StorageProvider";
import { ConnectionManager } from "../ConnectionManager"; import { ConnectionManager } from "../ConnectionManager";
import BotUsersManager, {BotUser} from "../Managers/BotUsersManager"; import BotUsersManager, {BotUser} from "../Managers/BotUsersManager";
import { assertUserPermissionsInRoom, GetConnectionsResponseItem } from "../provisioning/api"; import { assertUserPermissionsInRoom, GetConnectionsResponseItem } from "../provisioning/api";
import { Appservice, PowerLevelsEvent } from "matrix-bot-sdk"; import { Appservice, PowerLevelsEvent } from "matrix-bot-sdk";
import { GithubInstance } from '../Github/GithubInstance';
import { AllowedTokenTypes, TokenType, UserTokenStore } from '../UserTokenStore';
const log = new Logger("BridgeWidgetApi"); const log = new Logger("BridgeWidgetApi");
@ -23,6 +25,8 @@ export class BridgeWidgetApi {
private readonly connMan: ConnectionManager, private readonly connMan: ConnectionManager,
private readonly botUsersManager: BotUsersManager, private readonly botUsersManager: BotUsersManager,
private readonly as: Appservice, private readonly as: Appservice,
private readonly tokenStore: UserTokenStore,
private readonly github?: GithubInstance,
) { ) {
this.api = new ProvisioningApi( this.api = new ProvisioningApi(
storageProvider, storageProvider,
@ -54,6 +58,9 @@ export class BridgeWidgetApi {
this.api.addRoute("patch", '/v1/:roomId/connections/:connectionId', wrapHandler(this.updateConnection)); this.api.addRoute("patch", '/v1/:roomId/connections/:connectionId', wrapHandler(this.updateConnection));
this.api.addRoute("delete", '/v1/:roomId/connections/:connectionId', wrapHandler(this.deleteConnection)); this.api.addRoute("delete", '/v1/:roomId/connections/:connectionId', wrapHandler(this.deleteConnection));
this.api.addRoute("get", '/v1/targets/:type', wrapHandler(this.getConnectionTargets)); this.api.addRoute("get", '/v1/targets/:type', wrapHandler(this.getConnectionTargets));
this.api.addRoute('get', '/v1/service/:service/auth', wrapHandler(this.getAuth));
this.api.addRoute('get', '/v1/service/:service/auth/:state', wrapHandler(this.getAuthPoll));
this.api.addRoute('post', '/v1/service/:service/auth/logout', wrapHandler(this.postAuthLogout));
} }
private getBotUserInRoom(roomId: string, serviceType: string): BotUser { private getBotUserInRoom(roomId: string, serviceType: string): BotUser {
@ -95,8 +102,13 @@ export class BridgeWidgetApi {
} }
private async getServiceConfig(req: ProvisioningRequest, res: Response<Record<string, unknown>>) { private async getServiceConfig(req: ProvisioningRequest, res: Response<Record<string, unknown>>) {
// GitHub is a special case because it depends on live config.
if (req.params.service === 'github') {
res.send(this.config.github?.publicConfig(this.github));
} else {
res.send(this.config.getPublicConfigForService(req.params.service)); res.send(this.config.getPublicConfigForService(req.params.service));
} }
}
private async getConnectionsForRequest(req: ProvisioningRequest) { private async getConnectionsForRequest(req: ProvisioningRequest) {
if (!req.userId) { if (!req.userId) {
@ -221,4 +233,109 @@ export class BridgeWidgetApi {
const connections = await this.connMan.getConnectionTargets(req.userId, type, req.query); const connections = await this.connMan.getConnectionTargets(req.userId, type, req.query);
res.send(connections); res.send(connections);
} }
private async getAuth(req: ProvisioningRequest, res: Response<GetAuthResponse>) {
if (!req.userId) {
throw Error('Expected userId on request');
}
const service = req.params.service;
if (!service) {
throw Error('Expected service in parameters');
}
// TODO: Should this be part of the GitHub module code.
if (service === 'github') {
if (!this.config.github || !this.config.github.oauth) {
throw new ApiError('GitHub oauth is not configured', ErrCode.DisabledFeature);
}
let user;
try {
const octokit = await this.tokenStore.getOctokitForUser(req.userId);
if (octokit !== null) {
const me = await octokit.users.getAuthenticated();
user = {
name: me.data.login,
};
}
} catch (e) {
// Need to authenticate
}
if (user) {
return res.json({
authenticated: true,
user
});
} else {
const state = this.tokenStore.createStateForOAuth(req.userId);
const authUrl = GithubInstance.generateOAuthUrl(
this.config.github.baseUrl,
'authorize',
{
state,
client_id: this.config.github.oauth.client_id,
redirect_uri: this.config.github.oauth.redirect_uri,
}
);
return res.json({
authenticated: false,
stateId: state,
authUrl
});
}
} else {
throw new ApiError('Service not found', ErrCode.NotFound);
}
}
private async getAuthPoll(req: ProvisioningRequest, res: Response<GetAuthPollResponse>) {
if (!req.userId) {
throw Error('Expected userId on request');
}
const { service, state } = req.params;
if (!service) {
throw Error('Expected service in parameters');
}
// N.B. Service isn't really used.
const stateUserId = this.tokenStore.getUserIdForOAuthState(state);
if (!stateUserId || req.userId !== stateUserId) {
// If the state isn't found then either the state has been completed or the key is wrong.
// We don't actually know, so we assume the sender knows what they are doing.
res.send({
state: 'complete',
});
return;
}
res.send({
state: 'waiting',
});
return;
}
private async postAuthLogout(req: ProvisioningRequest, res: Response<{ok: true}>) {
if (!req.userId) {
throw Error('Expected userId on request');
}
const { service } = req.params;
if (!service) {
throw Error('Expected service in parameters');
}
if (AllowedTokenTypes.includes(service)) {
const result = await this.tokenStore.clearUserToken(service as TokenType, req.userId);
if (result) {
res.send({ok: true});
} else {
throw new ApiError("You are not logged in", ErrCode.NotFound);
}
} else {
throw new ApiError('Service not found', ErrCode.NotFound);
}
}
} }

View File

@ -36,3 +36,24 @@ export interface GetConnectionsForServiceResponse<T extends GetConnectionsRespon
connections: T[]; connections: T[];
canEdit: boolean; canEdit: boolean;
} }
export interface GetAuthResponseAuthenticated {
authenticated: true;
user: {
name: string;
}
}
export interface GetAuthResponseUnauthenticated {
authenticated: false;
authUrl: string;
stateId: string;
}
export type GetAuthResponse = GetAuthResponseAuthenticated|GetAuthResponseUnauthenticated;
export interface GetAuthPollResponse {
state: 'complete'|'waiting';
}

View File

@ -301,7 +301,7 @@ describe("GenericHookConnection", () => {
const intent = as.getIntentForUserId(senderUserId); const intent = as.getIntentForUserId(senderUserId);
// This should fail the first time, then pass once we've tried to invite the user // This should fail the first time, then pass once we've tried to invite the user
intent.ensureJoined = (roomId: string) => { intent.ensureJoined = () => {
throw new MatrixError({ errcode: "FORCED_FAILURE", error: "Test forced error"}, 500) throw new MatrixError({ errcode: "FORCED_FAILURE", error: "Test forced error"}, 500)
}; };
try { try {

View File

@ -1,6 +1,7 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import preact from '@preact/preset-vite' import preact from '@preact/preset-vite'
import svgLoader from 'vite-svg-loader' import svgLoader from 'vite-svg-loader'
import { resolve } from 'path'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
@ -9,6 +10,12 @@ export default defineConfig({
base: '', base: '',
build: { build: {
outDir: '../public', outDir: '../public',
rollupOptions: {
input: {
main: resolve('web', 'index.html'),
oauth: resolve('web', 'oauth.html'),
}
},
emptyOutDir: true, emptyOutDir: true,
}, },
}) })

View File

@ -1,4 +1,4 @@
import { BridgeRoomState, GetConnectionsForServiceResponse } from '../src/Widgets/BridgeWidgetInterface'; import { BridgeRoomState, GetAuthPollResponse, GetAuthResponse, GetConnectionsForServiceResponse } from '../src/Widgets/BridgeWidgetInterface';
import { GetConnectionsResponseItem } from "../src/provisioning/api"; import { GetConnectionsResponseItem } from "../src/provisioning/api";
import { ExchangeOpenAPIRequestBody, ExchangeOpenAPIResponseBody } from "matrix-appservice-bridge"; import { ExchangeOpenAPIRequestBody, ExchangeOpenAPIResponseBody } from "matrix-appservice-bridge";
import { WidgetApi } from 'matrix-widget-api'; import { WidgetApi } from 'matrix-widget-api';
@ -138,6 +138,18 @@ export class BridgeAPI {
const searchParams = filters && !!Object.keys(filters).length && new URLSearchParams(filters); const searchParams = filters && !!Object.keys(filters).length && new URLSearchParams(filters);
return this.request('GET', `/widgetapi/v1/targets/${encodeURIComponent(type)}${searchParams ? `?${searchParams}` : ''}`, undefined, { abortController }); return this.request('GET', `/widgetapi/v1/targets/${encodeURIComponent(type)}${searchParams ? `?${searchParams}` : ''}`, undefined, { abortController });
} }
async getAuth(service: string): Promise<GetAuthResponse> {
return this.request('GET', `/widgetapi/v1/service/${service}/auth`);
}
async getAuthPoll(service: string, state: string): Promise<GetAuthPollResponse> {
return this.request('GET', `/widgetapi/v1/service/${service}/auth/${state}`);
}
async serviceLogout(service: string): Promise<GetAuthResponse> {
return this.request('POST', `/widgetapi/v1/service/${service}/auth/logout`);
}
} }
export const embedTypeParameter = 'io_element_embed_type'; export const embedTypeParameter = 'io_element_embed_type';

View File

@ -12,6 +12,7 @@ type Project = DropItem;
interface IProps { interface IProps {
serviceName: string; serviceName: string;
addNewInstanceUrl?: string;
getInstances(): Promise<Instance[]>; getInstances(): Promise<Instance[]>;
getProjects(currentInstance: string, searchTerm?: string, abortController?: AbortController): Promise<Project[]>; getProjects(currentInstance: string, searchTerm?: string, abortController?: AbortController): Promise<Project[]>;
onPicked: (instanceValue: string, projectValue: string) => void; onPicked: (instanceValue: string, projectValue: string) => void;
@ -27,6 +28,7 @@ interface IProps {
*/ */
export function ConnectionSearch({ export function ConnectionSearch({
serviceName, serviceName,
addNewInstanceUrl,
onPicked, onPicked,
onClear, onClear,
getInstances, getInstances,
@ -104,15 +106,28 @@ export function ConnectionSearch({
const searchProps = useMemo(() => ({ instance: currentInstance }), [currentInstance]); const searchProps = useMemo(() => ({ instance: currentInstance }), [currentInstance]);
let addNewInstance = null;
if (instances?.length === 0) {
if (addNewInstanceUrl) {
addNewInstance = <p> You have not connected any {serviceName} instances.
<a href={addNewInstanceUrl} rel="noreferrer" target="_blank">Add a new instances</a>.
</p>;
} else {
addNewInstance = <p> You have not connected any {serviceName} instances.</p>;
}
} else if (addNewInstanceUrl) {
addNewInstance = <p><a href={addNewInstanceUrl} rel="noreferrer" target="_blank">Add a new instances</a>.</p>
} // otherwise, empty
return <div> return <div>
{!searchError && instances === null && <p> Loading {serviceName} instances. </p>} {!searchError && instances === null && <p> Loading {serviceName} instances. </p>}
{instances?.length === 0 && <p> You are not logged into any {serviceName} instances. </p>}
{searchError && <ErrorPane header="Search error"> {searchError} </ErrorPane> } {searchError && <ErrorPane header="Search error"> {searchError} </ErrorPane> }
<InputField visible={!!instances?.length} label={`${serviceName} Instance`} noPadding={true}> <InputField visible={!!instances?.length} label={`${serviceName} Instance`} noPadding={true}>
<select onChange={onInstancePicked}> <select onChange={onInstancePicked}>
{instanceListResults} {instanceListResults}
</select> </select>
</InputField> </InputField>
{ addNewInstance }
{ currentInstance && <InputField label="Project" noPadding={true}> { currentInstance && <InputField label="Project" noPadding={true}>
<DropdownSearch <DropdownSearch
placeholder={`Your project name, such as ${exampleProjectName}`} placeholder={`Your project name, such as ${exampleProjectName}`}

View File

@ -38,14 +38,12 @@ export const DropdownSearch = function<T>({searchFn, searchProps, onChange, onEr
// Reset if the search properties are altered. // Reset if the search properties are altered.
useEffect(() => { useEffect(() => {
console.log("Resetting props");
setSearchTerm(""); setSearchTerm("");
setSelectedItem(null); setSelectedItem(null);
}, [searchProps]); }, [searchProps]);
// Search whenever the term is updated. // Search whenever the term is updated.
useEffect(() => { useEffect(() => {
console.log("New search");
if (searchTerm.trim().length === 0) { if (searchTerm.trim().length === 0) {
return; return;
} }

View File

@ -0,0 +1,94 @@
import { useCallback, useEffect, useState } from "preact/hooks";
import { GetAuthResponse } from "../../../src/Widgets/BridgeWidgetInterface";
import { BridgeAPI } from "../../BridgeAPI";
import { Button } from "../elements";
const PollAuthEveryMs = 3000;
export const ServiceAuth = ({
api,
service,
loginLabel = "Log in",
authState,
onAuthSucceeded,
}: {
api: BridgeAPI,
service: string,
authState: GetAuthResponse,
onAuthSucceeded: () => void,
loginLabel?: string,
}) => {
const [pollStateId, setPollStateId] = useState<string|null>();
const pollAuth = useCallback(async (pollId) => {
try {
const res = await api.getAuthPoll(service, pollId);
if (res.state === "waiting") {
// Keep going
return;
}
// Completed.
setPollStateId(null);
onAuthSucceeded();
} catch (ex) {
console.warn(`Failed to poll for state check`, ex);
}
}, [service, api, onAuthSucceeded])
useEffect(() => {
if (!pollStateId) {
return;
}
let pollTimerId: number;
const poll = async () => {
try {
await pollAuth(pollStateId);
}
finally {
// Recursively call poll
pollTimerId = window.setTimeout(
poll,
PollAuthEveryMs,
);
}
};
void poll();
// Cleanup
return () => {
window.clearTimeout(pollTimerId);
};
}, [api, service, pollStateId, onAuthSucceeded, pollAuth]);
const loginToService = useCallback(() => {
if (authState.authenticated) {
// No need to do anything
return;
}
window.open(authState.authUrl, '_blank');
setPollStateId(authState.stateId);
}, [authState]);
const logoutOfService = useCallback(() => {
if (!authState.authenticated) {
// No need to do anything
return;
}
api.serviceLogout(service).then(() => {
onAuthSucceeded();
}).catch((ex) => {
console.warn(`Failed to poll for state check`, ex);
})
}, [api, onAuthSucceeded, service, authState]);
if ('authUrl' in authState) {
return <Button onClick={loginToService}>
{ loginLabel }
</Button>;
}
return <p>
Logged in as <strong>{authState.user?.name ?? ''}</strong>. <a href="#" onClick={logoutOfService}>Logout</a>
</p>;
};

View File

@ -2,7 +2,7 @@ import { FunctionComponent, createRef } from "preact";
import { useCallback } from "preact/hooks" import { useCallback } from "preact/hooks"
import { BridgeConfig } from "../../BridgeAPI"; import { BridgeConfig } from "../../BridgeAPI";
import { FeedConnectionState, FeedResponseItem } from "../../../src/Connections/FeedConnection"; import { FeedConnectionState, FeedResponseItem } from "../../../src/Connections/FeedConnection";
import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig"; import { ConnectionConfigurationProps, IRoomConfigText, RoomConfig } from "./RoomConfig";
import { Button, ButtonSet, InputField } from "../elements"; import { Button, ButtonSet, InputField } from "../elements";
import styles from "./FeedConnection.module.scss"; import styles from "./FeedConnection.module.scss";
@ -66,7 +66,7 @@ interface ServiceConfig {
pollIntervalSeconds: number, pollIntervalSeconds: number,
} }
const RoomConfigText = { const roomConfigText: IRoomConfigText = {
header: 'RSS/Atom feeds', header: 'RSS/Atom feeds',
createNew: 'Subscribe to a feed', createNew: 'Subscribe to a feed',
listCanEdit: 'Feeds subscribed to', listCanEdit: 'Feeds subscribed to',
@ -83,7 +83,7 @@ export const FeedsConfig: BridgeConfig = ({ api, roomId, showHeader }) => {
roomId={roomId} roomId={roomId}
type="feeds" type="feeds"
connectionEventType="uk.half-shot.matrix-hookshot.feed" connectionEventType="uk.half-shot.matrix-hookshot.feed"
text={RoomConfigText} text={roomConfigText}
listItemName={RoomConfigListItemFunc} listItemName={RoomConfigListItemFunc}
connectionConfigComponent={ConnectionConfiguration} connectionConfigComponent={ConnectionConfiguration}
/>; />;

View File

@ -1,13 +1,15 @@
import GitHubIcon from "../../icons/github.png"; import GitHubIcon from "../../icons/github.png";
import { BridgeConfig } from "../../BridgeAPI"; import { BridgeConfig } from "../../BridgeAPI";
import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig"; import { ConnectionConfigurationProps, IRoomConfigText, RoomConfig } from "./RoomConfig";
import { EventHookCheckbox } from '../elements/EventHookCheckbox'; import { EventHookCheckbox } from '../elements/EventHookCheckbox';
import { FunctionComponent, createRef } from "preact"; import { FunctionComponent, createRef } from "preact";
import { GitHubRepoConnectionState, GitHubRepoResponseItem, GitHubRepoConnectionRepoTarget, GitHubRepoConnectionOrgTarget } from "../../../src/Connections/GithubRepo"; import { GitHubRepoConnectionState, GitHubRepoResponseItem, GitHubRepoConnectionRepoTarget, GitHubRepoConnectionOrgTarget } from "../../../src/Connections/GithubRepo";
import { InputField, ButtonSet, Button } from "../elements"; import { InputField, ButtonSet, Button } from "../elements";
import { useState, useCallback, useMemo } from "preact/hooks"; import { useState, useCallback, useMemo, useEffect } from "preact/hooks";
import { DropItem } from "../elements/DropdownSearch"; import { DropItem } from "../elements/DropdownSearch";
import ConnectionSearch from "../elements/ConnectionSearch"; import ConnectionSearch from "../elements/ConnectionSearch";
import { ServiceAuth } from "./Auth";
import { GetAuthResponse } from "../../../src/Widgets/BridgeWidgetInterface";
const EventType = "uk.half-shot.matrix-hookshot.github.repository"; const EventType = "uk.half-shot.matrix-hookshot.github.repository";
@ -15,9 +17,37 @@ function getRepoFullName(state: GitHubRepoConnectionState) {
return `${state.org}/${state.repo}`; return `${state.org}/${state.repo}`;
} }
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, GitHubRepoResponseItem, GitHubRepoConnectionState>> = ({api, existingConnection, onSave, onRemove }) => { const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<never, GitHubRepoResponseItem, GitHubRepoConnectionState>> = ({
showAuthPrompt, loginLabel, serviceConfig, api, existingConnection, onSave, onRemove
}) => {
// Assume true if we have no auth prompt.
const [authedResponse, setAuthResponse] = useState<GetAuthResponse|null>(null);
const [enabledHooks, setEnabledHooks] = useState<string[]>(existingConnection?.config.enableHooks || []); const [enabledHooks, setEnabledHooks] = useState<string[]>(existingConnection?.config.enableHooks || []);
const checkAuth = useCallback(() => {
api.getAuth("github").then((res) => {
setAuthResponse(res);
}).catch(ex => {
console.warn("Could not check authed state, assuming yes", ex);
setAuthResponse({
authenticated: true,
user: {
name: 'Unknown'
}
});
})
}, [api]);
useEffect(() => {
if (!showAuthPrompt) {
return;
}
checkAuth();
}, [showAuthPrompt, checkAuth])
const { newInstallationUrl } = serviceConfig;
const toggleEnabledHook = useCallback((evt: any) => { const toggleEnabledHook = useCallback((evt: any) => {
const key = (evt.target as HTMLElement).getAttribute('x-event-name'); const key = (evt.target as HTMLElement).getAttribute('x-event-name');
if (key) { if (key) {
@ -74,8 +104,10 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
const clearInstance = useCallback(() => setConnectionState(null), [setConnectionState]); const clearInstance = useCallback(() => setConnectionState(null), [setConnectionState]);
return <form onSubmit={handleSave}> return <form onSubmit={handleSave}>
{!existingConnection && <ConnectionSearch {authedResponse && <ServiceAuth onAuthSucceeded={checkAuth} authState={authedResponse} service="github" loginLabel={loginLabel} api={api} />}
{!existingConnection && authedResponse?.authenticated && <ConnectionSearch
serviceName="GitHub" serviceName="GitHub"
addNewInstanceUrl={newInstallationUrl}
getInstances={getInstances} getInstances={getInstances}
getProjects={getProjects} getProjects={getProjects}
onPicked={setInstance} onPicked={setInstance}
@ -120,14 +152,15 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
</ul> </ul>
</InputField> </InputField>
<ButtonSet> <ButtonSet>
{ canEdit && <Button type="submit" disabled={!existingConnection && !connectionState}>{ existingConnection ? "Save" : "Add repository" }</Button>} { canEdit && authedResponse?.authenticated && <Button type="submit" disabled={!existingConnection && !connectionState}>{ existingConnection ? "Save" : "Add repository" }</Button>}
{ canEdit && existingConnection && <Button intent="remove" onClick={onRemove}>Remove repository</Button>} { canEdit && existingConnection && <Button intent="remove" onClick={onRemove}>Remove repository</Button>}
</ButtonSet> </ButtonSet>
</form>; </form>;
}; };
const RoomConfigText = { const roomConfigText: IRoomConfigText = {
header: 'GitHub Repositories', header: 'GitHub Repositories',
login: 'Log in to GitHub',
createNew: 'Add new GitHub repository', createNew: 'Add new GitHub repository',
listCanEdit: 'Your connected repositories', listCanEdit: 'Your connected repositories',
listCantEdit: 'Connected repositories', listCantEdit: 'Connected repositories',
@ -142,7 +175,8 @@ export const GithubRepoConfig: BridgeConfig = ({ api, roomId, showHeader }) => {
api={api} api={api}
roomId={roomId} roomId={roomId}
type="github" type="github"
text={RoomConfigText} showAuthPrompt={true}
text={roomConfigText}
listItemName={RoomConfigListItemFunc} listItemName={RoomConfigListItemFunc}
connectionEventType={EventType} connectionEventType={EventType}
connectionConfigComponent={ConnectionConfiguration} connectionConfigComponent={ConnectionConfiguration}

View File

@ -10,31 +10,46 @@ import { LoadingSpinner } from '../elements/LoadingSpinner';
export interface ConnectionConfigurationProps<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState> { export interface ConnectionConfigurationProps<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState> {
serviceConfig: SConfig; serviceConfig: SConfig;
loginLabel?: string;
showAuthPrompt?: boolean;
onSave: (newConfig: ConnectionState) => void, onSave: (newConfig: ConnectionState) => void,
existingConnection?: ConnectionType; existingConnection?: ConnectionType;
onRemove?: () => void, onRemove?: () => void,
api: BridgeAPI; api: BridgeAPI;
} }
export interface IRoomConfigText {
header: string;
login?: string;
createNew: string;
listCanEdit: string;
listCantEdit: string;
}
interface IRoomConfigProps<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState> { interface IRoomConfigProps<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState> {
api: BridgeAPI; api: BridgeAPI;
roomId: string; roomId: string;
type: string; type: string;
showAuthPrompt?: boolean;
showHeader: boolean; showHeader: boolean;
headerImg: string; headerImg: string;
text: { text: IRoomConfigText;
header: string;
createNew: string;
listCanEdit: string;
listCantEdit: string;
};
connectionEventType: string; connectionEventType: string;
listItemName: (c: ConnectionType) => string, listItemName: (c: ConnectionType) => string,
connectionConfigComponent: FunctionComponent<ConnectionConfigurationProps<SConfig, ConnectionType, ConnectionState>>; connectionConfigComponent: FunctionComponent<ConnectionConfigurationProps<SConfig, ConnectionType, ConnectionState>>;
} }
export const RoomConfig = function<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState>(props: IRoomConfigProps<SConfig, ConnectionType, ConnectionState>) { export const RoomConfig = function<SConfig, ConnectionType extends GetConnectionsResponseItem, ConnectionState extends IConnectionState>(props: IRoomConfigProps<SConfig, ConnectionType, ConnectionState>) {
const { api, roomId, type, headerImg, showHeader, text, listItemName, connectionEventType } = props; const {
api,
roomId,
type,
showAuthPrompt = false,
headerImg,
showHeader,
text,
listItemName,
connectionEventType
} = props;
const ConnectionConfigComponent = props.connectionConfigComponent; const ConnectionConfigComponent = props.connectionConfigComponent;
const [ error, setError ] = useState<null|{header?: string, message: string, isWarning?: boolean, forPrevious?: boolean}>(null); const [ error, setError ] = useState<null|{header?: string, message: string, isWarning?: boolean, forPrevious?: boolean}>(null);
const [ connections, setConnections ] = useState<ConnectionType[]|null>(null); const [ connections, setConnections ] = useState<ConnectionType[]|null>(null);
@ -115,6 +130,8 @@ export const RoomConfig = function<SConfig, ConnectionType extends GetConnection
api={api} api={api}
serviceConfig={serviceConfig} serviceConfig={serviceConfig}
onSave={handleSaveOnCreation} onSave={handleSaveOnCreation}
loginLabel={text.login}
showAuthPrompt={showAuthPrompt}
/>} />}
</section>} </section>}
{ connections === null && <LoadingSpinner /> } { connections === null && <LoadingSpinner /> }

View File

@ -4,7 +4,7 @@ import App from './App';
import "./fonts/fonts.scss" import "./fonts/fonts.scss"
import "./styling.scss"; import "./styling.scss";
const root = document.getElementsByTagName('main')[0]; const [ root ] = document.getElementsByTagName('main');
if (root) { if (root) {
render(<App />, root); render(<App />, root);

18
web/oauth.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Account</title>
</head>
<body>
<main>
<noscript>
<p>
Your account has been connected. You may now close this window.
</p>
</noscript>
</main>
<script type="module" src="/oauth.tsx"></script>
</body>
</html>

23
web/oauth.scss Normal file
View File

@ -0,0 +1,23 @@
a {
color: var(--primary-color);
}
a:visited {
color: var(--primary-color);
}
body {
margin-top: 5em;
margin-left: 5em;
margin-right: 5em;
text-align: center;
}
h1 {
font-size: 1.5em;
}
p {
font-size: 1.25em;
}

40
web/oauth.tsx Normal file
View File

@ -0,0 +1,40 @@
import "./fonts/fonts.scss"
import "./styling.scss";
import "./oauth.scss";
import { render } from 'preact';
import 'preact/devtools';
const root = document.getElementsByTagName('main')[0];
const ServiceToName: Record<string,string> = {
github: 'GitHub',
gitlab: 'GitLab',
default: ''
}
function RenderOAuth() {
const params = new URLSearchParams(window.location.search);
const service = params.get('service') ?? 'default';
const error = params.get('error');
const errcode = params.get('errcode');
const oauthKind = params.get('oauth-kind') ?? 'account';
if (error) {
return <>
<h1>Could not connect your { ServiceToName[service] } {oauthKind} to Hookshot.</h1>
<p>
<code>{errcode}</code> {error}
</p>
</>;
}
return <>
<h1>Your { ServiceToName[service] } {oauthKind} has been connected.</h1>
<p>You may close this window.</p>
</>;
}
if (root) {
render(<RenderOAuth />, root);
}

View File

@ -19,6 +19,14 @@
// } // }
// } // }
a {
color: var(--primary-color);
}
a:visited {
color: var(--primary-color);
}
body { body {
margin: 0; margin: 0;
} }

439
yarn.lock
View File

@ -658,6 +658,116 @@
enabled "2.0.x" enabled "2.0.x"
kuler "^2.0.0" kuler "^2.0.0"
"@esbuild/android-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz#cf91e86df127aa3d141744edafcba0abdc577d23"
integrity sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==
"@esbuild/android-arm@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz#025b6246d3f68b7bbaa97069144fb5fb70f2fff2"
integrity sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==
"@esbuild/android-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz#c820e0fef982f99a85c4b8bfdd582835f04cd96e"
integrity sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==
"@esbuild/darwin-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz#edef4487af6b21afabba7be5132c26d22379b220"
integrity sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==
"@esbuild/darwin-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz#42829168730071c41ef0d028d8319eea0e2904b4"
integrity sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==
"@esbuild/freebsd-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz#1f4af488bfc7e9ced04207034d398e793b570a27"
integrity sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==
"@esbuild/freebsd-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz#636306f19e9bc981e06aa1d777302dad8fddaf72"
integrity sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==
"@esbuild/linux-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz#a003f7ff237c501e095d4f3a09e58fc7b25a4aca"
integrity sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==
"@esbuild/linux-arm@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz#b591e6a59d9c4fe0eeadd4874b157ab78cf5f196"
integrity sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==
"@esbuild/linux-ia32@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz#24333a11027ef46a18f57019450a5188918e2a54"
integrity sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==
"@esbuild/linux-loong64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz#d5ad459d41ed42bbd4d005256b31882ec52227d8"
integrity sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==
"@esbuild/linux-mips64el@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz#4e5967a665c38360b0a8205594377d4dcf9c3726"
integrity sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==
"@esbuild/linux-ppc64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz#206443a02eb568f9fdf0b438fbd47d26e735afc8"
integrity sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==
"@esbuild/linux-riscv64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz#c351e433d009bf256e798ad048152c8d76da2fc9"
integrity sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==
"@esbuild/linux-s390x@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz#661f271e5d59615b84b6801d1c2123ad13d9bd87"
integrity sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==
"@esbuild/linux-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz#e4ba18e8b149a89c982351443a377c723762b85f"
integrity sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==
"@esbuild/netbsd-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz#7d4f4041e30c5c07dd24ffa295c73f06038ec775"
integrity sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==
"@esbuild/openbsd-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz#970fa7f8470681f3e6b1db0cc421a4af8060ec35"
integrity sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==
"@esbuild/sunos-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz#abc60e7c4abf8b89fb7a4fe69a1484132238022c"
integrity sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==
"@esbuild/win32-arm64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz#7b0ff9e8c3265537a7a7b1fd9a24e7bd39fcd87a"
integrity sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==
"@esbuild/win32-ia32@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz#e90fe5267d71a7b7567afdc403dfd198c292eb09"
integrity sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==
"@esbuild/win32-x64@0.16.17":
version "0.16.17"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz#c5a1a4bfe1b57f0c3e61b29883525c6da3e5c091"
integrity sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==
"@eslint/eslintrc@^1.0.5": "@eslint/eslintrc@^1.0.5":
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318"
@ -2252,36 +2362,44 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2:
shebang-command "^2.0.0" shebang-command "^2.0.0"
which "^2.0.1" which "^2.0.1"
css-select@^4.1.3: css-select@^5.1.0:
version "4.3.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
dependencies: dependencies:
boolbase "^1.0.0" boolbase "^1.0.0"
css-what "^6.0.1" css-what "^6.1.0"
domhandler "^4.3.1" domhandler "^5.0.2"
domutils "^2.8.0" domutils "^3.0.1"
nth-check "^2.0.1" nth-check "^2.0.1"
css-tree@^1.1.2, css-tree@^1.1.3: css-tree@^2.2.1:
version "1.1.3" version "2.3.1"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20"
integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==
dependencies: dependencies:
mdn-data "2.0.14" mdn-data "2.0.30"
source-map "^0.6.1" source-map-js "^1.0.1"
css-what@^6.0.1: css-tree@~2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032"
integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==
dependencies:
mdn-data "2.0.28"
source-map-js "^1.0.1"
css-what@^6.1.0:
version "6.1.0" version "6.1.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
csso@^4.2.0: csso@^5.0.5:
version "4.2.0" version "5.0.5"
resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6"
integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==
dependencies: dependencies:
css-tree "^1.1.2" css-tree "~2.2.0"
dashdash@^1.12.0: dashdash@^1.12.0:
version "1.14.1" version "1.14.1"
@ -2432,19 +2550,35 @@ dom-serializer@^1.0.1:
domhandler "^4.2.0" domhandler "^4.2.0"
entities "^2.0.0" entities "^2.0.0"
domelementtype@^2.0.1, domelementtype@^2.2.0: dom-serializer@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
dependencies:
domelementtype "^2.3.0"
domhandler "^5.0.2"
entities "^4.2.0"
domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: domhandler@^4.0.0, domhandler@^4.2.0:
version "4.3.1" version "4.3.1"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
dependencies: dependencies:
domelementtype "^2.2.0" domelementtype "^2.2.0"
domutils@^2.5.2, domutils@^2.8.0: domhandler@^5.0.1, domhandler@^5.0.2:
version "5.0.3"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
dependencies:
domelementtype "^2.3.0"
domutils@^2.5.2:
version "2.8.0" version "2.8.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
@ -2453,6 +2587,15 @@ domutils@^2.5.2, domutils@^2.8.0:
domelementtype "^2.2.0" domelementtype "^2.2.0"
domhandler "^4.2.0" domhandler "^4.2.0"
domutils@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c"
integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==
dependencies:
dom-serializer "^2.0.0"
domelementtype "^2.3.0"
domhandler "^5.0.1"
ecc-jsbn@~0.1.1: ecc-jsbn@~0.1.1:
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@ -2515,6 +2658,11 @@ entities@^2.0.0, entities@^2.0.3:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
entities@^4.2.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174"
integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==
entities@~2.1.0: entities@~2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
@ -2560,131 +2708,33 @@ es6-error@^4.0.1:
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
esbuild-android-64@0.14.38: esbuild@^0.16.14:
version "0.14.38" version "0.16.17"
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.38.tgz#5b94a1306df31d55055f64a62ff6b763a47b7f64" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259"
integrity sha512-aRFxR3scRKkbmNuGAK+Gee3+yFxkTJO/cx83Dkyzo4CnQl/2zVSurtG6+G86EQIZ+w+VYngVyK7P3HyTBKu3nw== integrity sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==
esbuild-android-arm64@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.38.tgz#78acc80773d16007de5219ccce544c036abd50b8"
integrity sha512-L2NgQRWuHFI89IIZIlpAcINy9FvBk6xFVZ7xGdOwIm8VyhX1vNCEqUJO3DPSSy945Gzdg98cxtNt8Grv1CsyhA==
esbuild-darwin-64@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.38.tgz#e02b1291f629ebdc2aa46fabfacc9aa28ff6aa46"
integrity sha512-5JJvgXkX87Pd1Og0u/NJuO7TSqAikAcQQ74gyJ87bqWRVeouky84ICoV4sN6VV53aTW+NE87qLdGY4QA2S7KNA==
esbuild-darwin-arm64@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.38.tgz#01eb6650ec010b18c990e443a6abcca1d71290a9"
integrity sha512-eqF+OejMI3mC5Dlo9Kdq/Ilbki9sQBw3QlHW3wjLmsLh+quNfHmGMp3Ly1eWm981iGBMdbtSS9+LRvR2T8B3eQ==
esbuild-freebsd-64@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.38.tgz#790b8786729d4aac7be17648f9ea8e0e16475b5e"
integrity sha512-epnPbhZUt93xV5cgeY36ZxPXDsQeO55DppzsIgWM8vgiG/Rz+qYDLmh5ts3e+Ln1wA9dQ+nZmVHw+RjaW3I5Ig==
esbuild-freebsd-arm64@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.38.tgz#b66340ab28c09c1098e6d9d8ff656db47d7211e6"
integrity sha512-/9icXUYJWherhk+y5fjPI5yNUdFPtXHQlwP7/K/zg8t8lQdHVj20SqU9/udQmeUo5pDFHMYzcEFfJqgOVeKNNQ==
esbuild-linux-32@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.38.tgz#7927f950986fd39f0ff319e92839455912b67f70"
integrity sha512-QfgfeNHRFvr2XeHFzP8kOZVnal3QvST3A0cgq32ZrHjSMFTdgXhMhmWdKzRXP/PKcfv3e2OW9tT9PpcjNvaq6g==
esbuild-linux-64@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.38.tgz#4893d07b229d9cfe34a2b3ce586399e73c3ac519"
integrity sha512-uuZHNmqcs+Bj1qiW9k/HZU3FtIHmYiuxZ/6Aa+/KHb/pFKr7R3aVqvxlAudYI9Fw3St0VCPfv7QBpUITSmBR1Q==
esbuild-linux-arm64@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.38.tgz#8442402e37d0b8ae946ac616784d9c1a2041056a"
integrity sha512-HlMGZTEsBrXrivr64eZ/EO0NQM8H8DuSENRok9d+Jtvq8hOLzrxfsAT9U94K3KOGk2XgCmkaI2KD8hX7F97lvA==
esbuild-linux-arm@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.38.tgz#d5dbf32d38b7f79be0ec6b5fb2f9251fd9066986"
integrity sha512-FiFvQe8J3VKTDXG01JbvoVRXQ0x6UZwyrU4IaLBZeq39Bsbatd94Fuc3F1RGqPF5RbIWW7RvkVQjn79ejzysnA==
esbuild-linux-mips64le@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.38.tgz#95081e42f698bbe35d8ccee0e3a237594b337eb5"
integrity sha512-qd1dLf2v7QBiI5wwfil9j0HG/5YMFBAmMVmdeokbNAMbcg49p25t6IlJFXAeLzogv1AvgaXRXvgFNhScYEUXGQ==
esbuild-linux-ppc64le@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.38.tgz#dceb0a1b186f5df679618882a7990bd422089b47"
integrity sha512-mnbEm7o69gTl60jSuK+nn+pRsRHGtDPfzhrqEUXyCl7CTOCLtWN2bhK8bgsdp6J/2NyS/wHBjs1x8aBWwP2X9Q==
esbuild-linux-riscv64@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.38.tgz#61fb8edb75f475f9208c4a93ab2bfab63821afd2"
integrity sha512-+p6YKYbuV72uikChRk14FSyNJZ4WfYkffj6Af0/Tw63/6TJX6TnIKE+6D3xtEc7DeDth1fjUOEqm+ApKFXbbVQ==
esbuild-linux-s390x@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.38.tgz#34c7126a4937406bf6a5e69100185fd702d12fe0"
integrity sha512-0zUsiDkGJiMHxBQ7JDU8jbaanUY975CdOW1YDrurjrM0vWHfjv9tLQsW9GSyEb/heSK1L5gaweRjzfUVBFoybQ==
esbuild-netbsd-64@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.38.tgz#322ea9937d9e529183ee281c7996b93eb38a5d95"
integrity sha512-cljBAApVwkpnJZfnRVThpRBGzCi+a+V9Ofb1fVkKhtrPLDYlHLrSYGtmnoTVWDQdU516qYI8+wOgcGZ4XIZh0Q==
esbuild-openbsd-64@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.38.tgz#1ca29bb7a2bf09592dcc26afdb45108f08a2cdbd"
integrity sha512-CDswYr2PWPGEPpLDUO50mL3WO/07EMjnZDNKpmaxUPsrW+kVM3LoAqr/CE8UbzugpEiflYqJsGPLirThRB18IQ==
esbuild-sunos-64@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.38.tgz#c9446f7d8ebf45093e7bb0e7045506a88540019b"
integrity sha512-2mfIoYW58gKcC3bck0j7lD3RZkqYA7MmujFYmSn9l6TiIcAMpuEvqksO+ntBgbLep/eyjpgdplF7b+4T9VJGOA==
esbuild-windows-32@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.38.tgz#f8e9b4602fd0ccbd48e5c8d117ec0ba4040f2ad1"
integrity sha512-L2BmEeFZATAvU+FJzJiRLFUP+d9RHN+QXpgaOrs2klshoAm1AE6Us4X6fS9k33Uy5SzScn2TpcgecbqJza1Hjw==
esbuild-windows-64@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.38.tgz#280f58e69f78535f470905ce3e43db1746518107"
integrity sha512-Khy4wVmebnzue8aeSXLC+6clo/hRYeNIm0DyikoEqX+3w3rcvrhzpoix0S+MF9vzh6JFskkIGD7Zx47ODJNyCw==
esbuild-windows-arm64@0.14.38:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.38.tgz#d97e9ac0f95a4c236d9173fa9f86c983d6a53f54"
integrity sha512-k3FGCNmHBkqdJXuJszdWciAH77PukEyDsdIryEHn9cKLQFxzhT39dSumeTuggaQcXY57UlmLGIkklWZo2qzHpw==
esbuild@^0.14.27:
version "0.14.38"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.38.tgz#99526b778cd9f35532955e26e1709a16cca2fb30"
integrity sha512-12fzJ0fsm7gVZX1YQ1InkOE5f9Tl7cgf6JPYXRJtPIoE0zkWAbHdPHVPPaLi9tYAcEBqheGzqLn/3RdTOyBfcA==
optionalDependencies: optionalDependencies:
esbuild-android-64 "0.14.38" "@esbuild/android-arm" "0.16.17"
esbuild-android-arm64 "0.14.38" "@esbuild/android-arm64" "0.16.17"
esbuild-darwin-64 "0.14.38" "@esbuild/android-x64" "0.16.17"
esbuild-darwin-arm64 "0.14.38" "@esbuild/darwin-arm64" "0.16.17"
esbuild-freebsd-64 "0.14.38" "@esbuild/darwin-x64" "0.16.17"
esbuild-freebsd-arm64 "0.14.38" "@esbuild/freebsd-arm64" "0.16.17"
esbuild-linux-32 "0.14.38" "@esbuild/freebsd-x64" "0.16.17"
esbuild-linux-64 "0.14.38" "@esbuild/linux-arm" "0.16.17"
esbuild-linux-arm "0.14.38" "@esbuild/linux-arm64" "0.16.17"
esbuild-linux-arm64 "0.14.38" "@esbuild/linux-ia32" "0.16.17"
esbuild-linux-mips64le "0.14.38" "@esbuild/linux-loong64" "0.16.17"
esbuild-linux-ppc64le "0.14.38" "@esbuild/linux-mips64el" "0.16.17"
esbuild-linux-riscv64 "0.14.38" "@esbuild/linux-ppc64" "0.16.17"
esbuild-linux-s390x "0.14.38" "@esbuild/linux-riscv64" "0.16.17"
esbuild-netbsd-64 "0.14.38" "@esbuild/linux-s390x" "0.16.17"
esbuild-openbsd-64 "0.14.38" "@esbuild/linux-x64" "0.16.17"
esbuild-sunos-64 "0.14.38" "@esbuild/netbsd-x64" "0.16.17"
esbuild-windows-32 "0.14.38" "@esbuild/openbsd-x64" "0.16.17"
esbuild-windows-64 "0.14.38" "@esbuild/sunos-x64" "0.16.17"
esbuild-windows-arm64 "0.14.38" "@esbuild/win32-arm64" "0.16.17"
"@esbuild/win32-ia32" "0.16.17"
"@esbuild/win32-x64" "0.16.17"
escalade@^3.1.1: escalade@^3.1.1:
version "3.1.1" version "3.1.1"
@ -3667,6 +3717,13 @@ is-core-module@^2.8.1:
dependencies: dependencies:
has "^1.0.3" has "^1.0.3"
is-core-module@^2.9.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
dependencies:
has "^1.0.3"
is-date-object@^1.0.1: is-date-object@^1.0.1:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
@ -4304,10 +4361,15 @@ matrix-widget-api@^1.0.0:
"@types/events" "^3.0.0" "@types/events" "^3.0.0"
events "^3.2.0" events "^3.2.0"
mdn-data@2.0.14: mdn-data@2.0.28:
version "2.0.14" version "2.0.28"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba"
integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==
mdn-data@2.0.30:
version "2.0.30"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc"
integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==
mdurl@^1.0.1: mdurl@^1.0.1:
version "1.0.1" version "1.0.1"
@ -4489,7 +4551,7 @@ nanoid@3.1.20:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788"
integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==
nanoid@^3.1.30, nanoid@^3.3.3, nanoid@^3.3.4: nanoid@^3.1.30, nanoid@^3.3.4:
version "3.3.4" version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
@ -4918,12 +4980,12 @@ postcss@^8.3.11:
picocolors "^1.0.0" picocolors "^1.0.0"
source-map-js "^1.0.1" source-map-js "^1.0.1"
postcss@^8.4.13: postcss@^8.4.21:
version "8.4.13" version "8.4.21"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA== integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
dependencies: dependencies:
nanoid "^3.3.3" nanoid "^3.3.4"
picocolors "^1.0.0" picocolors "^1.0.0"
source-map-js "^1.0.2" source-map-js "^1.0.2"
@ -5221,7 +5283,7 @@ resolve-from@^5.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
resolve@^1.20.0, resolve@^1.22.0: resolve@^1.20.0:
version "1.22.0" version "1.22.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
@ -5230,6 +5292,15 @@ resolve@^1.20.0, resolve@^1.22.0:
path-parse "^1.0.7" path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0" supports-preserve-symlinks-flag "^1.0.0"
resolve@^1.22.1:
version "1.22.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
dependencies:
is-core-module "^2.9.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
resolve@^2.0.0-next.3: resolve@^2.0.0-next.3:
version "2.0.0-next.3" version "2.0.0-next.3"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46"
@ -5255,10 +5326,10 @@ rimraf@^3.0.0, rimraf@^3.0.2:
dependencies: dependencies:
glob "^7.1.3" glob "^7.1.3"
rollup@^2.59.0: rollup@^3.10.0:
version "2.72.0" version "3.19.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.72.0.tgz#f94280b003bcf9f2f1f2594059a9db5abced371e" resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.19.1.tgz#2b3a31ac1ff9f3afab2e523fa687fef5b0ee20fc"
integrity sha512-KqtR2YcO35/KKijg4nx4STO3569aqCUeGRkKWnJ6r+AvBBrVY9L4pmf4NHVrQr4mTOq6msbohflxr2kpihhaOA== integrity sha512-lAbrdN7neYCg/8WaoWn/ckzCtz+jr70GFfYdlf50OF7387HTg+wiuiqJRFYawwSPpqfqDNYqK7smY/ks2iAudg==
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
@ -5547,11 +5618,6 @@ sshpk@^1.7.0:
safer-buffer "^2.0.2" safer-buffer "^2.0.2"
tweetnacl "~0.14.0" tweetnacl "~0.14.0"
stable@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
stack-trace@0.0.x: stack-trace@0.0.x:
version "0.0.10" version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
@ -5705,18 +5771,17 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
svgo@^2.7.0: svgo@^3.0.2:
version "2.8.0" version "3.0.2"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.0.2.tgz#5e99eeea42c68ee0dc46aa16da093838c262fe0a"
integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== integrity sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==
dependencies: dependencies:
"@trysound/sax" "0.2.0" "@trysound/sax" "0.2.0"
commander "^7.2.0" commander "^7.2.0"
css-select "^4.1.3" css-select "^5.1.0"
css-tree "^1.1.3" css-tree "^2.2.1"
csso "^4.2.0" csso "^5.0.5"
picocolors "^1.0.0" picocolors "^1.0.0"
stable "^0.1.8"
tdigest@^0.1.1: tdigest@^0.1.1:
version "0.1.1" version "0.1.1"
@ -5952,23 +6017,23 @@ verror@1.10.0:
core-util-is "1.0.2" core-util-is "1.0.2"
extsprintf "^1.2.0" extsprintf "^1.2.0"
vite-svg-loader@^3.4.0: vite-svg-loader@^4.0.0:
version "3.4.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/vite-svg-loader/-/vite-svg-loader-3.4.0.tgz#4638827fe86b85ecfcea1ad61dd972c351d5befd" resolved "https://registry.yarnpkg.com/vite-svg-loader/-/vite-svg-loader-4.0.0.tgz#1cec4337dba3c23ab13bcabb111896e251b047ac"
integrity sha512-xD3yb1FX+f4l9/TmsYIqyki8ncpcVsZ2gEJFh/wLuNNqt55C8OJ+JlcMWOA/Z9gRA+ylV/TA1wmJLxzZkCRqlA== integrity sha512-0MMf1yzzSYlV4MGePsLVAOqXsbF5IVxbn4EEzqRnWxTQl8BJg/cfwIzfQNmNQxZp5XXwd4kyRKF1LytuHZTnqA==
dependencies: dependencies:
"@vue/compiler-sfc" "^3.2.20" "@vue/compiler-sfc" "^3.2.20"
svgo "^2.7.0" svgo "^3.0.2"
vite@^2.9.13: vite@^4.1.4:
version "2.9.13" version "4.1.4"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.13.tgz#859cb5d4c316c0d8c6ec9866045c0f7858ca6abc" resolved "https://registry.yarnpkg.com/vite/-/vite-4.1.4.tgz#170d93bcff97e0ebc09764c053eebe130bfe6ca0"
integrity sha512-AsOBAaT0AD7Mhe8DuK+/kE4aWYFMx/i0ZNi98hJclxb4e0OhQcZYUrvLjIaQ8e59Ui7txcvKMiJC1yftqpQoDw== integrity sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==
dependencies: dependencies:
esbuild "^0.14.27" esbuild "^0.16.14"
postcss "^8.4.13" postcss "^8.4.21"
resolve "^1.22.0" resolve "^1.22.1"
rollup "^2.59.0" rollup "^3.10.0"
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"