mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 13:17:08 +00:00
Add an expiry time to inbound webhooks (#984)
* Add logic to enable generic hook expiry * Add storage for hook expiry warnings. * Migrate generic hooks / add expiry field * Allow reporting a specific error and status code for generic webhooks * Report the specific error when a message fails to send * Refactor input class to better support datetime * Remove single use of innerChild * Add UI support for expiry configuration * Add new packages * Add warnings when the timer is about to expire. * Add send expiry notice config option * lint * document new option s * Fixup test * Add tests for expiry * Add textual command for setting a duration on a webhook. * Add e2e test for inbound hooks. * changelog * Add a configuration option to force webhooks to expire. * update config.sample.yml * fix field not working
This commit is contained in:
parent
6571b9f710
commit
80c7d35a18
1
changelog.d/985.feature
Normal file
1
changelog.d/985.feature
Normal file
@ -0,0 +1 @@
|
||||
Add support for setting an expiry time on a webhook. See the documentation on [Generic Webhooks](https://matrix-org.github.io/matrix-hookshot/latest/setup/webhooks.html) for more information.
|
@ -102,10 +102,13 @@ listeners:
|
||||
# enabled: false
|
||||
# outbound: false
|
||||
# enableHttpGet: false
|
||||
# sendExpiryNotice: false
|
||||
# requireExpiryTime: false
|
||||
# urlPrefix: https://example.com/webhook/
|
||||
# userIdPrefix: _webhooks_
|
||||
# allowJsTransformationFunctions: false
|
||||
# waitForComplete: false
|
||||
# maxExpiryTime: 30d
|
||||
|
||||
#feeds:
|
||||
# # (Optional) Configure this to enable RSS/Atom feed support
|
||||
|
@ -15,6 +15,8 @@ generic:
|
||||
allowJsTransformationFunctions: false
|
||||
waitForComplete: false
|
||||
enableHttpGet: false
|
||||
# maxExpiryTime: 30d
|
||||
# sendExpiryNotice: false
|
||||
# userIdPrefix: webhook_
|
||||
```
|
||||
|
||||
@ -43,6 +45,13 @@ has been sent (`true`). By default this is `false`.
|
||||
`enableHttpGet` means that webhooks can be triggered by `GET` requests, in addition to `POST` and `PUT`. This was previously on by default,
|
||||
but is now disabled due to concerns mentioned below.
|
||||
|
||||
`maxExpiryTime` sets an upper limit on how long a webhook can be valid for before the bridge expires it. By default this is unlimited. This
|
||||
takes a duration represented by a string. E.g. "30d" is 30 days. See [this page](https://github.com/jkroso/parse-duration?tab=readme-ov-file#available-unit-types-are)
|
||||
for available units. Additionally:
|
||||
|
||||
- `sendExpiryNotice` configures whether a message is sent into a room when the connection is close to expiring.
|
||||
- `requireExpiryTime` forbids creating a webhook without a expiry time. This does not apply to existing webhooks.
|
||||
|
||||
You may set a `userIdPrefix` to create a specific user for each new webhook connection in a room. For example, a connection with a name
|
||||
like `example` for a prefix of `webhook_` will create a user called `@webhook_example:example.com`. If you enable this option,
|
||||
you need to configure the user to be part of your registration file e.g.:
|
||||
@ -117,6 +126,13 @@ can specify this either globally in your config, or on the widget with `waitForC
|
||||
If you make use of the `webhookResponse` feature, you will need to enable `waitForComplete` as otherwise hookshot will
|
||||
immeditately respond with it's default response values.
|
||||
|
||||
|
||||
#### Expiring webhooks
|
||||
|
||||
Webhooks can be configured to expire, such that beyond a certain date they will fail any incoming requests. Currently this expiry time
|
||||
is mutable, so anybody able to configure connections will be able to change the expiry date. Hookshot will send a notice to the room
|
||||
at large when the webhook has less than 3 days until it's due to expire (if `sendExpiryNotice` is set).
|
||||
|
||||
### JavaScript Transformations
|
||||
|
||||
<section class="notice">
|
||||
|
@ -48,11 +48,13 @@
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@octokit/webhooks": "^12.0.10",
|
||||
"@sentry/node": "^7.52.1",
|
||||
"@vector-im/compound-design-tokens": "^1.3.0",
|
||||
"@vector-im/compound-web": "^4.8.0",
|
||||
"@vector-im/compound-design-tokens": "^2.0.1",
|
||||
"@vector-im/compound-web": "^7.3.0",
|
||||
"ajv": "^8.11.0",
|
||||
"axios": "^1.7.4",
|
||||
"clsx": "^2.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns": "^4.1.0",
|
||||
"express": "^4.20.0",
|
||||
"figma-js": "^1.14.0",
|
||||
"helmet": "^7.1.0",
|
||||
@ -67,6 +69,7 @@
|
||||
"mime": "^4.0.1",
|
||||
"node-emoji": "^2.1.3",
|
||||
"p-queue": "^6.6.2",
|
||||
"parse-duration": "^1.1.0",
|
||||
"preact-render-to-string": "^6.3.1",
|
||||
"prom-client": "^15.1.0",
|
||||
"quickjs-emscripten": "^0.26.0",
|
||||
|
119
spec/generic-hooks.spec.ts
Normal file
119
spec/generic-hooks.spec.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import { E2ESetupTestTimeout, E2ETestEnv, E2ETestMatrixClient } from "./util/e2e-test";
|
||||
import { describe, it } from "@jest/globals";
|
||||
import { GenericHookConnection } from "../src/Connections";
|
||||
import { TextualMessageEventContent } from "matrix-bot-sdk";
|
||||
import { add } from "date-fns/add";
|
||||
|
||||
async function createInboundConnection(user: E2ETestMatrixClient, botMxid: string, roomId: string, duration?: string) {
|
||||
const join = user.waitForRoomJoin({ sender: botMxid, roomId });
|
||||
const connectionEvent = user.waitForRoomEvent({
|
||||
eventType: GenericHookConnection.CanonicalEventType,
|
||||
stateKey: 'test',
|
||||
sender: botMxid
|
||||
});
|
||||
await user.inviteUser(botMxid, roomId);
|
||||
await user.setUserPowerLevel(botMxid, roomId, 50);
|
||||
await join;
|
||||
|
||||
// Note: Here we create the DM proactively so this works across multiple
|
||||
// tests.
|
||||
// Get the DM room so we can get the token.
|
||||
const dmRoomId = await user.dms.getOrCreateDm(botMxid);
|
||||
|
||||
await user.sendText(roomId, '!hookshot webhook test' + (duration ? ` ${duration}` : ""));
|
||||
// Test the contents of this.
|
||||
await connectionEvent;
|
||||
|
||||
const msgPromise = user.waitForRoomEvent({ sender: botMxid, eventType: "m.room.message", roomId: dmRoomId });
|
||||
const { data: msgData } = await msgPromise;
|
||||
const msgContent = msgData.content as unknown as TextualMessageEventContent;
|
||||
const [_unused1, _unused2, url] = msgContent.body.split('\n');
|
||||
return url;
|
||||
}
|
||||
|
||||
describe('Inbound (Generic) Webhooks', () => {
|
||||
let testEnv: E2ETestEnv;
|
||||
|
||||
beforeAll(async () => {
|
||||
const webhooksPort = 9500 + E2ETestEnv.workerId;
|
||||
testEnv = await E2ETestEnv.createTestEnv({
|
||||
matrixLocalparts: ['user'],
|
||||
config: {
|
||||
generic: {
|
||||
enabled: true,
|
||||
// Prefer to wait for complete as it reduces the concurrency of the test.
|
||||
waitForComplete: true,
|
||||
urlPrefix: `http://localhost:${webhooksPort}`
|
||||
},
|
||||
listeners: [{
|
||||
port: webhooksPort,
|
||||
bindAddress: '0.0.0.0',
|
||||
// Bind to the SAME listener to ensure we don't have conflicts.
|
||||
resources: ['webhooks'],
|
||||
}],
|
||||
}
|
||||
});
|
||||
await testEnv.setUp();
|
||||
}, E2ESetupTestTimeout);
|
||||
|
||||
afterAll(() => {
|
||||
return testEnv?.tearDown();
|
||||
});
|
||||
|
||||
it('should be able to create a new webhook and handle an incoming request.', async () => {
|
||||
const user = testEnv.getUser('user');
|
||||
const roomId = await user.createRoom({ name: 'My Test Webhooks room'});
|
||||
const okMsg = user.waitForRoomEvent({ eventType: "m.room.message", sender: testEnv.botMxid, roomId });
|
||||
const url = await createInboundConnection(user, testEnv.botMxid, roomId);
|
||||
expect((await okMsg).data.content.body).toEqual('Room configured to bridge webhooks. See admin room for secret url.');
|
||||
|
||||
const expectedMsg = user.waitForRoomEvent({ eventType: "m.room.message", sender: testEnv.botMxid, roomId });
|
||||
const req = await fetch(url, {
|
||||
method: "PUT",
|
||||
body: "Hello world"
|
||||
});
|
||||
expect(req.status).toEqual(200);
|
||||
expect(await req.json()).toEqual({ ok: true });
|
||||
expect((await expectedMsg).data.content).toEqual({
|
||||
msgtype: 'm.notice',
|
||||
body: 'Received webhook data: Hello world',
|
||||
formatted_body: '<p>Received webhook data: Hello world</p>',
|
||||
format: 'org.matrix.custom.html',
|
||||
'uk.half-shot.hookshot.webhook_data': 'Hello world'
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to create a new expiring webhook and handle valid requests.', async () => {
|
||||
jest.useFakeTimers();
|
||||
const user = testEnv.getUser('user');
|
||||
const roomId = await user.createRoom({ name: 'My Test Webhooks room'});
|
||||
const okMsg = user.waitForRoomEvent({ eventType: "m.room.message", sender: testEnv.botMxid, roomId });
|
||||
const url = await createInboundConnection(user, testEnv.botMxid, roomId, '2h');
|
||||
expect((await okMsg).data.content.body).toEqual('Room configured to bridge webhooks. See admin room for secret url.');
|
||||
|
||||
const expectedMsg = user.waitForRoomEvent({ eventType: "m.room.message", sender: testEnv.botMxid, roomId });
|
||||
const req = await fetch(url, {
|
||||
method: "PUT",
|
||||
body: "Hello world"
|
||||
});
|
||||
expect(req.status).toEqual(200);
|
||||
expect(await req.json()).toEqual({ ok: true });
|
||||
expect((await expectedMsg).data.content).toEqual({
|
||||
msgtype: 'm.notice',
|
||||
body: 'Received webhook data: Hello world',
|
||||
formatted_body: '<p>Received webhook data: Hello world</p>',
|
||||
format: 'org.matrix.custom.html',
|
||||
'uk.half-shot.hookshot.webhook_data': 'Hello world'
|
||||
});
|
||||
jest.setSystemTime(add(new Date(), { hours: 3 }));
|
||||
const expiredReq = await fetch(url, {
|
||||
method: "PUT",
|
||||
body: "Hello world"
|
||||
});
|
||||
expect(expiredReq.status).toEqual(404);
|
||||
expect(await expiredReq.json()).toEqual({
|
||||
ok: false,
|
||||
error: "This hook has expired",
|
||||
});
|
||||
});
|
||||
});
|
@ -10,7 +10,7 @@ import { GetIssueResponse, GetIssueOpts } from "./Gitlab/Types"
|
||||
import { GithubInstance } from "./github/GithubInstance";
|
||||
import { IBridgeStorageProvider } from "./Stores/StorageProvider";
|
||||
import { IConnection, GitHubDiscussionSpace, GitHubDiscussionConnection, GitHubUserSpace, JiraProjectConnection, GitLabRepoConnection,
|
||||
GitHubIssueConnection, GitHubProjectConnection, GitHubRepoConnection, GitLabIssueConnection, FigmaFileConnection, FeedConnection, GenericHookConnection, WebhookResponse } from "./Connections";
|
||||
GitHubIssueConnection, GitHubProjectConnection, GitHubRepoConnection, GitLabIssueConnection, FigmaFileConnection, FeedConnection, GenericHookConnection } from "./Connections";
|
||||
import { IGitLabWebhookIssueStateEvent, IGitLabWebhookMREvent, IGitLabWebhookNoteEvent, IGitLabWebhookPushEvent, IGitLabWebhookReleaseEvent, IGitLabWebhookTagPushEvent, IGitLabWebhookWikiPageEvent } from "./Gitlab/WebhookTypes";
|
||||
import { JiraIssueEvent, JiraIssueUpdatedEvent, JiraVersionEvent } from "./jira/WebhookTypes";
|
||||
import { JiraOAuthResult } from "./jira/Types";
|
||||
@ -606,7 +606,7 @@ export class Bridge {
|
||||
|
||||
if (!connections.length) {
|
||||
await this.queue.push<GenericWebhookEventResult>({
|
||||
data: {notFound: true},
|
||||
data: {successful: true, notFound: true},
|
||||
sender: "Bridge",
|
||||
messageId: messageId,
|
||||
eventName: "response.generic-webhook.event",
|
||||
@ -621,21 +621,19 @@ export class Bridge {
|
||||
await c.onGenericHook(data.hookData);
|
||||
return;
|
||||
}
|
||||
let successful: boolean|null = null;
|
||||
let response: WebhookResponse|undefined;
|
||||
if (this.config.generic?.waitForComplete || c.waitForComplete) {
|
||||
const result = await c.onGenericHook(data.hookData);
|
||||
successful = result.successful;
|
||||
response = result.response;
|
||||
await this.queue.push<GenericWebhookEventResult>({
|
||||
data: {successful, response},
|
||||
data: result,
|
||||
sender: "Bridge",
|
||||
messageId,
|
||||
eventName: "response.generic-webhook.event",
|
||||
});
|
||||
} else {
|
||||
await this.queue.push<GenericWebhookEventResult>({
|
||||
data: {},
|
||||
data: {
|
||||
successful: null,
|
||||
},
|
||||
sender: "Bridge",
|
||||
messageId,
|
||||
eventName: "response.generic-webhook.event",
|
||||
|
@ -8,9 +8,13 @@ import { Appservice, Intent, StateEvent } from "matrix-bot-sdk";
|
||||
import { ApiError, ErrCode } from "../api";
|
||||
import { BaseConnection } from "./BaseConnection";
|
||||
import { GetConnectionsResponseItem } from "../provisioning/api";
|
||||
import { BridgeConfigGenericWebhooks } from "../config/Config";
|
||||
import { BridgeConfigGenericWebhooks } from "../config/sections";
|
||||
import { ensureUserIsInRoom } from "../IntentUtils";
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { GenericWebhookEventResult } from "../generic/types";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
import { IBridgeStorageProvider } from "../Stores/StorageProvider";
|
||||
import { formatDuration, isMatch, millisecondsToHours } from "date-fns";
|
||||
|
||||
export interface GenericHookConnectionState extends IConnectionState {
|
||||
/**
|
||||
@ -21,11 +25,17 @@ export interface GenericHookConnectionState extends IConnectionState {
|
||||
* The name given in the provisioning UI and displaynames.
|
||||
*/
|
||||
name: string;
|
||||
transformationFunction: string|undefined;
|
||||
transformationFunction?: string;
|
||||
/**
|
||||
* Should the webhook only respond on completion.
|
||||
*/
|
||||
waitForComplete: boolean|undefined;
|
||||
waitForComplete?: boolean|undefined;
|
||||
|
||||
/**
|
||||
* If the webhook has an expriation date, then the date at which the webhook is no longer value
|
||||
* (in UTC) time.
|
||||
*/
|
||||
expirationDate?: string;
|
||||
}
|
||||
|
||||
export interface GenericHookSecrets {
|
||||
@ -37,6 +47,10 @@ export interface GenericHookSecrets {
|
||||
* The hookId of the webhook.
|
||||
*/
|
||||
hookId: string;
|
||||
/**
|
||||
* How long remains until the webhook expires.
|
||||
*/
|
||||
timeRemainingMs?: number
|
||||
}
|
||||
|
||||
export type GenericHookResponseItem = GetConnectionsResponseItem<GenericHookConnectionState, GenericHookSecrets>;
|
||||
@ -64,6 +78,14 @@ interface WebhookTransformationResult {
|
||||
webhookResponse?: WebhookResponse;
|
||||
}
|
||||
|
||||
export interface GenericHookServiceConfig {
|
||||
userIdPrefix?: string;
|
||||
allowJsTransformationFunctions?: boolean,
|
||||
waitForComplete?: boolean,
|
||||
maxExpiryTime?: number,
|
||||
requireExpiryTime: boolean,
|
||||
}
|
||||
|
||||
const log = new Logger("GenericHookConnection");
|
||||
const md = new markdownit();
|
||||
|
||||
@ -71,6 +93,12 @@ const TRANSFORMATION_TIMEOUT_MS = 500;
|
||||
const SANITIZE_MAX_DEPTH = 10;
|
||||
const SANITIZE_MAX_BREADTH = 50;
|
||||
|
||||
const WARN_AT_EXPIRY_MS = 3 * 24 * 60 * 60 * 1000;
|
||||
const MIN_EXPIRY_MS = 60 * 60 * 1000;
|
||||
const CHECK_EXPIRY_MS = 15 * 60 * 1000;
|
||||
|
||||
const EXPIRY_NOTICE_MESSAGE = "The webhook **%NAME** will be expiring in %TIME."
|
||||
|
||||
/**
|
||||
* Handles rooms connected to a generic webhook.
|
||||
*/
|
||||
@ -123,8 +151,8 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
return obj;
|
||||
}
|
||||
|
||||
static validateState(state: Record<string, unknown>): GenericHookConnectionState {
|
||||
const {name, transformationFunction, waitForComplete} = state;
|
||||
static validateState(state: Partial<Record<keyof GenericHookConnectionState, unknown>>): GenericHookConnectionState {
|
||||
const {name, transformationFunction, waitForComplete, expirationDate: expirationDateStr} = state;
|
||||
if (!name) {
|
||||
throw new ApiError('Missing name', ErrCode.BadValue);
|
||||
}
|
||||
@ -143,14 +171,26 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
throw new ApiError('Transformation functions must be a string', ErrCode.BadValue);
|
||||
}
|
||||
}
|
||||
let expirationDate: string|undefined;
|
||||
if (expirationDateStr != undefined) {
|
||||
if (typeof expirationDateStr !== "string" || !expirationDateStr) {
|
||||
throw new ApiError("'expirationDate' must be a non-empty string", ErrCode.BadValue);
|
||||
}
|
||||
if (!isMatch(expirationDateStr, "yyyy-MM-dd'T'HH:mm:ss.SSSXX")) {
|
||||
throw new ApiError("'expirationDate' must be a valid date", ErrCode.BadValue);
|
||||
}
|
||||
expirationDate = expirationDateStr;
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
transformationFunction: transformationFunction || undefined,
|
||||
waitForComplete,
|
||||
expirationDate,
|
||||
};
|
||||
}
|
||||
|
||||
static async createConnectionForState(roomId: string, event: StateEvent<Record<string, unknown>>, {as, intent, config, messageClient}: InstantiateConnectionOpts) {
|
||||
static async createConnectionForState(roomId: string, event: StateEvent<Record<string, unknown>>, {as, intent, config, messageClient, storage}: InstantiateConnectionOpts) {
|
||||
if (!config.generic) {
|
||||
throw Error('Generic webhooks are not configured');
|
||||
}
|
||||
@ -162,6 +202,10 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
if (!hookId) {
|
||||
hookId = randomUUID();
|
||||
log.warn(`hookId for ${roomId} not set in accountData, setting to ${hookId}`);
|
||||
// If this is a new hook...
|
||||
if (config.generic.requireExpiryTime && !state.expirationDate) {
|
||||
throw new Error('Expiration date must be set');
|
||||
}
|
||||
await GenericHookConnection.ensureRoomAccountData(roomId, intent, hookId, event.stateKey);
|
||||
}
|
||||
|
||||
@ -174,18 +218,41 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
config.generic,
|
||||
as,
|
||||
intent,
|
||||
storage,
|
||||
);
|
||||
}
|
||||
|
||||
static async provisionConnection(roomId: string, userId: string, data: Record<string, unknown> = {}, {as, intent, config, messageClient}: ProvisionConnectionOpts) {
|
||||
static async provisionConnection(roomId: string, userId: string, data: Partial<Record<keyof GenericHookConnectionState, unknown>> = {}, {as, intent, config, messageClient, storage}: ProvisionConnectionOpts) {
|
||||
if (!config.generic) {
|
||||
throw Error('Generic Webhooks are not configured');
|
||||
}
|
||||
const hookId = randomUUID();
|
||||
const validState = GenericHookConnection.validateState(data);
|
||||
if (validState.expirationDate) {
|
||||
const durationRemaining = new Date(validState.expirationDate).getTime() - Date.now();
|
||||
if (config.generic.maxExpiryTimeMs) {
|
||||
if (durationRemaining > config.generic.maxExpiryTimeMs) {
|
||||
throw new ApiError('Expiration date cannot exceed the configured max expiry time', ErrCode.BadValue);
|
||||
}
|
||||
}
|
||||
if (durationRemaining < MIN_EXPIRY_MS) {
|
||||
// If the webhook is actually created with a shorter expiry time than
|
||||
// our warning period, then just mark it as warned.
|
||||
throw new ApiError('Expiration date must at least be a hour in the future', ErrCode.BadValue);
|
||||
}
|
||||
if (durationRemaining < WARN_AT_EXPIRY_MS) {
|
||||
// If the webhook is actually created with a shorter expiry time than
|
||||
// our warning period, then just mark it as warned.
|
||||
await storage.setHasGenericHookWarnedExpiry(hookId, true);
|
||||
}
|
||||
} else if (config.generic.requireExpiryTime) {
|
||||
throw new ApiError('Expiration date must be set', ErrCode.BadValue);
|
||||
}
|
||||
|
||||
|
||||
await GenericHookConnection.ensureRoomAccountData(roomId, intent, hookId, validState.name);
|
||||
await intent.underlyingClient.sendStateEvent(roomId, this.CanonicalEventType, validState.name, validState);
|
||||
const connection = new GenericHookConnection(roomId, validState, hookId, validState.name, messageClient, config.generic, as, intent);
|
||||
const connection = new GenericHookConnection(roomId, validState, hookId, validState.name, messageClient, config.generic, as, intent, storage);
|
||||
return {
|
||||
connection,
|
||||
stateEventContent: validState,
|
||||
@ -218,6 +285,8 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
|
||||
private transformationFunction?: string;
|
||||
private cachedDisplayname?: string;
|
||||
private warnOnExpiryInterval?: NodeJS.Timeout;
|
||||
|
||||
/**
|
||||
* @param state Should be a pre-validated state object returned by {@link validateState}
|
||||
*/
|
||||
@ -230,11 +299,19 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
private readonly config: BridgeConfigGenericWebhooks,
|
||||
private readonly as: Appservice,
|
||||
private readonly intent: Intent,
|
||||
private readonly storage: IBridgeStorageProvider,
|
||||
) {
|
||||
super(roomId, stateKey, GenericHookConnection.CanonicalEventType);
|
||||
if (state.transformationFunction && GenericHookConnection.quickModule) {
|
||||
this.transformationFunction = state.transformationFunction;
|
||||
}
|
||||
this.handleExpiryTimeUpdate(false).catch(ex => {
|
||||
log.warn("Failed to configure expiry time warning for hook", ex);
|
||||
});
|
||||
}
|
||||
|
||||
public get expiresAt(): Date|undefined {
|
||||
return this.state.expirationDate ? new Date(this.state.expirationDate) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -313,7 +390,49 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
} else {
|
||||
this.transformationFunction = undefined;
|
||||
}
|
||||
|
||||
const prevDate = this.state.expirationDate;
|
||||
this.state = validatedConfig;
|
||||
if (prevDate !== validatedConfig.expirationDate) {
|
||||
await this.handleExpiryTimeUpdate(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the expiry time has been updated for the connection. If the connection
|
||||
* no longer has an expiry time. This voids the interval.
|
||||
* @returns
|
||||
*/
|
||||
private async handleExpiryTimeUpdate(shouldWrite: boolean) {
|
||||
if (!this.config.sendExpiryNotice) {
|
||||
return;
|
||||
}
|
||||
if (this.warnOnExpiryInterval) {
|
||||
clearInterval(this.warnOnExpiryInterval);
|
||||
this.warnOnExpiryInterval = undefined;
|
||||
}
|
||||
if (!this.state.expirationDate) {
|
||||
return;
|
||||
}
|
||||
|
||||
const durationRemaining = new Date(this.state.expirationDate).getTime() - Date.now();
|
||||
if (durationRemaining < WARN_AT_EXPIRY_MS) {
|
||||
// If the webhook is actually created with a shorter expiry time than
|
||||
// our warning period, then just mark it as warned.
|
||||
if (shouldWrite) {
|
||||
await this.storage.setHasGenericHookWarnedExpiry(this.hookId, true);
|
||||
}
|
||||
} else {
|
||||
const fuzzCheckTimeMs = Math.round((Math.random() * CHECK_EXPIRY_MS));
|
||||
this.warnOnExpiryInterval = setInterval(() => {
|
||||
this.checkAndWarnExpiry().catch(ex => {
|
||||
log.warn("Failed to check expiry time for hook", ex);
|
||||
})
|
||||
}, CHECK_EXPIRY_MS + fuzzCheckTimeMs);
|
||||
if (shouldWrite) {
|
||||
await this.storage.setHasGenericHookWarnedExpiry(this.hookId, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public transformHookData(data: unknown): {plain: string, html?: string} {
|
||||
@ -424,8 +543,18 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
* @param data Structured data. This may either be a string, or an object.
|
||||
* @returns `true` if the webhook completed, or `false` if it failed to complete
|
||||
*/
|
||||
public async onGenericHook(data: unknown): Promise<{successful: boolean, response?: WebhookResponse}> {
|
||||
public async onGenericHook(data: unknown): Promise<GenericWebhookEventResult> {
|
||||
log.info(`onGenericHook ${this.roomId} ${this.hookId}`);
|
||||
|
||||
if (this.expiresAt && new Date() >= this.expiresAt) {
|
||||
log.warn("Ignoring incoming webhook. This hook has expired");
|
||||
return {
|
||||
successful: false,
|
||||
statusCode: StatusCodes.NOT_FOUND,
|
||||
error: 'This hook has expired',
|
||||
};
|
||||
}
|
||||
|
||||
let content: {plain: string, html?: string, msgtype?: string}|undefined;
|
||||
let webhookResponse: WebhookResponse|undefined;
|
||||
let successful = true;
|
||||
@ -487,16 +616,19 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
transformationFunction: this.state.transformationFunction,
|
||||
waitForComplete: this.waitForComplete,
|
||||
name: this.state.name,
|
||||
expirationDate: this.state.expirationDate,
|
||||
},
|
||||
...(showSecrets ? { secrets: {
|
||||
url: new URL(this.hookId, this.config.parsedUrlPrefix),
|
||||
hookId: this.hookId,
|
||||
timeRemainingMs: this.expiresAt ? this.expiresAt.getTime() - Date.now() : undefined,
|
||||
} satisfies GenericHookSecrets} : undefined)
|
||||
}
|
||||
}
|
||||
|
||||
public async onRemove() {
|
||||
log.info(`Removing ${this.toString()} for ${this.roomId}`);
|
||||
clearInterval(this.warnOnExpiryInterval);
|
||||
// Do a sanity check that the event exists.
|
||||
try {
|
||||
await this.intent.underlyingClient.getRoomStateEvent(this.roomId, GenericHookConnection.CanonicalEventType, this.stateKey);
|
||||
@ -508,8 +640,9 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
await GenericHookConnection.ensureRoomAccountData(this.roomId, this.intent, this.hookId, this.stateKey, true);
|
||||
}
|
||||
|
||||
public async provisionerUpdateConfig(userId: string, config: Record<string, unknown>) {
|
||||
public async provisionerUpdateConfig(_userId: string, config: Record<keyof GenericHookConnectionState, unknown>) {
|
||||
// Apply previous state to the current config, as provisioners might not return "unknown" keys.
|
||||
config.expirationDate = config.expirationDate ?? undefined;
|
||||
config = { ...this.state, ...config };
|
||||
const validatedConfig = GenericHookConnection.validateState(config);
|
||||
await this.intent.underlyingClient.sendStateEvent(this.roomId, GenericHookConnection.CanonicalEventType, this.stateKey,
|
||||
@ -521,6 +654,35 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
this.state = validatedConfig;
|
||||
}
|
||||
|
||||
private async checkAndWarnExpiry() {
|
||||
const remainingMs = this.expiresAt ? this.expiresAt.getTime() - Date.now() : undefined;
|
||||
if (!remainingMs) {
|
||||
return;
|
||||
}
|
||||
if (remainingMs < CHECK_EXPIRY_MS) {
|
||||
// Nearly expired
|
||||
return;
|
||||
}
|
||||
if (remainingMs > WARN_AT_EXPIRY_MS) {
|
||||
return;
|
||||
}
|
||||
if (await this.storage.getHasGenericHookWarnedExpiry(this.hookId)) {
|
||||
return;
|
||||
}
|
||||
// Warn
|
||||
const markdownStr = EXPIRY_NOTICE_MESSAGE.replace('%NAME', this.state.name).replace('%TIME', formatDuration({
|
||||
hours: millisecondsToHours(remainingMs)
|
||||
}));
|
||||
await this.messageClient.sendMatrixMessage(this.roomId, {
|
||||
msgtype: "m.notice",
|
||||
body: markdownStr,
|
||||
// render can output redundant trailing newlines, so trim it.
|
||||
formatted_body: md.render(markdownStr).trim(),
|
||||
format: "org.matrix.custom.html",
|
||||
}, 'm.room.message', this.getUserId());
|
||||
await this.storage.setHasGenericHookWarnedExpiry(this.hookId, true);
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `GenericHookConnection ${this.hookId}`;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import { IConnection, IConnectionState, ProvisionConnectionOpts } from "./IConne
|
||||
import { ApiError, Logger } from "matrix-appservice-bridge";
|
||||
import { Intent } from "matrix-bot-sdk";
|
||||
import YAML from 'yaml';
|
||||
import parseDuration from 'parse-duration';
|
||||
import { HoundConnection } from "./HoundConnection";
|
||||
const md = new markdown();
|
||||
const log = new Logger("SetupConnection");
|
||||
@ -209,18 +210,27 @@ export class SetupConnection extends CommandConnection {
|
||||
return this.client.sendHtmlNotice(this.roomId, md.renderInline(`Room no longer bridged to Jira project \`${safeUrl}\`.`));
|
||||
}
|
||||
|
||||
@botCommand("webhook", { help: "Create an inbound webhook.", requiredArgs: ["name"], includeUserId: true, category: GenericHookConnection.ServiceCategory})
|
||||
public async onWebhook(userId: string, name: string) {
|
||||
@botCommand("webhook", { help: "Create an inbound webhook. The liveDuration must be specified as a duration string (e.g. 30d).", requiredArgs: ["name"], includeUserId: true, optionalArgs: ['liveDuration'], category: GenericHookConnection.ServiceCategory})
|
||||
public async onWebhook(userId: string, name: string, liveDuration?: string) {
|
||||
if (!this.config.generic?.enabled) {
|
||||
throw new CommandError("not-configured", "The bridge is not configured to support webhooks.");
|
||||
}
|
||||
|
||||
let expirationDate: string|undefined = undefined;
|
||||
if (liveDuration) {
|
||||
const expirationDuration = parseDuration(liveDuration);
|
||||
if (!expirationDuration) {
|
||||
throw new CommandError("Bad webhook duration", "A webhook name must be between 3-64 characters.");
|
||||
}
|
||||
expirationDate = new Date(expirationDuration + Date.now()).toISOString();
|
||||
}
|
||||
|
||||
await this.checkUserPermissions(userId, "webhooks", GitHubRepoConnection.CanonicalEventType);
|
||||
|
||||
if (!name || name.length < 3 || name.length > 64) {
|
||||
throw new CommandError("Bad webhook name", "A webhook name must be between 3-64 characters.");
|
||||
}
|
||||
const c = await GenericHookConnection.provisionConnection(this.roomId, userId, {name}, this.provisionOpts);
|
||||
const c = await GenericHookConnection.provisionConnection(this.roomId, userId, {name, expirationDate}, this.provisionOpts);
|
||||
this.pushConnections(c.connection);
|
||||
const url = new URL(c.connection.hookId, this.config.generic.parsedUrlPrefix);
|
||||
const adminRoom = await this.getOrCreateAdminRoom(this.intent, userId);
|
||||
|
@ -34,7 +34,7 @@ export class MatrixSender {
|
||||
try {
|
||||
await this.sendMatrixMessage(msg.messageId || randomUUID(), msg.data);
|
||||
} catch (ex) {
|
||||
log.error(`Failed to send message (${msg.data.roomId}, ${msg.data.sender}, ${msg.data.type})`);
|
||||
log.error(`Failed to send message (${msg.data.roomId}, ${msg.data.sender}, ${msg.data.type})`, ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ export class MemoryStorageProvider extends MSP implements IBridgeStorageProvider
|
||||
private feedGuids = new Map<string, Array<string>>();
|
||||
private houndActivityIds = new Map<string, Array<string>>();
|
||||
private houndActivityIdToEvent = new Map<string, string>();
|
||||
private hasGenericHookWarnedExpiry = new Set<string>();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -139,4 +140,12 @@ export class MemoryStorageProvider extends MSP implements IBridgeStorageProvider
|
||||
public async getHoundActivity(challengeId: string, activityId: string): Promise<string|null> {
|
||||
return this.houndActivityIdToEvent.get(`${challengeId}.${activityId}`) ?? null;
|
||||
}
|
||||
|
||||
public async getHasGenericHookWarnedExpiry(hookId: string): Promise<boolean> {
|
||||
return this.hasGenericHookWarnedExpiry.has(hookId);
|
||||
}
|
||||
|
||||
public async setHasGenericHookWarnedExpiry(hookId: string, hasWarned: boolean): Promise<void> {
|
||||
this.hasGenericHookWarnedExpiry[hasWarned ? "add" : "delete"](hookId);
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ const FEED_GUIDS = "feeds.guids.";
|
||||
const HOUND_GUIDS = "hound.guids.";
|
||||
const HOUND_EVENTS = "hound.events.";
|
||||
|
||||
const GENERIC_HOOK_HAS_WARNED = "generichook.haswarned";
|
||||
|
||||
const log = new Logger("RedisASProvider");
|
||||
|
||||
export class RedisStorageContextualProvider implements IStorageProvider {
|
||||
@ -284,4 +286,12 @@ export class RedisStorageProvider extends RedisStorageContextualProvider impleme
|
||||
public async getHoundActivity(challengeId: string, activityId: string): Promise<string|null> {
|
||||
return this.redis.get(`${HOUND_EVENTS}${challengeId}.${activityId}`);
|
||||
}
|
||||
|
||||
public async getHasGenericHookWarnedExpiry(hookId: string): Promise<boolean> {
|
||||
return await this.redis.sismember(GENERIC_HOOK_HAS_WARNED, hookId) === 1;
|
||||
}
|
||||
|
||||
public async setHasGenericHookWarnedExpiry(hookId: string, hasWarned: boolean): Promise<void> {
|
||||
await this.redis[hasWarned ? "sadd" : "srem"](GENERIC_HOOK_HAS_WARNED, hookId);
|
||||
}
|
||||
}
|
||||
|
@ -36,4 +36,7 @@ export interface IBridgeStorageProvider extends IAppserviceStorageProvider, ISto
|
||||
hasSeenHoundActivity(challengeId: string, ...activityHashes: string[]): Promise<string[]>;
|
||||
storeHoundActivityEvent(challengeId: string, activityId: string, eventId: string): Promise<void>;
|
||||
getHoundActivity(challengeId: string, activityId: string): Promise<string|null>;
|
||||
|
||||
getHasGenericHookWarnedExpiry(hookId: string): Promise<boolean>;
|
||||
setHasGenericHookWarnedExpiry(hookId: string, hasWarned: boolean): Promise<void>;
|
||||
}
|
@ -108,7 +108,7 @@ export class BridgeWidgetApi extends ProvisioningApi {
|
||||
});
|
||||
}
|
||||
|
||||
private async getServiceConfig(req: ProvisioningRequest, res: Response<Record<string, unknown>>) {
|
||||
private async getServiceConfig(req: ProvisioningRequest, res: Response<object>) {
|
||||
// GitHub is a special case because it depends on live config.
|
||||
if (req.params.service === 'github') {
|
||||
res.send(this.config.github?.publicConfig(this.github));
|
||||
|
@ -9,12 +9,14 @@ import { BridgeConfigActorPermission, BridgePermissions } from "../libRs";
|
||||
import { ConfigError } from "../errors";
|
||||
import { ApiError, ErrCode } from "../api";
|
||||
import { GithubInstance, GITHUB_CLOUD_URL } from "../github/GithubInstance";
|
||||
import { DefaultDisallowedIpRanges, Logger } from "matrix-appservice-bridge";
|
||||
import { Logger } from "matrix-appservice-bridge";
|
||||
import { BridgeConfigCache } from "./sections/cache";
|
||||
import { BridgeConfigQueue } from "./sections";
|
||||
import { BridgeConfigGenericWebhooks, BridgeConfigQueue, BridgeGenericWebhooksConfigYAML } from "./sections";
|
||||
import { GenericHookServiceConfig } from "../Connections";
|
||||
|
||||
const log = new Logger("Config");
|
||||
|
||||
|
||||
function makePrefixedUrl(urlString: string): URL {
|
||||
return new URL(urlString.endsWith("/") ? urlString : urlString + "/");
|
||||
}
|
||||
@ -288,56 +290,6 @@ export interface BridgeConfigFigma {
|
||||
}};
|
||||
}
|
||||
|
||||
export interface BridgeGenericWebhooksConfigYAML {
|
||||
enabled: boolean;
|
||||
urlPrefix: string;
|
||||
userIdPrefix?: string;
|
||||
allowJsTransformationFunctions?: boolean;
|
||||
waitForComplete?: boolean;
|
||||
enableHttpGet?: boolean;
|
||||
outbound?: boolean;
|
||||
disallowedIpRanges?: string[];
|
||||
}
|
||||
|
||||
export class BridgeConfigGenericWebhooks {
|
||||
public readonly enabled: boolean;
|
||||
public readonly outbound: boolean;
|
||||
|
||||
@hideKey()
|
||||
public readonly parsedUrlPrefix: URL;
|
||||
public readonly urlPrefix: () => string;
|
||||
|
||||
public readonly userIdPrefix?: string;
|
||||
public readonly allowJsTransformationFunctions?: boolean;
|
||||
public readonly waitForComplete?: boolean;
|
||||
public readonly enableHttpGet: boolean;
|
||||
constructor(yaml: BridgeGenericWebhooksConfigYAML) {
|
||||
this.enabled = yaml.enabled || false;
|
||||
this.outbound = yaml.outbound || false;
|
||||
this.enableHttpGet = yaml.enableHttpGet || false;
|
||||
try {
|
||||
this.parsedUrlPrefix = makePrefixedUrl(yaml.urlPrefix);
|
||||
this.urlPrefix = () => { return this.parsedUrlPrefix.href; }
|
||||
} catch (err) {
|
||||
throw new ConfigError("generic.urlPrefix", "is not defined or not a valid URL");
|
||||
}
|
||||
this.userIdPrefix = yaml.userIdPrefix;
|
||||
this.allowJsTransformationFunctions = yaml.allowJsTransformationFunctions;
|
||||
this.waitForComplete = yaml.waitForComplete;
|
||||
}
|
||||
|
||||
@hideKey()
|
||||
public get publicConfig() {
|
||||
return {
|
||||
userIdPrefix: this.userIdPrefix,
|
||||
allowJsTransformationFunctions: this.allowJsTransformationFunctions,
|
||||
waitForComplete: this.waitForComplete,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
interface BridgeWidgetConfigYAML {
|
||||
publicUrl: string;
|
||||
/**
|
||||
@ -779,8 +731,8 @@ remove "useLegacySledStore" from your configuration file, and restart Hookshot.
|
||||
return services;
|
||||
}
|
||||
|
||||
public getPublicConfigForService(serviceName: string): Record<string, unknown> {
|
||||
let config: undefined|Record<string, unknown>;
|
||||
public getPublicConfigForService(serviceName: string): Record<string, unknown>|GenericHookServiceConfig {
|
||||
let config: undefined|Record<string, unknown>|GenericHookServiceConfig;
|
||||
switch (serviceName) {
|
||||
case "feeds":
|
||||
config = this.feeds?.publicConfig;
|
||||
|
@ -109,6 +109,8 @@ export const DefaultConfigRoot: BridgeConfigRoot = {
|
||||
urlPrefix: `${hookshotWebhooksUrl}/webhook/`,
|
||||
userIdPrefix: "_webhooks_",
|
||||
waitForComplete: false,
|
||||
maxExpiryTime: "30d",
|
||||
sendExpiryNotice: false,
|
||||
},
|
||||
figma: {
|
||||
publicUrl: `${hookshotWebhooksUrl}/hookshot/`,
|
||||
|
74
src/config/sections/generichooks.ts
Normal file
74
src/config/sections/generichooks.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { GenericHookServiceConfig } from "../../Connections";
|
||||
import { ConfigError } from "../../errors";
|
||||
import { hideKey } from "../Decorators";
|
||||
import parseDuration from "parse-duration";
|
||||
|
||||
function makePrefixedUrl(urlString: string): URL {
|
||||
return new URL(urlString.endsWith("/") ? urlString : urlString + "/");
|
||||
}
|
||||
|
||||
export interface BridgeGenericWebhooksConfigYAML {
|
||||
enabled: boolean;
|
||||
urlPrefix: string;
|
||||
userIdPrefix?: string;
|
||||
allowJsTransformationFunctions?: boolean;
|
||||
waitForComplete?: boolean;
|
||||
enableHttpGet?: boolean;
|
||||
outbound?: boolean;
|
||||
disallowedIpRanges?: string[];
|
||||
maxExpiryTime?: string;
|
||||
sendExpiryNotice?: boolean;
|
||||
requireExpiryTime?: boolean;
|
||||
}
|
||||
|
||||
export class BridgeConfigGenericWebhooks {
|
||||
public readonly enabled: boolean;
|
||||
public readonly outbound: boolean;
|
||||
|
||||
@hideKey()
|
||||
public readonly parsedUrlPrefix: URL;
|
||||
public readonly urlPrefix: () => string;
|
||||
|
||||
public readonly userIdPrefix?: string;
|
||||
public readonly allowJsTransformationFunctions?: boolean;
|
||||
public readonly waitForComplete?: boolean;
|
||||
public readonly enableHttpGet: boolean;
|
||||
|
||||
@hideKey()
|
||||
public readonly maxExpiryTimeMs?: number;
|
||||
public readonly sendExpiryNotice: boolean;
|
||||
public readonly requireExpiryTime: boolean;
|
||||
// Public facing value for config generator
|
||||
public readonly maxExpiryTime?: string;
|
||||
|
||||
constructor(yaml: BridgeGenericWebhooksConfigYAML) {
|
||||
this.enabled = yaml.enabled || false;
|
||||
this.outbound = yaml.outbound || false;
|
||||
this.enableHttpGet = yaml.enableHttpGet || false;
|
||||
this.sendExpiryNotice = yaml.sendExpiryNotice || false;
|
||||
this.requireExpiryTime = yaml.requireExpiryTime || false;
|
||||
try {
|
||||
this.parsedUrlPrefix = makePrefixedUrl(yaml.urlPrefix);
|
||||
this.urlPrefix = () => { return this.parsedUrlPrefix.href; }
|
||||
} catch (err) {
|
||||
throw new ConfigError("generic.urlPrefix", "is not defined or not a valid URL");
|
||||
}
|
||||
this.userIdPrefix = yaml.userIdPrefix;
|
||||
this.allowJsTransformationFunctions = yaml.allowJsTransformationFunctions;
|
||||
this.waitForComplete = yaml.waitForComplete;
|
||||
this.maxExpiryTimeMs = yaml.maxExpiryTime ? parseDuration(yaml.maxExpiryTime) : undefined;
|
||||
this.maxExpiryTime = yaml.maxExpiryTime;
|
||||
}
|
||||
|
||||
@hideKey()
|
||||
public get publicConfig(): GenericHookServiceConfig {
|
||||
return {
|
||||
userIdPrefix: this.userIdPrefix,
|
||||
allowJsTransformationFunctions: this.allowJsTransformationFunctions,
|
||||
waitForComplete: this.waitForComplete,
|
||||
maxExpiryTime: this.maxExpiryTimeMs,
|
||||
requireExpiryTime: this.requireExpiryTime,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
export * from "./cache";
|
||||
export * from "./queue";
|
||||
export * from "./generichooks";
|
@ -4,7 +4,8 @@ import { Logger } from "matrix-appservice-bridge";
|
||||
import { ApiError, ErrCode } from "../api";
|
||||
import { GenericWebhookEvent, GenericWebhookEventResult } from "./types";
|
||||
import * as xml from "xml2js";
|
||||
import helmet, { crossOriginOpenerPolicy } from "helmet";
|
||||
import helmet from "helmet";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
|
||||
const WEBHOOK_RESPONSE_TIMEOUT = 5000;
|
||||
|
||||
@ -42,21 +43,21 @@ export class GenericWebhooksRouter {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
res.status(404).send({ok: false, error: "Webhook not found"});
|
||||
res.status(StatusCodes.NOT_FOUND).send({ok: false, error: "Webhook not found"});
|
||||
} else if (response.successful) {
|
||||
const body = response.response?.body ?? {ok: true};
|
||||
if (response.response?.contentType) {
|
||||
res.contentType(response.response.contentType);
|
||||
}
|
||||
res.status(response.response?.statusCode ?? 200).send(body);
|
||||
res.status(response.response?.statusCode ?? StatusCodes.OK).send(body);
|
||||
} else if (response.successful === false) {
|
||||
res.status(500).send({ok: false, error: "Failed to process webhook"});
|
||||
res.status(response.statusCode ?? StatusCodes.INTERNAL_SERVER_ERROR).send({ok: false, error: response.error || "Failed to process webhook"});
|
||||
} else {
|
||||
res.status(202).send({ok: true});
|
||||
res.status(StatusCodes.ACCEPTED).send({ok: true});
|
||||
}
|
||||
}).catch((err) => {
|
||||
log.error(`Failed to emit payload: ${err}`);
|
||||
res.status(500).send({ok: false, error: "Failed to handle webhook"});
|
||||
res.status(StatusCodes.INTERNAL_SERVER_ERROR).send({ok: false, error: "Failed to handle webhook"});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,18 @@ export interface GenericWebhookEvent {
|
||||
hookId: string;
|
||||
}
|
||||
|
||||
export interface GenericWebhookEventResult {
|
||||
successful?: boolean|null;
|
||||
|
||||
export type GenericWebhookEventResult = GenericWebhookEventResultSuccess | GenericWebhookEventResultFailure;
|
||||
|
||||
|
||||
export interface GenericWebhookEventResultSuccess {
|
||||
successful: true|null;
|
||||
response?: WebhookResponse,
|
||||
notFound?: boolean;
|
||||
}
|
||||
export interface GenericWebhookEventResultFailure {
|
||||
successful: false;
|
||||
statusCode?: number;
|
||||
error?: string;
|
||||
notFound?: boolean;
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { expect } from "chai";
|
||||
import { MatrixError } from "matrix-bot-sdk";
|
||||
import { BridgeConfigGenericWebhooks, BridgeGenericWebhooksConfigYAML } from "../../src/config/Config";
|
||||
import { assert, expect } from "chai";
|
||||
import { Appservice, Intent, MatrixError } from "matrix-bot-sdk";
|
||||
import { BridgeConfigGenericWebhooks, BridgeGenericWebhooksConfigYAML } from "../../src/config/sections";
|
||||
import { GenericHookConnection, GenericHookConnectionState } from "../../src/Connections/GenericHook";
|
||||
import { MessageSenderClient, IMatrixSendMessage } from "../../src/MatrixSender";
|
||||
import { LocalMQ } from "../../src/MessageQueue/LocalMQ";
|
||||
import { AppserviceMock } from "../utils/AppserviceMock";
|
||||
import { MemoryStorageProvider } from "../../src/Stores/MemoryStorageProvider";
|
||||
import { BridgeConfig } from "../../src/config/Config";
|
||||
import { ProvisionConnectionOpts } from "../../src/Connections";
|
||||
import { add } from "date-fns";
|
||||
|
||||
const ROOM_ID = "!foo:bar";
|
||||
|
||||
@ -31,12 +35,15 @@ async function testSimpleWebhook(connection: GenericHookConnection, mq: LocalMQ,
|
||||
});
|
||||
}
|
||||
|
||||
const ConfigDefaults = {enabled: true, urlPrefix: "https://example.com/webhookurl"};
|
||||
|
||||
function createGenericHook(
|
||||
state: Partial<GenericHookConnectionState> = { },
|
||||
config: BridgeGenericWebhooksConfigYAML = { enabled: true, urlPrefix: "https://example.com/webhookurl"}
|
||||
) {
|
||||
config: Partial<BridgeGenericWebhooksConfigYAML> = { }
|
||||
): [GenericHookConnection, LocalMQ, Appservice, Intent] {
|
||||
const mq = new LocalMQ();
|
||||
mq.subscribe('*');
|
||||
const storage = new MemoryStorageProvider();
|
||||
const messageClient = new MessageSenderClient(mq);
|
||||
const as = AppserviceMock.create();
|
||||
const intent = as.getIntentForUserId('@webhooks:example.test');
|
||||
@ -45,7 +52,10 @@ function createGenericHook(
|
||||
transformationFunction: undefined,
|
||||
waitForComplete: undefined,
|
||||
...state,
|
||||
}, "foobar", "foobar", messageClient, new BridgeConfigGenericWebhooks(config), as, intent);
|
||||
}, "foobar", "foobar", messageClient, new BridgeConfigGenericWebhooks({
|
||||
...ConfigDefaults,
|
||||
...config,
|
||||
}), as, intent, storage);
|
||||
return [connection, mq, as, intent];
|
||||
}
|
||||
|
||||
@ -162,8 +172,6 @@ describe("GenericHookConnection", () => {
|
||||
it("will handle a hook event with a v1 transformation function", async () => {
|
||||
const webhookData = {question: 'What is the meaning of life?', answer: 42};
|
||||
const [connection, mq] = createGenericHook({name: 'test', transformationFunction: V1TFFunction}, {
|
||||
enabled: true,
|
||||
urlPrefix: "https://example.com/webhookurl",
|
||||
allowJsTransformationFunctions: true,
|
||||
}
|
||||
);
|
||||
@ -185,8 +193,6 @@ describe("GenericHookConnection", () => {
|
||||
it("will handle a hook event with a v2 transformation function", async () => {
|
||||
const webhookData = {question: 'What is the meaning of life?', answer: 42};
|
||||
const [connection, mq] = createGenericHook({name: 'test', transformationFunction: V2TFFunction}, {
|
||||
enabled: true,
|
||||
urlPrefix: "https://example.com/webhookurl",
|
||||
allowJsTransformationFunctions: true,
|
||||
}
|
||||
);
|
||||
@ -208,8 +214,6 @@ describe("GenericHookConnection", () => {
|
||||
it("will handle a hook event with a top-level return", async () => {
|
||||
const webhookData = {question: 'What is the meaning of life?', answer: 42};
|
||||
const [connection, mq] = createGenericHook({name: 'test', transformationFunction: V2TFFunctionWithReturn}, {
|
||||
enabled: true,
|
||||
urlPrefix: "https://example.com/webhookurl",
|
||||
allowJsTransformationFunctions: true,
|
||||
}
|
||||
);
|
||||
@ -231,8 +235,6 @@ describe("GenericHookConnection", () => {
|
||||
it("will fail to handle a webhook with an invalid script", async () => {
|
||||
const webhookData = {question: 'What is the meaning of life?', answer: 42};
|
||||
const [connection, mq] = createGenericHook({name: 'test', transformationFunction: "bibble bobble"}, {
|
||||
enabled: true,
|
||||
urlPrefix: "https://example.com/webhookurl",
|
||||
allowJsTransformationFunctions: true,
|
||||
}
|
||||
);
|
||||
@ -290,8 +292,7 @@ describe("GenericHookConnection", () => {
|
||||
});
|
||||
|
||||
it("should handle simple hook events with user Id prefix", async () => {
|
||||
const config = { enabled: true, urlPrefix: "https://example.com/webhookurl", userIdPrefix: "_webhooks_"};
|
||||
const [connection, mq] = createGenericHook(undefined, config);
|
||||
const [connection, mq] = createGenericHook(undefined, { userIdPrefix: "_webhooks_"});
|
||||
await testSimpleWebhook(connection, mq, "data1");
|
||||
// regression test covering https://github.com/matrix-org/matrix-hookshot/issues/625
|
||||
await testSimpleWebhook(connection, mq, "data2");
|
||||
@ -299,22 +300,21 @@ describe("GenericHookConnection", () => {
|
||||
|
||||
it("should invite a configured puppet to the room if it's unable to join", async () => {
|
||||
const senderUserId = "@_webhooks_some-name:example.test";
|
||||
const config = { enabled: true, urlPrefix: "https://example.com/webhookurl", userIdPrefix: "_webhooks_"};
|
||||
const [connection, mq, as, botIntent] = createGenericHook(undefined, config);
|
||||
const [connection, mq, as, botIntent] = createGenericHook(undefined, { userIdPrefix: "_webhooks_"});
|
||||
const intent = as.getIntentForUserId(senderUserId);
|
||||
let hasInvited = false;
|
||||
|
||||
// This should fail the first time, then pass once we've tried to invite the user
|
||||
intent.ensureJoined = (roomId: string) => {
|
||||
intent.ensureJoined = async (roomId: string) => {
|
||||
if (hasInvited) {
|
||||
return;
|
||||
return roomId;
|
||||
}
|
||||
expect(roomId).to.equal(ROOM_ID);
|
||||
throw new MatrixError({ errcode: "M_FORBIDDEN", error: "Test forced error"}, 401)
|
||||
};
|
||||
|
||||
// This should invite the puppet user.
|
||||
botIntent.underlyingClient.inviteUser = (userId: string, roomId: string) => {
|
||||
botIntent.underlyingClient.inviteUser = async (userId: string, roomId: string) => {
|
||||
expect(userId).to.equal(senderUserId);
|
||||
expect(roomId).to.equal(ROOM_ID);
|
||||
hasInvited = true;
|
||||
@ -328,8 +328,7 @@ describe("GenericHookConnection", () => {
|
||||
|
||||
it("should fail a message if a bot cannot join a room", async () => {
|
||||
const senderUserId = "@_webhooks_some-name:example.test";
|
||||
const config = { enabled: true, urlPrefix: "https://example.com/webhookurl", userIdPrefix: "_webhooks_"};
|
||||
const [connection, mq, as] = createGenericHook(undefined, config);
|
||||
const [connection, mq, as] = createGenericHook(undefined, { userIdPrefix: "_webhooks_"});
|
||||
const intent = as.getIntentForUserId(senderUserId);
|
||||
|
||||
// This should fail the first time, then pass once we've tried to invite the user
|
||||
@ -343,4 +342,95 @@ describe("GenericHookConnection", () => {
|
||||
expect(ex.message).to.contain(`Could not ensure that ${senderUserId} is in ${ROOM_ID}`)
|
||||
}
|
||||
});
|
||||
|
||||
it('should fail to create a hook with an invalid expiry time', () => {
|
||||
for (const expirationDate of [0, 1, -1, false, true, {}, [], new Date(), ""]) {
|
||||
expect(() => GenericHookConnection.validateState({
|
||||
name: "beep",
|
||||
expirationDate,
|
||||
})).to.throw("'expirationDate' must be a non-empty string");
|
||||
}
|
||||
for (const expirationDate of ["no", "\0", "true", " 2024", "2024-01-01", "15:56", "2024-01-01 15:16"]) {
|
||||
expect(() => GenericHookConnection.validateState({
|
||||
name: "beep",
|
||||
expirationDate,
|
||||
})).to.throw("'expirationDate' must be a valid date");
|
||||
}
|
||||
});
|
||||
it('should fail to create a hook with a too short expiry time', async () => {
|
||||
const as = AppserviceMock.create();
|
||||
try {
|
||||
await GenericHookConnection.provisionConnection(ROOM_ID, "@some:user", {
|
||||
name: "foo",
|
||||
expirationDate: new Date().toISOString(),
|
||||
}, {
|
||||
as: as,
|
||||
intent: as.botIntent,
|
||||
config: { generic: new BridgeConfigGenericWebhooks(ConfigDefaults) } as unknown as BridgeConfig,
|
||||
messageClient: new MessageSenderClient(new LocalMQ()),
|
||||
storage: new MemoryStorageProvider(),
|
||||
} as unknown as ProvisionConnectionOpts);
|
||||
assert.fail('Expected function to throw');
|
||||
} catch (ex) {
|
||||
expect(ex.message).to.contain('Expiration date must at least be a hour in the future');
|
||||
}
|
||||
});
|
||||
it('should fail to create a hook with a too long expiry time', async () => {
|
||||
const as = AppserviceMock.create();
|
||||
try {
|
||||
await GenericHookConnection.provisionConnection(ROOM_ID, "@some:user", {
|
||||
name: "foo",
|
||||
expirationDate: add(new Date(), { days: 1, seconds: 1}).toISOString(),
|
||||
}, {
|
||||
as: as,
|
||||
intent: as.botIntent,
|
||||
config: { generic: new BridgeConfigGenericWebhooks({
|
||||
...ConfigDefaults,
|
||||
maxExpiryTime: '1d'
|
||||
}) } as unknown as BridgeConfig,
|
||||
messageClient: new MessageSenderClient(new LocalMQ()),
|
||||
storage: new MemoryStorageProvider(),
|
||||
} as unknown as ProvisionConnectionOpts);
|
||||
assert.fail('Expected function to throw');
|
||||
} catch (ex) {
|
||||
expect(ex.message).to.contain('Expiration date cannot exceed the configured max expiry time');
|
||||
}
|
||||
});
|
||||
it('should fail to create a hook without an expiry time when required by config', async () => {
|
||||
const as = AppserviceMock.create();
|
||||
try {
|
||||
await GenericHookConnection.provisionConnection(ROOM_ID, "@some:user", {
|
||||
name: "foo",
|
||||
}, {
|
||||
as: as,
|
||||
intent: as.botIntent,
|
||||
config: { generic: new BridgeConfigGenericWebhooks({
|
||||
...ConfigDefaults,
|
||||
maxExpiryTime: '1d',
|
||||
requireExpiryTime: true,
|
||||
}) } as unknown as BridgeConfig,
|
||||
messageClient: new MessageSenderClient(new LocalMQ()),
|
||||
storage: new MemoryStorageProvider(),
|
||||
} as unknown as ProvisionConnectionOpts);
|
||||
assert.fail('Expected function to throw');
|
||||
} catch (ex) {
|
||||
expect(ex.message).to.contain('Expiration date must be set');
|
||||
}
|
||||
});
|
||||
it('should create a hook and handle a request within the expiry time', async () => {
|
||||
const [connection, mq] = createGenericHook({
|
||||
expirationDate: add(new Date(), { seconds: 30 }).toISOString(),
|
||||
});
|
||||
await testSimpleWebhook(connection, mq, "test");
|
||||
});
|
||||
it('should reject requests to an expired hook', async () => {
|
||||
const [connection] = createGenericHook({
|
||||
expirationDate: new Date().toISOString(),
|
||||
});
|
||||
expect(await connection.onGenericHook({test: "value"})).to.deep.equal({
|
||||
error: "This hook has expired",
|
||||
statusCode: 404,
|
||||
successful: false,
|
||||
});
|
||||
});
|
||||
})
|
||||
|
@ -14,6 +14,7 @@ import GitHubIcon from "../icons/github.png";
|
||||
import GitLabIcon from "../icons/gitlab.png";
|
||||
import JiraIcon from "../icons/jira.png";
|
||||
import WebhookIcon from "../icons/webhook.png";
|
||||
import { ChevronLeftIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
|
||||
interface IProps {
|
||||
@ -118,7 +119,7 @@ export default function RoomConfigView(props: IProps) {
|
||||
{!serviceScope && activeConnectionType &&
|
||||
<header>
|
||||
<span className={style.backButton} onClick={() => setActiveConnectionType(null)}>
|
||||
<span className="chevron" /> Browse integrations
|
||||
<ChevronLeftIcon /> Browse integrations
|
||||
</span>
|
||||
</header>
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ import { FunctionComponent, h } from "preact";
|
||||
import style from "./Button.module.scss";
|
||||
|
||||
interface ButtonProps extends h.JSX.HTMLAttributes<HTMLButtonElement> {
|
||||
intent?: string;
|
||||
intent?: "remove";
|
||||
}
|
||||
|
||||
export const Button: FunctionComponent = (props: ButtonProps) => {
|
||||
export const Button: FunctionComponent<ButtonProps> = (props) => {
|
||||
let className = style.button;
|
||||
if (props.intent === "remove") {
|
||||
className += ` ${style.remove}`;
|
||||
|
@ -35,7 +35,7 @@
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
input[type="text"],input[type="search"] {
|
||||
input[type="text"],input[type="search"],input[type="datetime-local"] {
|
||||
border: 1px solid var(--cpd-color-border-interactive-primary);
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
@ -57,3 +57,12 @@
|
||||
background: var(--cpd-color-bg-subtle-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
|
||||
button {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,22 @@
|
||||
import { FunctionComponent } from "preact";
|
||||
import style from "./InputField.module.scss";
|
||||
|
||||
import styles from "./InputField.module.scss";
|
||||
import clsx from 'clsx';
|
||||
interface Props {
|
||||
className?: string;
|
||||
visible?: boolean;
|
||||
label?: string;
|
||||
label: string;
|
||||
noPadding: boolean;
|
||||
innerChild?: boolean;
|
||||
}
|
||||
|
||||
export const InputField: FunctionComponent<Props> = ({ className, children, visible = true, label, noPadding, innerChild = false }) => {
|
||||
const inputClassName = [
|
||||
className,
|
||||
style.inputField,
|
||||
noPadding && style.nopad
|
||||
].filter(a => !!a).join(' ');
|
||||
return visible ? <div className={inputClassName}>
|
||||
{label && <label>{innerChild && children}{label}</label>}
|
||||
{(!label || !innerChild) && children}
|
||||
</div> : <></>
|
||||
export const InputField: FunctionComponent<Props> = ({ className, children, visible = true, label, noPadding }) => {
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div className={clsx(className, styles.inputField, noPadding && styles.nopad)}>
|
||||
<label>{label}</label>
|
||||
<div className={styles.container}>
|
||||
{children}
|
||||
</div>
|
||||
</div>;
|
||||
};
|
@ -1,14 +1,15 @@
|
||||
import { FunctionComponent } from "preact"
|
||||
import { ComponentChild, FunctionComponent } from "preact"
|
||||
import { useState } from "preact/hooks"
|
||||
import style from "./ListItem.module.scss";
|
||||
import { ChevronDownIcon, ChevronUpIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
export const ListItem: FunctionComponent<{text: string}> = ({ text, children }) => {
|
||||
export const ListItem: FunctionComponent<{text: ComponentChild}> = ({ text, children }) => {
|
||||
const [expand, setExpand] = useState(false);
|
||||
|
||||
return <div className={style.root}>
|
||||
<div className={style.header} onClick={() => setExpand(!expand)}>
|
||||
<span>{text}</span><span className={`chevron ${expand ? "up" : "down"}`} />
|
||||
</div>
|
||||
<h3 className={style.header} onClick={() => setExpand(!expand)}>
|
||||
{text} {expand ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
</h3>
|
||||
<div className={style.contents}>
|
||||
{expand && children}
|
||||
</div>
|
||||
|
@ -2,11 +2,14 @@ import { FunctionComponent, createRef } from "preact";
|
||||
import { useCallback, useEffect, useState } from "preact/hooks"
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { add, format } from "date-fns";
|
||||
import { BridgeConfig } from "../../BridgeAPI";
|
||||
import { GenericHookConnectionState, GenericHookResponseItem } from "../../../src/Connections/GenericHook";
|
||||
import type { GenericHookConnectionState, GenericHookResponseItem, GenericHookServiceConfig } from "../../../src/Connections/GenericHook";
|
||||
import { ConnectionConfigurationProps, RoomConfig } from "./RoomConfig";
|
||||
import { InputField, ButtonSet, Button } from "../elements";
|
||||
import WebhookIcon from "../../icons/webhook.png";
|
||||
import { Alert, ToggleInput } from "@vector-im/compound-web";
|
||||
import { InfoIcon, WarningIcon } from "@vector-im/compound-design-tokens/assets/web/icons"
|
||||
|
||||
const EXAMPLE_SCRIPT = `if (data.counter === undefined) {
|
||||
result = {
|
||||
@ -28,14 +31,20 @@ const EXAMPLE_SCRIPT = `if (data.counter === undefined) {
|
||||
const DOCUMENTATION_LINK = "https://matrix-org.github.io/matrix-hookshot/latest/setup/webhooks.html#script-api";
|
||||
const CODE_MIRROR_EXTENSIONS = [javascript({})];
|
||||
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ServiceConfig, GenericHookResponseItem, GenericHookConnectionState>> = ({serviceConfig, existingConnection, onSave, onRemove, isUpdating}) => {
|
||||
const EXPIRY_WARN_AT_MS = 3 * 24 * 60 * 60 * 1000;
|
||||
|
||||
const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<GenericHookServiceConfig, GenericHookResponseItem, GenericHookConnectionState>> = ({serviceConfig, existingConnection, onSave, onRemove, isUpdating}) => {
|
||||
const [transFn, setTransFn] = useState<string>(existingConnection?.config.transformationFunction as string || EXAMPLE_SCRIPT);
|
||||
const [transFnEnabled, setTransFnEnabled] = useState(serviceConfig.allowJsTransformationFunctions && !!existingConnection?.config.transformationFunction);
|
||||
const [waitForComplete, setWaitForComplete] = useState(existingConnection?.config.waitForComplete ?? false);
|
||||
|
||||
const nameRef = createRef<HTMLInputElement>();
|
||||
const minExpiryTime = format(add(new Date(), { hours: 1 }), "yyyy-MM-dd'T'HH:mm");
|
||||
const maxExpiryTime = serviceConfig.maxExpiryTime ? format(Date.now() + serviceConfig.maxExpiryTime, "yyyy-MM-dd'T'HH:mm") : undefined;
|
||||
|
||||
const canEdit = !existingConnection || (existingConnection?.canEdit ?? false);
|
||||
const nameRef = createRef<HTMLInputElement>();
|
||||
const expiryRef = createRef<HTMLInputElement>();
|
||||
|
||||
const canEdit = !existingConnection || existingConnection?.canEdit || false;
|
||||
const handleSave = useCallback((evt: Event) => {
|
||||
evt.preventDefault();
|
||||
if (!canEdit) {
|
||||
@ -43,10 +52,11 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<Se
|
||||
}
|
||||
onSave({
|
||||
name: nameRef?.current?.value || existingConnection?.config.name || "Generic Webhook",
|
||||
expirationDate: expiryRef?.current?.value ? new Date(expiryRef?.current?.value).toISOString() : undefined,
|
||||
waitForComplete,
|
||||
...(transFnEnabled ? { transformationFunction: transFn } : undefined),
|
||||
});
|
||||
}, [canEdit, onSave, nameRef, transFn, existingConnection, transFnEnabled, waitForComplete]);
|
||||
}, [expiryRef, canEdit, onSave, nameRef, transFn, existingConnection, transFnEnabled, waitForComplete]);
|
||||
|
||||
const [codeMirrorTheme, setCodeMirrorTheme] = useState<"light"|"dark">("light");
|
||||
useEffect(() => {
|
||||
@ -55,7 +65,6 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<Se
|
||||
}
|
||||
const mm = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const fn = (event: MediaQueryListEvent) => {
|
||||
console.log('media change!');
|
||||
setCodeMirrorTheme(event.matches ? "dark" : "light");
|
||||
};
|
||||
mm.addEventListener('change', fn);
|
||||
@ -63,56 +72,96 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<Se
|
||||
return () => mm.removeEventListener('change', fn);
|
||||
}, [transFnEnabled]);
|
||||
|
||||
|
||||
const hasExpired = existingConnection?.secrets?.timeRemainingMs ? existingConnection?.secrets?.timeRemainingMs <= 0 : false;
|
||||
const willExpireSoon = !hasExpired && existingConnection?.secrets?.timeRemainingMs ? existingConnection?.secrets?.timeRemainingMs <= EXPIRY_WARN_AT_MS : false;
|
||||
|
||||
useEffect(() => {
|
||||
if (!expiryRef.current || !existingConnection?.config.expirationDate) {
|
||||
return;
|
||||
}
|
||||
console.log('Setting date', new Date(existingConnection.config.expirationDate));
|
||||
expiryRef.current.valueAsDate = new Date(existingConnection.config.expirationDate);
|
||||
}, [existingConnection, expiryRef]);
|
||||
|
||||
return <form onSubmit={handleSave}>
|
||||
{hasExpired && <Alert type="critical" title="This Webhook has expired">
|
||||
This Webhook has expired and will no longer handle any incoming requests. Please set a new expiry date or <strong>remove</strong> the Webhook.
|
||||
</Alert>}
|
||||
{willExpireSoon && <Alert type="info" title="This Webhook is expiring soon">
|
||||
This Webhook will expired soon will no longer handle any incoming requests. To extend the Webhook lifetime, set a new expiry date below.
|
||||
</Alert>}
|
||||
<InputField visible={!existingConnection} label="Friendly name" noPadding={true}>
|
||||
<input ref={nameRef} disabled={!canEdit} placeholder="My webhook" type="text" value={existingConnection?.config.name} />
|
||||
<input ref={nameRef} disabled={!canEdit} placeholder="My Webhook" type="text" value={existingConnection?.config.name} />
|
||||
</InputField>
|
||||
|
||||
<InputField visible={!!existingConnection} label="URL" noPadding={true}>
|
||||
<input disabled={true} placeholder="URL hidden" type="text" value={existingConnection?.secrets?.url || ""} />
|
||||
<input disabled={true} placeholder="URL hidden" type="text" value={existingConnection?.secrets?.url?.toString() || ""} />
|
||||
</InputField>
|
||||
|
||||
<InputField label="Expiration date" noPadding={true}>
|
||||
<input
|
||||
type="datetime-local"
|
||||
required={serviceConfig.requireExpiryTime}
|
||||
disabled={!canEdit}
|
||||
ref={expiryRef}
|
||||
min={minExpiryTime}
|
||||
max={maxExpiryTime} />
|
||||
<Button intent="remove" onClick={(ev) => {
|
||||
ev.preventDefault();
|
||||
if (expiryRef.current?.value) {
|
||||
expiryRef.current.value = "";
|
||||
}
|
||||
}} disabled={!!expiryRef.current?.value}>Clear</Button>
|
||||
</InputField>
|
||||
|
||||
<InputField visible={serviceConfig.allowJsTransformationFunctions} label="Enable Transformation JavaScript" noPadding={true}>
|
||||
<input disabled={!canEdit} type="checkbox" checked={transFnEnabled} onChange={useCallback(() => setTransFnEnabled(v => !v), [])} />
|
||||
<ToggleInput disabled={!canEdit} type="checkbox" checked={transFnEnabled} onChange={useCallback(() => setTransFnEnabled(v => !v), [])} />
|
||||
</InputField>
|
||||
|
||||
|
||||
<InputField visible={serviceConfig.allowJsTransformationFunctions && transFnEnabled} label="Respond after function completes" noPadding={true}>
|
||||
<input disabled={!canEdit || serviceConfig.waitForComplete} type="checkbox" checked={waitForComplete || serviceConfig.waitForComplete} onChange={useCallback(() => setWaitForComplete(v => !v), [])} />
|
||||
<ToggleInput disabled={!canEdit || serviceConfig.waitForComplete} type="checkbox" checked={waitForComplete || serviceConfig.waitForComplete} onChange={useCallback(() => setWaitForComplete(v => !v), [])} />
|
||||
</InputField>
|
||||
|
||||
<InputField visible={transFnEnabled} noPadding={true}>
|
||||
<CodeMirror
|
||||
value={transFn}
|
||||
theme={codeMirrorTheme}
|
||||
extensions={CODE_MIRROR_EXTENSIONS}
|
||||
onChange={setTransFn}
|
||||
/>
|
||||
<p> See the <a target="_blank" rel="noopener noreferrer" href={DOCUMENTATION_LINK}>documentation</a> for help writing transformation functions </p>
|
||||
</InputField>
|
||||
{transFnEnabled && <><CodeMirror
|
||||
value={transFn}
|
||||
theme={codeMirrorTheme}
|
||||
extensions={CODE_MIRROR_EXTENSIONS}
|
||||
onChange={setTransFn}
|
||||
/>
|
||||
<p> See the <a target="_blank" rel="noopener noreferrer" href={DOCUMENTATION_LINK}>documentation</a> for help writing transformation functions </p>
|
||||
</>}
|
||||
<ButtonSet>
|
||||
{ canEdit && <Button disabled={isUpdating} type="submit">{ existingConnection ? "Save" : "Add webhook" }</Button>}
|
||||
{ canEdit && existingConnection && <Button disabled={isUpdating} intent="remove" onClick={onRemove}>Remove webhook</Button>}
|
||||
{ canEdit && <Button disabled={isUpdating} type="submit">{ existingConnection ? "Save" : "Add Webhook" }</Button>}
|
||||
{ canEdit && existingConnection && <Button disabled={isUpdating} intent="remove" onClick={onRemove}>Remove Webhook</Button>}
|
||||
</ButtonSet>
|
||||
</form>;
|
||||
};
|
||||
|
||||
interface ServiceConfig {
|
||||
allowJsTransformationFunctions: boolean,
|
||||
waitForComplete: boolean,
|
||||
}
|
||||
|
||||
const RoomConfigText = {
|
||||
header: 'Inbound (Generic) Webhooks',
|
||||
createNew: 'Create new webhook',
|
||||
listCanEdit: 'Your webhooks',
|
||||
listCantEdit: 'Configured webhooks',
|
||||
createNew: 'Create new Webhook',
|
||||
listCanEdit: 'Your Webhooks',
|
||||
listCantEdit: 'Configured Webhooks',
|
||||
};
|
||||
|
||||
const RoomConfigListItemFunc = (c: GenericHookResponseItem) => c.config.name;
|
||||
const RoomConfigListItemFunc = (c: GenericHookResponseItem) => {
|
||||
const hasExpired = c.secrets?.timeRemainingMs ? c.secrets.timeRemainingMs <= 0 : false;
|
||||
const willExpireSoon = !hasExpired && c.secrets?.timeRemainingMs ? c.secrets?.timeRemainingMs <= EXPIRY_WARN_AT_MS : false;
|
||||
|
||||
return <>
|
||||
<span style={{verticalAlign: "middle"}}>{c.config.name}</span>
|
||||
<span style={{marginLeft: "8px", verticalAlign: "middle"}}>
|
||||
{hasExpired && <WarningIcon />}
|
||||
{willExpireSoon && <InfoIcon />}
|
||||
</span>
|
||||
</>
|
||||
};
|
||||
|
||||
export const GenericWebhookConfig: BridgeConfig = ({ roomId, showHeader }) => {
|
||||
return <RoomConfig<ServiceConfig, GenericHookResponseItem, GenericHookConnectionState>
|
||||
return <RoomConfig<GenericHookServiceConfig, GenericHookResponseItem, GenericHookConnectionState>
|
||||
headerImg={WebhookIcon}
|
||||
darkHeaderImg={true}
|
||||
showHeader={showHeader}
|
||||
|
@ -88,7 +88,7 @@ const ConnectionConfiguration: FunctionComponent<ConnectionConfigurationProps<ne
|
||||
<InputField visible={!!existingConnection || !!newConnectionState} label="Command Prefix" noPadding={true}>
|
||||
<input ref={commandPrefixRef} type="text" value={existingConnection?.config.commandPrefix} placeholder="!gl" />
|
||||
</InputField>
|
||||
<InputField visible={!!existingConnection || !!newConnectionState} label="Include comment bodies" noPadding={true} innerChild={true}>
|
||||
<InputField visible={!!existingConnection || !!newConnectionState} label="Include comment bodies" noPadding={true}>
|
||||
<input ref={includeBodyRef} disabled={!canEdit} type="checkbox" checked={!!existingConnection?.config.includeCommentBody} />
|
||||
</InputField>
|
||||
<InputField visible={!!existingConnection || !!newConnectionState} label="Events" noPadding={true}>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FunctionComponent } from "preact";
|
||||
import { ComponentChild, FunctionComponent } from "preact";
|
||||
import { useCallback, useContext, useEffect, useReducer, useState } from "preact/hooks"
|
||||
import { BridgeAPIError } from "../../BridgeAPI";
|
||||
import { ListItem, Card } from "../elements";
|
||||
@ -39,7 +39,7 @@ interface IRoomConfigProps<SConfig, ConnectionType extends GetConnectionsRespons
|
||||
headerImg: string;
|
||||
text: IRoomConfigText;
|
||||
connectionEventType: string;
|
||||
listItemName: (c: ConnectionType) => string,
|
||||
listItemName: (c: ConnectionType) => ComponentChild,
|
||||
connectionConfigComponent: FunctionComponent<ConnectionConfigurationProps<SConfig, ConnectionType, ConnectionState>>;
|
||||
}
|
||||
|
||||
|
@ -37,33 +37,3 @@ button {
|
||||
font-weight: 600;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
|
||||
.chevron::before {
|
||||
border-style: solid;
|
||||
border-width: 0.25em 0.25em 0 0;
|
||||
border-radius: 3px;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 10px;
|
||||
position: relative;
|
||||
top: 0.15em;
|
||||
vertical-align: top;
|
||||
width: 10px;
|
||||
left: 0.25em;
|
||||
transform: rotate(-135deg);
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.chevron.down::before {
|
||||
transform: rotate(-225deg);
|
||||
border-color: var(--cpd-color-text-primary);
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
.chevron.up::before {
|
||||
transform: rotate(-45deg);
|
||||
border-color: var(--cpd-color-text-primary);
|
||||
float: right;
|
||||
}
|
||||
|
749
yarn.lock
749
yarn.lock
@ -309,7 +309,7 @@
|
||||
"@babel/plugin-syntax-jsx" "^7.23.3"
|
||||
"@babel/types" "^7.23.4"
|
||||
|
||||
"@babel/runtime@^7.13.10", "@babel/runtime@^7.18.6", "@babel/runtime@^7.6.0":
|
||||
"@babel/runtime@^7.18.6", "@babel/runtime@^7.6.0":
|
||||
version "7.23.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.6.tgz#c05e610dc228855dc92ef1b53d07389ed8ab521d"
|
||||
integrity sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==
|
||||
@ -647,20 +647,20 @@
|
||||
dependencies:
|
||||
"@floating-ui/dom" "^1.5.1"
|
||||
|
||||
"@floating-ui/react-dom@^2.0.8", "@floating-ui/react-dom@^2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.0.tgz#4f0e5e9920137874b2405f7d6c862873baf4beff"
|
||||
integrity sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA==
|
||||
"@floating-ui/react-dom@^2.1.2":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31"
|
||||
integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==
|
||||
dependencies:
|
||||
"@floating-ui/dom" "^1.0.0"
|
||||
|
||||
"@floating-ui/react@^0.26.9":
|
||||
version "0.26.17"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.17.tgz#efa2e1a0dea3d9d308965c5ccd49756bb64a883d"
|
||||
integrity sha512-ESD+jYWwqwVzaIgIhExrArdsCL1rOAzryG/Sjlu8yaD3Mtqi3uVyhbE2V7jD58Mo52qbzKz2eUY/Xgh5I86FCQ==
|
||||
"@floating-ui/react@^0.26.24":
|
||||
version "0.26.28"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.28.tgz#93f44ebaeb02409312e9df9507e83aab4a8c0dc7"
|
||||
integrity sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==
|
||||
dependencies:
|
||||
"@floating-ui/react-dom" "^2.1.0"
|
||||
"@floating-ui/utils" "^0.2.0"
|
||||
"@floating-ui/react-dom" "^2.1.2"
|
||||
"@floating-ui/utils" "^0.2.8"
|
||||
tabbable "^6.0.0"
|
||||
|
||||
"@floating-ui/utils@^0.1.3":
|
||||
@ -673,6 +673,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.2.tgz#d8bae93ac8b815b2bd7a98078cf91e2724ef11e5"
|
||||
integrity sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==
|
||||
|
||||
"@floating-ui/utils@^0.2.8":
|
||||
version "0.2.8"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62"
|
||||
integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==
|
||||
|
||||
"@humanwhocodes/config-array@^0.11.13":
|
||||
version "0.11.13"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297"
|
||||
@ -1328,337 +1333,286 @@
|
||||
"@prefresh/utils" "^1.2.0"
|
||||
"@rollup/pluginutils" "^4.2.1"
|
||||
|
||||
"@radix-ui/primitive@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd"
|
||||
integrity sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2"
|
||||
integrity sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==
|
||||
|
||||
"@radix-ui/react-arrow@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d"
|
||||
integrity sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==
|
||||
"@radix-ui/react-arrow@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz#744f388182d360b86285217e43b6c63633f39e7a"
|
||||
integrity sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
|
||||
"@radix-ui/react-collection@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.3.tgz#9595a66e09026187524a36c6e7e9c7d286469159"
|
||||
integrity sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==
|
||||
"@radix-ui/react-collection@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed"
|
||||
integrity sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
"@radix-ui/react-context" "1.1.0"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-slot" "1.1.0"
|
||||
|
||||
"@radix-ui/react-compose-refs@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989"
|
||||
integrity sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74"
|
||||
integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==
|
||||
|
||||
"@radix-ui/react-context-menu@^2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-2.1.5.tgz#1bdbd72761439f9166f75dc4598f276265785c83"
|
||||
integrity sha512-R5XaDj06Xul1KGb+WP8qiOh7tKJNz2durpLBXAGZjSVtctcRFCuEvy2gtMwRJGePwQQE5nV77gs4FwRi8T+r2g==
|
||||
"@radix-ui/react-context-menu@^2.2.1":
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-2.2.2.tgz#efcddc559fc3011721b65148f062d04027f76c7a"
|
||||
integrity sha512-99EatSTpW+hRYHt7m8wdDlLtkmTovEe8Z/hnxUPV+SKuuNL5HWNhQI4QSdjZqNSgXHay2z4M3Dym73j9p2Gx5Q==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-menu" "2.0.6"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
"@radix-ui/primitive" "1.1.0"
|
||||
"@radix-ui/react-context" "1.1.1"
|
||||
"@radix-ui/react-menu" "2.1.2"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||
|
||||
"@radix-ui/react-context@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c"
|
||||
integrity sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-context@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8"
|
||||
integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==
|
||||
|
||||
"@radix-ui/react-dialog@^1.0.4":
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300"
|
||||
integrity sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==
|
||||
"@radix-ui/react-context@1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.1.tgz#82074aa83a472353bb22e86f11bcbd1c61c4c71a"
|
||||
integrity sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==
|
||||
|
||||
"@radix-ui/react-dialog@^1.1.1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz#d9345575211d6f2d13e209e84aec9a8584b54d6c"
|
||||
integrity sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-dismissable-layer" "1.0.5"
|
||||
"@radix-ui/react-focus-guards" "1.0.1"
|
||||
"@radix-ui/react-focus-scope" "1.0.4"
|
||||
"@radix-ui/react-id" "1.0.1"
|
||||
"@radix-ui/react-portal" "1.0.4"
|
||||
"@radix-ui/react-presence" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
"@radix-ui/primitive" "1.1.0"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
"@radix-ui/react-context" "1.1.1"
|
||||
"@radix-ui/react-dismissable-layer" "1.1.1"
|
||||
"@radix-ui/react-focus-guards" "1.1.1"
|
||||
"@radix-ui/react-focus-scope" "1.1.0"
|
||||
"@radix-ui/react-id" "1.1.0"
|
||||
"@radix-ui/react-portal" "1.1.2"
|
||||
"@radix-ui/react-presence" "1.1.1"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-slot" "1.1.0"
|
||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.5"
|
||||
react-remove-scroll "2.6.0"
|
||||
|
||||
"@radix-ui/react-direction@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b"
|
||||
integrity sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-direction@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz#a7d39855f4d077adc2a1922f9c353c5977a09cdc"
|
||||
integrity sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==
|
||||
|
||||
"@radix-ui/react-dismissable-layer@1.0.5":
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4"
|
||||
integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==
|
||||
"@radix-ui/react-dismissable-layer@1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz#cbdcb739c5403382bdde5f9243042ba643883396"
|
||||
integrity sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-escape-keydown" "1.0.3"
|
||||
"@radix-ui/primitive" "1.1.0"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
"@radix-ui/react-use-escape-keydown" "1.1.0"
|
||||
|
||||
"@radix-ui/react-dropdown-menu@^2.0.6":
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz#cdf13c956c5e263afe4e5f3587b3071a25755b63"
|
||||
integrity sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==
|
||||
"@radix-ui/react-dropdown-menu@^2.1.1":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.2.tgz#acc49577130e3c875ef0133bd1e271ea3392d924"
|
||||
integrity sha512-GVZMR+eqK8/Kes0a36Qrv+i20bAPXSn8rCBTHx30w+3ECnR5o3xixAlqcVaYvLeyKUsm0aqyhWfmUcqufM8nYA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-id" "1.0.1"
|
||||
"@radix-ui/react-menu" "2.0.6"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
"@radix-ui/primitive" "1.1.0"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
"@radix-ui/react-context" "1.1.1"
|
||||
"@radix-ui/react-id" "1.1.0"
|
||||
"@radix-ui/react-menu" "2.1.2"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||
|
||||
"@radix-ui/react-focus-guards@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz#1ea7e32092216b946397866199d892f71f7f98ad"
|
||||
integrity sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-focus-guards@1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz#8635edd346304f8b42cae86b05912b61aef27afe"
|
||||
integrity sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==
|
||||
|
||||
"@radix-ui/react-focus-scope@1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525"
|
||||
integrity sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==
|
||||
"@radix-ui/react-focus-scope@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz#ebe2891a298e0a33ad34daab2aad8dea31caf0b2"
|
||||
integrity sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
|
||||
"@radix-ui/react-form@^0.0.3":
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-form/-/react-form-0.0.3.tgz#328e7163e723ccc748459d66a2d685d7b4f85d5a"
|
||||
integrity sha512-kgE+Z/haV6fxE5WqIXj05KkaXa3OkZASoTDy25yX2EIp/x0c54rOH/vFr5nOZTg7n7T1z8bSyXmiVIFP9bbhPQ==
|
||||
"@radix-ui/react-form@^0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-form/-/react-form-0.1.0.tgz#7111a6aa54a2bde0d11fb72643f9ffc871ac58ad"
|
||||
integrity sha512-1/oVYPDjbFILOLIarcGcMKo+y6SbTVT/iUKVEw59CF4offwZgBgC3ZOeSBewjqU0vdA6FWTPWTN63obj55S/tQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-id" "1.0.1"
|
||||
"@radix-ui/react-label" "2.0.2"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/primitive" "1.1.0"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
"@radix-ui/react-context" "1.1.0"
|
||||
"@radix-ui/react-id" "1.1.0"
|
||||
"@radix-ui/react-label" "2.1.0"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
|
||||
"@radix-ui/react-id@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0"
|
||||
integrity sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==
|
||||
"@radix-ui/react-id@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.0.tgz#de47339656594ad722eb87f94a6b25f9cffae0ed"
|
||||
integrity sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||
|
||||
"@radix-ui/react-label@2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-2.0.2.tgz#9c72f1d334aac996fdc27b48a8bdddd82108fb6d"
|
||||
integrity sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==
|
||||
"@radix-ui/react-label@2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-2.1.0.tgz#3aa2418d70bb242be37c51ff5e51a2adcbc372e3"
|
||||
integrity sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
|
||||
"@radix-ui/react-menu@2.0.6":
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.0.6.tgz#2c9e093c1a5d5daa87304b2a2f884e32288ae79e"
|
||||
integrity sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==
|
||||
"@radix-ui/react-menu@2.1.2":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.1.2.tgz#91f6815845a4298dde775563ed2d80b7ad667899"
|
||||
integrity sha512-lZ0R4qR2Al6fZ4yCCZzu/ReTFrylHFxIqy7OezIpWF4bL0o9biKo0pFIvkaew3TyZ9Fy5gYVrR5zCGZBVbO1zg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-collection" "1.0.3"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-direction" "1.0.1"
|
||||
"@radix-ui/react-dismissable-layer" "1.0.5"
|
||||
"@radix-ui/react-focus-guards" "1.0.1"
|
||||
"@radix-ui/react-focus-scope" "1.0.4"
|
||||
"@radix-ui/react-id" "1.0.1"
|
||||
"@radix-ui/react-popper" "1.1.3"
|
||||
"@radix-ui/react-portal" "1.0.4"
|
||||
"@radix-ui/react-presence" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-roving-focus" "1.0.4"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/primitive" "1.1.0"
|
||||
"@radix-ui/react-collection" "1.1.0"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
"@radix-ui/react-context" "1.1.1"
|
||||
"@radix-ui/react-direction" "1.1.0"
|
||||
"@radix-ui/react-dismissable-layer" "1.1.1"
|
||||
"@radix-ui/react-focus-guards" "1.1.1"
|
||||
"@radix-ui/react-focus-scope" "1.1.0"
|
||||
"@radix-ui/react-id" "1.1.0"
|
||||
"@radix-ui/react-popper" "1.2.0"
|
||||
"@radix-ui/react-portal" "1.1.2"
|
||||
"@radix-ui/react-presence" "1.1.1"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-roving-focus" "1.1.0"
|
||||
"@radix-ui/react-slot" "1.1.0"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.5"
|
||||
react-remove-scroll "2.6.0"
|
||||
|
||||
"@radix-ui/react-popper@1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42"
|
||||
integrity sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==
|
||||
"@radix-ui/react-popper@1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz#a3e500193d144fe2d8f5d5e60e393d64111f2a7a"
|
||||
integrity sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@floating-ui/react-dom" "^2.0.0"
|
||||
"@radix-ui/react-arrow" "1.0.3"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
"@radix-ui/react-use-rect" "1.0.1"
|
||||
"@radix-ui/react-use-size" "1.0.1"
|
||||
"@radix-ui/rect" "1.0.1"
|
||||
"@radix-ui/react-arrow" "1.1.0"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
"@radix-ui/react-context" "1.1.0"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||
"@radix-ui/react-use-rect" "1.1.0"
|
||||
"@radix-ui/react-use-size" "1.1.0"
|
||||
"@radix-ui/rect" "1.1.0"
|
||||
|
||||
"@radix-ui/react-portal@1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15"
|
||||
integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==
|
||||
"@radix-ui/react-portal@1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.2.tgz#51eb46dae7505074b306ebcb985bf65cc547d74e"
|
||||
integrity sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||
|
||||
"@radix-ui/react-presence@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba"
|
||||
integrity sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==
|
||||
"@radix-ui/react-presence@1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.1.tgz#98aba423dba5e0c687a782c0669dcd99de17f9b1"
|
||||
integrity sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||
|
||||
"@radix-ui/react-primitive@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0"
|
||||
integrity sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==
|
||||
"@radix-ui/react-primitive@2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884"
|
||||
integrity sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
"@radix-ui/react-slot" "1.1.0"
|
||||
|
||||
"@radix-ui/react-roving-focus@1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974"
|
||||
integrity sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==
|
||||
"@radix-ui/react-progress@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-progress/-/react-progress-1.1.0.tgz#28c267885ec154fc557ec7a66cb462787312f7e2"
|
||||
integrity sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-collection" "1.0.3"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-direction" "1.0.1"
|
||||
"@radix-ui/react-id" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
"@radix-ui/react-context" "1.1.0"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
|
||||
"@radix-ui/react-separator@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.0.3.tgz#be5a931a543d5726336b112f465f58585c04c8aa"
|
||||
integrity sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==
|
||||
"@radix-ui/react-roving-focus@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz#b30c59daf7e714c748805bfe11c76f96caaac35e"
|
||||
integrity sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/primitive" "1.1.0"
|
||||
"@radix-ui/react-collection" "1.1.0"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
"@radix-ui/react-context" "1.1.0"
|
||||
"@radix-ui/react-direction" "1.1.0"
|
||||
"@radix-ui/react-id" "1.1.0"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||
|
||||
"@radix-ui/react-slot@1.0.2", "@radix-ui/react-slot@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab"
|
||||
integrity sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==
|
||||
"@radix-ui/react-separator@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.1.0.tgz#ee0f4d86003b0e3ea7bc6ccab01ea0adee32663e"
|
||||
integrity sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-primitive" "2.0.0"
|
||||
|
||||
"@radix-ui/react-tooltip@^1.0.6":
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz#8f55070f852e7e7450cc1d9210b793d2e5a7686e"
|
||||
integrity sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==
|
||||
"@radix-ui/react-slot@1.1.0", "@radix-ui/react-slot@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.0.tgz#7c5e48c36ef5496d97b08f1357bb26ed7c714b84"
|
||||
integrity sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-dismissable-layer" "1.0.5"
|
||||
"@radix-ui/react-id" "1.0.1"
|
||||
"@radix-ui/react-popper" "1.1.3"
|
||||
"@radix-ui/react-portal" "1.0.4"
|
||||
"@radix-ui/react-presence" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
"@radix-ui/react-visually-hidden" "1.0.3"
|
||||
"@radix-ui/react-compose-refs" "1.1.0"
|
||||
|
||||
"@radix-ui/react-use-callback-ref@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a"
|
||||
integrity sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-callback-ref@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1"
|
||||
integrity sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==
|
||||
|
||||
"@radix-ui/react-use-controllable-state@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz#ecd2ced34e6330caf89a82854aa2f77e07440286"
|
||||
integrity sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==
|
||||
"@radix-ui/react-use-controllable-state@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz#1321446857bb786917df54c0d4d084877aab04b0"
|
||||
integrity sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
|
||||
"@radix-ui/react-use-escape-keydown@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755"
|
||||
integrity sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==
|
||||
"@radix-ui/react-use-escape-keydown@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz#31a5b87c3b726504b74e05dac1edce7437b98754"
|
||||
integrity sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||
|
||||
"@radix-ui/react-use-layout-effect@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399"
|
||||
integrity sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27"
|
||||
integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==
|
||||
|
||||
"@radix-ui/react-use-rect@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz#fde50b3bb9fd08f4a1cd204572e5943c244fcec2"
|
||||
integrity sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==
|
||||
"@radix-ui/react-use-rect@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz#13b25b913bd3e3987cc9b073a1a164bb1cf47b88"
|
||||
integrity sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/rect" "1.0.1"
|
||||
"@radix-ui/rect" "1.1.0"
|
||||
|
||||
"@radix-ui/react-use-size@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz#1c5f5fea940a7d7ade77694bb98116fb49f870b2"
|
||||
integrity sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==
|
||||
"@radix-ui/react-use-size@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz#b4dba7fbd3882ee09e8d2a44a3eed3a7e555246b"
|
||||
integrity sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||
|
||||
"@radix-ui/react-visually-hidden@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz#51aed9dd0fe5abcad7dee2a234ad36106a6984ac"
|
||||
integrity sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
|
||||
"@radix-ui/rect@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.1.tgz#bf8e7d947671996da2e30f4904ece343bc4a883f"
|
||||
integrity sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/rect@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438"
|
||||
integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==
|
||||
|
||||
"@rollup/plugin-alias@^5.1.0":
|
||||
version "5.1.0"
|
||||
@ -2097,11 +2051,6 @@
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/q@^1.5.1":
|
||||
version "1.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.8.tgz#95f6c6a08f2ad868ba230ead1d2d7f7be3db3837"
|
||||
integrity sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==
|
||||
|
||||
"@types/qs@*":
|
||||
version "6.9.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.11.tgz#208d8a30bc507bd82e03ada29e4732ea46a6bbda"
|
||||
@ -2349,39 +2298,32 @@
|
||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
||||
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
|
||||
|
||||
"@vector-im/compound-design-tokens@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-1.3.0.tgz#1d04f006a9e56b920432095d08d7c84c0933ebc7"
|
||||
integrity sha512-RXcyEAdxNzekMhVuvxtLPt9zb6yT2N+5cnb2Hul9zwRiF7+XEHpD36+IF6V0QOXk2pkN0wOr3jCvc9eOWOq9SQ==
|
||||
dependencies:
|
||||
svg2vectordrawable "^2.9.1"
|
||||
"@vector-im/compound-design-tokens@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@vector-im/compound-design-tokens/-/compound-design-tokens-2.0.1.tgz#add14494caab16cdbe98f2bdabe726908739def4"
|
||||
integrity sha512-4nkPcrPII+sejispn+UkWZYFN7LecN39e4WGBupdceiMq0NJrfXrnVtJ9/6BDLgSqHInb6R/IWQkIbPbzfqRMg==
|
||||
|
||||
"@vector-im/compound-web@^4.8.0":
|
||||
version "4.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-4.8.0.tgz#1fe11d78549694f8d91b40065994bad19a7cebf2"
|
||||
integrity sha512-kyB8wQPbdTUFWIzAbb4HcZ4iisUUpbm0xwmEjV9ZNN1/EIodidW6nLeYATh3Vc1fBvTGTgbFiPc1DiAcBuudiw==
|
||||
"@vector-im/compound-web@^7.3.0":
|
||||
version "7.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-7.3.0.tgz#9594113ac50bff4794715104a30a60c52d15517d"
|
||||
integrity sha512-gDppQUtpk5LvNHUg+Zlv9qzo1iBAag0s3g8Ec0qS5q4zGBKG6ruXXrNUKg1aK8rpbo2hYQsGaHM6dD8NqLoq3Q==
|
||||
dependencies:
|
||||
"@floating-ui/react" "^0.26.9"
|
||||
"@floating-ui/react-dom" "^2.0.8"
|
||||
"@radix-ui/react-context-menu" "^2.1.5"
|
||||
"@radix-ui/react-dropdown-menu" "^2.0.6"
|
||||
"@radix-ui/react-form" "^0.0.3"
|
||||
"@radix-ui/react-separator" "^1.0.3"
|
||||
"@radix-ui/react-slot" "^1.0.2"
|
||||
"@radix-ui/react-tooltip" "^1.0.6"
|
||||
classnames "^2.3.2"
|
||||
vaul "^0.7.0"
|
||||
"@floating-ui/react" "^0.26.24"
|
||||
"@radix-ui/react-context-menu" "^2.2.1"
|
||||
"@radix-ui/react-dropdown-menu" "^2.1.1"
|
||||
"@radix-ui/react-form" "^0.1.0"
|
||||
"@radix-ui/react-progress" "^1.1.0"
|
||||
"@radix-ui/react-separator" "^1.1.0"
|
||||
"@radix-ui/react-slot" "^1.1.0"
|
||||
classnames "^2.5.1"
|
||||
ts-xor "^1.3.0"
|
||||
vaul "^1.0.0"
|
||||
|
||||
abbrev@1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
|
||||
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
|
||||
|
||||
abs-svg-path@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/abs-svg-path/-/abs-svg-path-0.1.1.tgz#df601c8e8d2ba10d4a76d625e236a9a39c2723bf"
|
||||
integrity sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==
|
||||
|
||||
accepts@~1.3.8:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
|
||||
@ -3008,7 +2950,7 @@ chalk@4, chalk@^4, chalk@^4.0.0, chalk@^4.1.0:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^2.4.1, chalk@^2.4.2:
|
||||
chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
@ -3054,10 +2996,10 @@ cjs-module-lexer@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107"
|
||||
integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==
|
||||
|
||||
classnames@^2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
|
||||
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
|
||||
classnames@^2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
|
||||
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
|
||||
|
||||
clean-stack@^2.0.0:
|
||||
version "2.2.0"
|
||||
@ -3091,6 +3033,11 @@ cliui@^8.0.1:
|
||||
strip-ansi "^6.0.1"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
clsx@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||
|
||||
cluster-key-slot@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac"
|
||||
@ -3101,15 +3048,6 @@ co@^4.6.0:
|
||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==
|
||||
|
||||
coa@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3"
|
||||
integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==
|
||||
dependencies:
|
||||
"@types/q" "^1.5.1"
|
||||
chalk "^2.4.1"
|
||||
q "^1.1.2"
|
||||
|
||||
codemirror@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-6.0.1.tgz#62b91142d45904547ee3e0e0e4c1a79158035a29"
|
||||
@ -3280,17 +3218,6 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
css-select@^4.1.3:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b"
|
||||
integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
css-what "^6.0.1"
|
||||
domhandler "^4.3.1"
|
||||
domutils "^2.8.0"
|
||||
nth-check "^2.0.1"
|
||||
|
||||
css-select@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
|
||||
@ -3302,14 +3229,6 @@ css-select@^5.1.0:
|
||||
domutils "^3.0.1"
|
||||
nth-check "^2.0.1"
|
||||
|
||||
css-tree@^1.1.2, css-tree@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
|
||||
integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
|
||||
dependencies:
|
||||
mdn-data "2.0.14"
|
||||
source-map "^0.6.1"
|
||||
|
||||
css-tree@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20"
|
||||
@ -3326,18 +3245,11 @@ css-tree@~2.2.0:
|
||||
mdn-data "2.0.28"
|
||||
source-map-js "^1.0.1"
|
||||
|
||||
css-what@^6.0.1, css-what@^6.1.0:
|
||||
css-what@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
|
||||
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
|
||||
|
||||
csso@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529"
|
||||
integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==
|
||||
dependencies:
|
||||
css-tree "^1.1.2"
|
||||
|
||||
csso@^5.0.5:
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6"
|
||||
@ -3352,6 +3264,11 @@ dashdash@^1.12.0:
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
date-fns@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14"
|
||||
integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
@ -3531,7 +3448,7 @@ domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
|
||||
integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
|
||||
@ -3545,7 +3462,7 @@ domhandler@^5.0.2, domhandler@^5.0.3:
|
||||
dependencies:
|
||||
domelementtype "^2.3.0"
|
||||
|
||||
domutils@^2.5.2, domutils@^2.8.0:
|
||||
domutils@^2.5.2:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
|
||||
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
|
||||
@ -5034,11 +4951,6 @@ is-string@^1.0.5, is-string@^1.0.7:
|
||||
dependencies:
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-svg-path@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-svg-path/-/is-svg-path-1.0.2.tgz#77ab590c12b3d20348e5c7a13d0040c87784dda0"
|
||||
integrity sha512-Lj4vePmqpPR1ZnRctHv8ltSh1OrSxHkhUkd7wi+VQdcdP15/KvQFyk7LhNuM7ZW0EVbJz8kZLVmL9quLrfq4Kg==
|
||||
|
||||
is-symbol@^1.0.2, is-symbol@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
|
||||
@ -6064,11 +5976,6 @@ matrix-widget-api@^1.6.0:
|
||||
"@types/events" "^3.0.0"
|
||||
events "^3.2.0"
|
||||
|
||||
mdn-data@2.0.14:
|
||||
version "2.0.14"
|
||||
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
|
||||
integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
|
||||
|
||||
mdn-data@2.0.28:
|
||||
version "2.0.28"
|
||||
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba"
|
||||
@ -6350,13 +6257,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
normalize-svg-path@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz#0e614eca23c39f0cffe821d6be6cd17e569a766c"
|
||||
integrity sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==
|
||||
dependencies:
|
||||
svg-arc-to-cubic-bezier "^3.0.0"
|
||||
|
||||
npm-run-path@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
|
||||
@ -6603,6 +6503,11 @@ parent-module@^1.0.0:
|
||||
dependencies:
|
||||
callsites "^3.0.0"
|
||||
|
||||
parse-duration@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-duration/-/parse-duration-1.1.0.tgz#5192084c5d8f2a3fd676d04a451dbd2e05a1819c"
|
||||
integrity sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ==
|
||||
|
||||
parse-json@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
|
||||
@ -6618,11 +6523,6 @@ parse-srcset@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1"
|
||||
integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==
|
||||
|
||||
parse-svg-path@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/parse-svg-path/-/parse-svg-path-0.1.2.tgz#7a7ec0d1eb06fa5325c7d3e009b859a09b5d49eb"
|
||||
integrity sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==
|
||||
|
||||
parseley@^0.12.0:
|
||||
version "0.12.1"
|
||||
resolved "https://registry.yarnpkg.com/parseley/-/parseley-0.12.1.tgz#4afd561d50215ebe259e3e7a853e62f600683aef"
|
||||
@ -6879,11 +6779,6 @@ pure-rand@^6.0.0:
|
||||
resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7"
|
||||
integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==
|
||||
|
||||
q@^1.1.2:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||
integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==
|
||||
|
||||
qs@6.11.0:
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
|
||||
@ -6981,20 +6876,20 @@ react-is@^18.0.0:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||
|
||||
react-remove-scroll-bar@^2.3.3:
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz#53e272d7a5cb8242990c7f144c44d8bd8ab5afd9"
|
||||
integrity sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==
|
||||
react-remove-scroll-bar@^2.3.6:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c"
|
||||
integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==
|
||||
dependencies:
|
||||
react-style-singleton "^2.2.1"
|
||||
tslib "^2.0.0"
|
||||
|
||||
react-remove-scroll@2.5.5:
|
||||
version "2.5.5"
|
||||
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77"
|
||||
integrity sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==
|
||||
react-remove-scroll@2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz#fb03a0845d7768a4f1519a99fdb84983b793dc07"
|
||||
integrity sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==
|
||||
dependencies:
|
||||
react-remove-scroll-bar "^2.3.3"
|
||||
react-remove-scroll-bar "^2.3.6"
|
||||
react-style-singleton "^2.2.1"
|
||||
tslib "^2.1.0"
|
||||
use-callback-ref "^1.3.0"
|
||||
@ -7559,11 +7454,6 @@ sshpk@^1.14.1, sshpk@^1.7.0:
|
||||
safer-buffer "^2.0.2"
|
||||
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:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
|
||||
@ -7766,45 +7656,6 @@ 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"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
svg-arc-to-cubic-bezier@^3.0.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz#390c450035ae1c4a0104d90650304c3bc814abe6"
|
||||
integrity sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==
|
||||
|
||||
svg-path-bounds@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/svg-path-bounds/-/svg-path-bounds-1.0.2.tgz#00312f672b08afc432a66ddfbd06db40cec8d0d0"
|
||||
integrity sha512-H4/uAgLWrppIC0kHsb2/dWUYSmb4GE5UqH06uqWBcg6LBjX2fu0A8+JrO2/FJPZiSsNOKZAhyFFgsLTdYUvSqQ==
|
||||
dependencies:
|
||||
abs-svg-path "^0.1.1"
|
||||
is-svg-path "^1.0.1"
|
||||
normalize-svg-path "^1.0.0"
|
||||
parse-svg-path "^0.1.2"
|
||||
|
||||
svg2vectordrawable@^2.9.1:
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/svg2vectordrawable/-/svg2vectordrawable-2.9.1.tgz#23186ff7ace7038d09c031176dbca04063a97e5d"
|
||||
integrity sha512-7WJIh4SzZLyEJtn45y+f8rREkgBiQMWfb0FoYkXuioywESjDWfbSuP0FQEmIiHP2zOi0oOO8pTG4VkeWJyidWw==
|
||||
dependencies:
|
||||
coa "^2.0.2"
|
||||
mkdirp "^1.0.4"
|
||||
svg-path-bounds "^1.0.1"
|
||||
svgo "^2.8.0"
|
||||
svgpath "^2.5.0"
|
||||
|
||||
svgo@^2.8.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24"
|
||||
integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==
|
||||
dependencies:
|
||||
"@trysound/sax" "0.2.0"
|
||||
commander "^7.2.0"
|
||||
css-select "^4.1.3"
|
||||
css-tree "^1.1.3"
|
||||
csso "^4.2.0"
|
||||
picocolors "^1.0.0"
|
||||
stable "^0.1.8"
|
||||
|
||||
svgo@^3.1.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.2.0.tgz#7a5dff2938d8c6096e00295c2390e8e652fa805d"
|
||||
@ -7818,11 +7669,6 @@ svgo@^3.1.0:
|
||||
csso "^5.0.5"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
svgpath@^2.5.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/svgpath/-/svgpath-2.6.0.tgz#5b160ef3d742b7dfd2d721bf90588d3450d7a90d"
|
||||
integrity sha512-OIWR6bKzXvdXYyO4DK/UWa1VA1JeKq8E+0ug2DG98Y/vOmMpfZNj+TIG988HjfYSqtcy/hFOtZq/n/j5GSESNg==
|
||||
|
||||
tabbable@^6.0.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97"
|
||||
@ -7932,6 +7778,11 @@ ts-node@^10.9.1:
|
||||
v8-compile-cache-lib "^3.0.1"
|
||||
yn "3.1.1"
|
||||
|
||||
ts-xor@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-xor/-/ts-xor-1.3.0.tgz#3e59f24f0321f9f10f350e0cee3b534b89a2c70b"
|
||||
integrity sha512-RLXVjliCzc1gfKQFLRpfeD0rrWmjnSTgj7+RFhoq3KRkUYa8LE/TIidYOzM5h+IdFBDSjjSgk9Lto9sdMfDFEA==
|
||||
|
||||
tslib@^1.8.1:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
@ -8177,12 +8028,12 @@ vary@^1, vary@~1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
||||
|
||||
vaul@^0.7.0:
|
||||
version "0.7.9"
|
||||
resolved "https://registry.yarnpkg.com/vaul/-/vaul-0.7.9.tgz#365dfe8f6c1df3a81a26508474db0e0ceb98ac8c"
|
||||
integrity sha512-RrcnGOHOq/cEU3YpyyZrnjh0H79xMpF3IrHZs9ichvHlpKjLDc4Vwjn4VkuGzeUGrmQ3wamfm/cpdKWpvBIgQw==
|
||||
vaul@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/vaul/-/vaul-1.1.1.tgz#93aceaad16f7c53aacf28a2609b2dd43b5a91fa0"
|
||||
integrity sha512-+ejzF6ffQKPcfgS7uOrGn017g39F8SO4yLPXbBhpC7a0H+oPqPna8f1BUfXaz8eU4+pxbQcmjxW+jWBSbxjaFg==
|
||||
dependencies:
|
||||
"@radix-ui/react-dialog" "^1.0.4"
|
||||
"@radix-ui/react-dialog" "^1.1.1"
|
||||
|
||||
verror@1.10.0:
|
||||
version "1.10.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user