mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 13:17:08 +00:00
Make it work
This commit is contained in:
parent
9795d7ffb0
commit
91f8392a5d
@ -271,6 +271,9 @@ export interface BridgeGenericWebhooksConfigYAML {
|
||||
urlPrefix: string;
|
||||
userIdPrefix?: string;
|
||||
allowJsTransformationFunctions?: boolean;
|
||||
transformationFeatures?: {
|
||||
allowloadMatrixScript?: boolean;
|
||||
};
|
||||
waitForComplete?: boolean;
|
||||
enableHttpGet?: boolean;
|
||||
}
|
||||
@ -286,6 +289,11 @@ export class BridgeConfigGenericWebhooks {
|
||||
public readonly allowJsTransformationFunctions?: boolean;
|
||||
public readonly waitForComplete?: boolean;
|
||||
public readonly enableHttpGet: boolean;
|
||||
|
||||
public readonly transformationFeatures?: {
|
||||
allowloadMatrixScript?: boolean;
|
||||
};
|
||||
|
||||
constructor(yaml: BridgeGenericWebhooksConfigYAML) {
|
||||
this.enabled = yaml.enabled || false;
|
||||
this.enableHttpGet = yaml.enableHttpGet || false;
|
||||
@ -298,6 +306,7 @@ export class BridgeConfigGenericWebhooks {
|
||||
this.userIdPrefix = yaml.userIdPrefix;
|
||||
this.allowJsTransformationFunctions = yaml.allowJsTransformationFunctions;
|
||||
this.waitForComplete = yaml.waitForComplete;
|
||||
this.transformationFeatures = yaml.transformationFeatures;
|
||||
}
|
||||
|
||||
@hideKey()
|
||||
|
@ -36,7 +36,7 @@ export interface GenericHookSecrets {
|
||||
|
||||
export type GenericHookResponseItem = GetConnectionsResponseItem<GenericHookConnectionState, GenericHookSecrets>;
|
||||
|
||||
const MatrixUriEventRegex = /^matrix:(roomid|r)\/(.+:.+)\/[a-zA-Z0-9+/]+/;
|
||||
const MatrixUriEventRegex = /^matrix:(roomid|r)\/(.+:.+)\/(\$.+)/;
|
||||
|
||||
/** */
|
||||
export interface GenericHookAccountData {
|
||||
@ -61,6 +61,8 @@ const TRANSFORMATION_TIMEOUT_MS = 500;
|
||||
const SANITIZE_MAX_DEPTH = 5;
|
||||
const SANITIZE_MAX_BREADTH = 25;
|
||||
|
||||
const SCRIPT_EXECUTE_LIMIT = 10;
|
||||
|
||||
/**
|
||||
* Handles rooms connected to a generic webhook.
|
||||
*/
|
||||
@ -201,15 +203,20 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
private readonly messageClient: MessageSenderClient,
|
||||
private readonly config: BridgeConfigGenericWebhooks,
|
||||
private readonly as: Appservice) {
|
||||
super(roomId, stateKey, GenericHookConnection.CanonicalEventType);
|
||||
if (state.transformationFunction && config.allowJsTransformationFunctions) {
|
||||
this.transformationFunction = new Script(state.transformationFunction);
|
||||
}
|
||||
super(roomId, stateKey, GenericHookConnection.CanonicalEventType);
|
||||
if (state.transformationFunction && config.allowJsTransformationFunctions) {
|
||||
this.transformationFunction = GenericHookConnection.compileScript(state.transformationFunction);
|
||||
}
|
||||
}
|
||||
|
||||
public get priority(): number {
|
||||
return this.state.priority || super.priority;
|
||||
}
|
||||
public static compileScript(scriptSrc: string) {
|
||||
// We do this so that any `await` calls at the top level work.
|
||||
return new Script(`return (async () => { ${scriptSrc} })();`);
|
||||
}
|
||||
|
||||
public get priority(): number {
|
||||
return this.state.priority || super.priority;
|
||||
}
|
||||
|
||||
|
||||
public isInterestedInStateEvent(eventType: string, stateKey: string) {
|
||||
@ -257,7 +264,7 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
const validatedConfig = GenericHookConnection.validateState(stateEv.content as Record<string, unknown>, this.config.allowJsTransformationFunctions || false);
|
||||
if (validatedConfig.transformationFunction) {
|
||||
try {
|
||||
this.transformationFunction = new Script(validatedConfig.transformationFunction);
|
||||
this.transformationFunction = GenericHookConnection.compileScript(validatedConfig.transformationFunction);
|
||||
} catch (ex) {
|
||||
await this.messageClient.sendMatrixText(this.roomId, 'Could not compile transformation function:' + ex);
|
||||
}
|
||||
@ -294,8 +301,30 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
// TODO: Transform Slackdown into markdown.
|
||||
return msg;
|
||||
}
|
||||
|
||||
public async loadMatrixScript(scriptPath: string) {
|
||||
if (typeof scriptPath !== "string") {
|
||||
throw Error('loadMatrixScript takes a string')
|
||||
}
|
||||
const matrixUri = MatrixUriEventRegex.exec(scriptPath);
|
||||
if (!matrixUri) {
|
||||
throw Error('Not a valid matrix path. Use the event URI scheme from https://spec.matrix.org/v1.3/appendices/#matrix-uri-scheme');
|
||||
}
|
||||
const [ _, type, prefixlessRoomId, eventId ] = matrixUri;
|
||||
let roomId = "!" + prefixlessRoomId;
|
||||
if (type === "r") {
|
||||
// RoomAlias -> resolve
|
||||
roomId = await this.as.botClient.resolveRoom("#" + prefixlessRoomId);
|
||||
}
|
||||
const eventData = (await this.as.botClient.getEvent(roomId, eventId)).content as GenericHookConnectionState;
|
||||
|
||||
public executeTransformationFunction(data: unknown): {plain: string, html?: string, msgtype?: string}|null {
|
||||
if (typeof eventData.transformationFunction !== "string") {
|
||||
throw Error('Event did not contain a transformation function!');
|
||||
}
|
||||
return eventData.transformationFunction;
|
||||
}
|
||||
|
||||
public async executeTransformationFunction(data: unknown): Promise<{plain: string, html?: string, msgtype?: string}|null> {
|
||||
if (!this.transformationFunction) {
|
||||
throw Error('Transformation function not defined');
|
||||
}
|
||||
@ -306,31 +335,37 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
eval: false,
|
||||
timeout: TRANSFORMATION_TIMEOUT_MS,
|
||||
});
|
||||
|
||||
vm.setGlobal('HookshotApiVersion', 'v2');
|
||||
vm.setGlobal('data', data);
|
||||
vm.setGlobal('loadMatrixScript', async (scriptPath: string) => {
|
||||
if (typeof scriptPath !== "string") {
|
||||
throw Error('loadMatrixScript takes a string')
|
||||
}
|
||||
const matrixUri = MatrixUriEventRegex.exec(scriptPath);
|
||||
if (!matrixUri) {
|
||||
throw Error('Not a valid matrix path. Use the event URI scheme from https://spec.matrix.org/v1.3/appendices/#matrix-uri-scheme');
|
||||
}
|
||||
let roomId = "!" + matrixUri[2];
|
||||
if (matrixUri[1] === "r") {
|
||||
// RoomAlias -> resolve
|
||||
roomId = await this.as.botClient.resolveRoom("#" + matrixUri[2]);
|
||||
}
|
||||
const eventData = await this.as.botClient.getEvent(roomId, matrixUri[3]);
|
||||
if (typeof eventData.content.transformationFunction !== "string") {
|
||||
throw Error('Event did not contain a transformation function!');
|
||||
}
|
||||
// Add a callback to run the script in a seperate context.
|
||||
return () => {
|
||||
vm.run(eventData.content.transformationFunction);
|
||||
}
|
||||
});
|
||||
vm.run(this.transformationFunction);
|
||||
|
||||
if (this.config.transformationFeatures?.allowloadMatrixScript) {
|
||||
let executions = 0;
|
||||
vm.setGlobal('loadMatrixScript', async (scriptPath: string) => {
|
||||
// TODO: Cache scripts.
|
||||
// TODO: Hot-path scripts which we already have loaded in from other connections.
|
||||
// Prevent a script from nesting too hard.
|
||||
if (executions > SCRIPT_EXECUTE_LIMIT) {
|
||||
throw Error('Execution limit for loadMatrixScript reached');
|
||||
}
|
||||
executions++;
|
||||
const script = await this.loadMatrixScript(scriptPath);
|
||||
const innerVM = new NodeVM({
|
||||
console: 'off',
|
||||
wrapper: 'none',
|
||||
wasm: false,
|
||||
eval: false,
|
||||
timeout: TRANSFORMATION_TIMEOUT_MS,
|
||||
});
|
||||
innerVM.setGlobal('HookshotApiVersion', 'v2');
|
||||
innerVM.setGlobal('data', data);
|
||||
await innerVM.run(script);
|
||||
return innerVM.getGlobal('result');
|
||||
});
|
||||
}
|
||||
|
||||
const script = this.transformationFunction;
|
||||
await vm.run(script);
|
||||
const result = vm.getGlobal('result');
|
||||
|
||||
// Legacy v1 api
|
||||
@ -379,7 +414,7 @@ export class GenericHookConnection extends BaseConnection implements IConnection
|
||||
content = this.transformHookData(data);
|
||||
} else {
|
||||
try {
|
||||
const potentialContent = this.executeTransformationFunction(data);
|
||||
const potentialContent = await this.executeTransformationFunction(data);
|
||||
if (potentialContent === null) {
|
||||
// Explitly no action
|
||||
return true;
|
||||
|
Loading…
x
Reference in New Issue
Block a user