Skip to content

Commit 6a4ec10

Browse files
committed
refactor(apps): drop Deno specific APIs in deno-runtime
1 parent 9e16c96 commit 6a4ec10

9 files changed

Lines changed: 98 additions & 75 deletions

File tree

packages/apps/deno-runtime/deno.jsonc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"@rocket.chat/apps/": "../",
77
"@std/cli": "jsr:@std/cli@^1.0.9",
88
"@std/io": "jsr:@std/io@^0.225.3",
9-
"@std/streams": "jsr:@std/streams@^1.0.16",
109
"acorn": "npm:acorn@8.10.0",
1110
"acorn-walk": "npm:acorn-walk@8.2.0",
1211
"astring": "npm:astring@1.8.6",
@@ -16,7 +15,7 @@
1615
},
1716
"unstable": ["detect-cjs","sloppy-imports"],
1817
"tasks": {
19-
"test": "deno test --no-check --allow-read --allow-write"
18+
"test": "deno test --no-check --allow-read --allow-write --allow-env"
2019
},
2120
"fmt": {
2221
"lineWidth": 160,

packages/apps/deno-runtime/deno.lock

Lines changed: 0 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/apps/deno-runtime/handlers/app/handleUploadEvents.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { Buffer } from 'node:buffer';
1+
import { open } from 'node:fs/promises';
22

33
import type { App } from '@rocket.chat/apps-engine/definition/App';
44
import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions/AppsEngineException';
55
import type { IFileUploadContext } from '@rocket.chat/apps-engine/definition/uploads/IFileUploadContext'
66
import type { IUploadDetails } from '@rocket.chat/apps-engine/definition/uploads/IUploadDetails'
7-
import { toArrayBuffer } from '@std/streams';
87
import { Defined, JsonRpcError } from 'jsonrpc-lite';
98

109
import { AppObjectRegistry } from '../../AppObjectRegistry';
@@ -42,25 +41,29 @@ export default async function handleUploadEvents(request: RequestContext): Promi
4241
assertIsUpload(file);
4342
assertString(path);
4443

45-
using tempFile = await Deno.open(path, { read: true, create: false });
46-
let context: IFileUploadContext;
44+
const tempFile = await open(path, 'r');
4745

48-
switch (method) {
49-
case 'executePreFileUpload': {
50-
const fileContents = await toArrayBuffer(tempFile.readable);
51-
context = { file, content: Buffer.from(fileContents) };
52-
break;
46+
try {
47+
let context: IFileUploadContext;
48+
49+
switch (method) {
50+
case 'executePreFileUpload': {
51+
context = { file, content: await tempFile.readFile() };
52+
break;
53+
}
5354
}
54-
}
5555

56-
return await handlerFunction.call(
57-
wrapAppForRequest(app, request),
58-
context,
59-
AppAccessorsInstance.getReader(),
60-
AppAccessorsInstance.getHttp(),
61-
AppAccessorsInstance.getPersistence(),
62-
AppAccessorsInstance.getModifier(),
63-
);
56+
return await handlerFunction.call(
57+
wrapAppForRequest(app, request),
58+
context,
59+
AppAccessorsInstance.getReader(),
60+
AppAccessorsInstance.getHttp(),
61+
AppAccessorsInstance.getPersistence(),
62+
AppAccessorsInstance.getModifier(),
63+
);
64+
} finally {
65+
await tempFile.close();
66+
}
6467
} catch(e) {
6568
if (e?.name === AppsEngineException.name) {
6669
return new JsonRpcError(e.message, AppsEngineException.JSONRPC_ERROR_CODE, { name: e.name });

packages/apps/deno-runtime/handlers/tests/upload-event-handler.test.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
// deno-lint-ignore-file no-explicit-any
22
import { Buffer } from 'node:buffer';
3+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
4+
import { tmpdir } from 'node:os';
5+
import { join } from 'node:path';
36

47
import type { App } from '@rocket.chat/apps-engine/definition/App';
58
import type { IPreFileUpload } from '@rocket.chat/apps-engine/definition/uploads/IPreFileUpload';
@@ -16,13 +19,15 @@ import { AppObjectRegistry } from '../../AppObjectRegistry';
1619

1720
describe('handlers > upload', () => {
1821
let app: App & IPreFileUpload;
22+
let tempDir: string;
1923
let path: string;
2024
let file: IUploadDetails;
2125

2226
beforeEach(async () => {
2327
AppObjectRegistry.clear();
2428

25-
path = await Deno.makeTempFile();
29+
tempDir = await mkdtemp(join(tmpdir(), 'rc-apps-upload-'));
30+
path = join(tempDir, 'tempfile');
2631

2732
app = {
2833
extendConfiguration: () => {},
@@ -33,7 +38,7 @@ describe('handlers > upload', () => {
3338

3439
const content = 'Temp file for testing';
3540

36-
await Deno.writeTextFile(path, content);
41+
await writeFile(path, content);
3742

3843
file = {
3944
name: 'TempFile.txt',
@@ -45,7 +50,7 @@ describe('handlers > upload', () => {
4550
});
4651

4752
afterEach(async () => {
48-
await Deno.remove(path).catch((e) => e?.code !== 'ENOENT' && console.warn(`Failed to remove temp file at ${path}`, e));
53+
await rm(tempDir, { recursive: true, force: true }).catch((e) => console.warn(`Failed to remove temp dir at ${tempDir}`, e));
4954
});
5055

5156
it('correctly handles valid parameters', async () => {
@@ -97,7 +102,7 @@ describe('handlers > upload', () => {
97102
});
98103

99104
it('fails when "path" is not a readable file path', async () => {
100-
await Deno.remove(path);
105+
await rm(path);
101106

102107
const result = await handleUploadEvents(createMockRequest({ method: 'app:executePreFileUpload', params: [{ file, path }] }));
103108

packages/apps/deno-runtime/lib/messenger.ts

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { writeAll } from '@std/io';
2-
31
import * as jsonrpc from 'jsonrpc-lite';
42

53
import { encoder } from './codec';
@@ -47,7 +45,7 @@ export const Queue = new (class Queue {
4745
const message = this.queue.shift();
4846

4947
if (message) {
50-
await Transport.send(message);
48+
await transport.send(message);
5149
}
5250
}
5351

@@ -64,34 +62,36 @@ export const Queue = new (class Queue {
6462
}
6563
})();
6664

67-
export const Transport = new (class Transporter {
68-
private selectedTransport: Transporter['stdoutTransport'] | Transporter['noopTransport'];
69-
70-
constructor() {
71-
this.selectedTransport = this.stdoutTransport.bind(this);
72-
}
73-
74-
private async stdoutTransport(message: Uint8Array): Promise<void> {
75-
await writeAll(Deno.stdout, message);
76-
}
77-
78-
private async noopTransport(_message: Uint8Array): Promise<void> {}
79-
80-
public selectTransport(transport: 'stdout' | 'noop'): void {
81-
switch (transport) {
82-
case 'stdout':
83-
this.selectedTransport = this.stdoutTransport.bind(this);
84-
break;
85-
case 'noop':
86-
this.selectedTransport = this.noopTransport.bind(this);
87-
break;
88-
}
89-
}
90-
91-
public send(message: Uint8Array): Promise<void> {
92-
return this.selectedTransport(message);
93-
}
94-
})();
65+
/**
66+
* A platform-dependent component responsible for delivering encoded messages to
67+
* the host that controls this runtime.
68+
*
69+
* Each runtime platform is expected to provide its own implementation and
70+
* inject it via {@link setTransport}.
71+
*/
72+
export type Transport = {
73+
send(message: Uint8Array): Promise<void>;
74+
};
75+
76+
/**
77+
* The default transport. It discards every message, and is used until a
78+
* platform injects its own transport via {@link setTransport}.
79+
*/
80+
export const noopTransport: Transport = {
81+
send: () => Promise.resolve(),
82+
};
83+
84+
let transport: Transport = noopTransport;
85+
86+
/**
87+
* Injects the transport implementation to be used when sending messages.
88+
*
89+
* Platforms must call this during bootstrap to wire up the appropriate
90+
* transport. Until then, messages are discarded by the default no-op transport.
91+
*/
92+
export function setTransport(newTransport: Transport): void {
93+
transport = newTransport;
94+
}
9595

9696
export function parseMessage(message: string | Record<string, unknown>) {
9797
let parsed: jsonrpc.IParsedObject | jsonrpc.IParsedObject[];

packages/apps/deno-runtime/lib/metricsCollector.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { writeAll } from '@std/io';
1+
import process from 'node:process';
2+
23
import { Queue } from './messenger';
34

45
export function collectMetrics() {
56
return {
6-
pid: Deno.pid,
7+
pid: process.pid,
78
queueSize: Queue.getCurrentSize(),
89
};
910
}
@@ -16,5 +17,7 @@ const encoder = new TextEncoder();
1617
export async function sendMetrics() {
1718
const metrics = collectMetrics();
1819

19-
await writeAll(Deno.stderr, encoder.encode(JSON.stringify(metrics)));
20+
await new Promise<void>((resolve, reject) => {
21+
process.stderr.write(encoder.encode(JSON.stringify(metrics)), (error) => (error ? reject(error) : resolve()));
22+
});
2023
}

packages/apps/deno-runtime/lib/tests/messenger.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { afterAll, beforeEach, describe, it } from 'https://deno.land/std@0.203.
33
import { spy } from 'https://deno.land/std@0.203.0/testing/mock.ts';
44

55
import * as Messenger from '../messenger';
6+
import { stdoutTransport } from '../transports/stdoutTransport';
67
import { AppObjectRegistry } from '../../AppObjectRegistry';
78
import { createMockRequest } from '../../handlers/tests/helpers/mod';
89
import { RequestContext } from '../requestContext';
@@ -14,14 +15,14 @@ describe('Messenger', () => {
1415
beforeEach(() => {
1516
AppObjectRegistry.clear();
1617
AppObjectRegistry.set('id', 'test');
17-
Messenger.Transport.selectTransport('noop');
18+
Messenger.setTransport(Messenger.noopTransport);
1819

1920
context = createMockRequest({ method: 'test', params: [] });
2021
});
2122

2223
afterAll(() => {
2324
AppObjectRegistry.clear();
24-
Messenger.Transport.selectTransport('stdout');
25+
Messenger.setTransport(stdoutTransport);
2526
});
2627

2728
it('should add logs to success responses', async () => {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { writeAll } from '@std/io';
2+
3+
import type { Transport } from '../messenger';
4+
5+
/**
6+
* Transport that writes messages to the process' standard output.
7+
*
8+
* This is the transport used when the runtime is executed as a subprocess by
9+
* the Apps-Engine framework, and it is specific to platforms that expose a
10+
* Node-compatible `process.stdout`.
11+
*/
12+
export const stdoutTransport: Transport = {
13+
send(message: Uint8Array): Promise<void> {
14+
return writeAll(Deno.stdout, message);
15+
},
16+
};

packages/apps/deno-runtime/main.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
if (!Deno.args.includes('--subprocess')) {
2-
Deno.stderr.writeSync(
3-
new TextEncoder().encode(`
1+
import process from 'node:process';
2+
3+
if (!process.argv.includes('--subprocess')) {
4+
process.stderr.write(`
45
This is a Deno wrapper for Rocket.Chat Apps. It is not meant to be executed stand-alone;
56
It is instead meant to be executed as a subprocess by the Apps-Engine framework.
6-
`),
7-
);
8-
Deno.exit(1001);
7+
`);
8+
process.exit(1001);
99
}
1010

1111
import { JsonRpcError } from 'jsonrpc-lite';
1212

1313
import * as Messenger from './lib/messenger';
14+
import { stdoutTransport } from './lib/transports/stdoutTransport';
1415
import { decoder } from './lib/codec';
1516
import { Logger } from './lib/logger';
1617

@@ -101,7 +102,7 @@ function handleResponse(response: Messenger.JsonRpcResponse): void {
101102
async function main() {
102103
Messenger.sendNotification({ method: 'ready' });
103104

104-
for await (const message of decoder.decodeStream(Deno.stdin.readable)) {
105+
for await (const message of decoder.decodeStream(process.stdin)) {
105106
try {
106107
// Process PING command first as it is not JSON RPC
107108
if (message === COMMAND_PING) {
@@ -130,6 +131,9 @@ async function main() {
130131
}
131132
}
132133

134+
// This runtime communicates with the Apps-Engine host through stdout
135+
Messenger.setTransport(stdoutTransport);
136+
133137
registerErrorListeners();
134138

135139
main();

0 commit comments

Comments
 (0)