Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adopt jsonrpc-lite package #671

Merged
merged 1 commit into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deno-runtime/deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"acorn": "npm:[email protected]",
"acorn-walk": "npm:[email protected]",
"astring": "npm:[email protected]"
"jsonrpc-lite": "npm:[email protected]",
}
}
7 changes: 6 additions & 1 deletion deno-runtime/deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

151 changes: 67 additions & 84 deletions deno-runtime/lib/messenger.ts
Original file line number Diff line number Diff line change
@@ -1,113 +1,90 @@
export type JSONRPC_Message = {
jsonrpc: '2.0-rc';
};

export type RequestDescriptor = {
method: string;
params: any[];
};

export type Request = JSONRPC_Message &
RequestDescriptor & {
id: string;
};

export type SuccessResponseDescriptor = {
id: string;
result: any;
};

export type SuccessResponse = JSONRPC_Message & SuccessResponseDescriptor;

export type ErrorResponseDescriptor = {
error: {
code: number;
message: string;
data?: Record<string, unknown>;
};
id: string | null;
};

export type ErrorResponse = JSONRPC_Message & ErrorResponseDescriptor;

export type Response = SuccessResponse | ErrorResponse;

export function isJSONRPCMessage(message: object): message is JSONRPC_Message {
return 'jsonrpc' in message && message['jsonrpc'] === '2.0-rc';
}
import * as jsonrpc from 'jsonrpc-lite';

export function isRequest(message: object): message is Request {
return isJSONRPCMessage(message) && 'method' in message && 'params' in message && 'id' in message;
}
export type RequestDescriptor = Pick<jsonrpc.RequestObject, 'method' | 'params'>;

export type NotificationDescriptor = Pick<jsonrpc.NotificationObject, 'method' | 'params'>;

export type SuccessResponseDescriptor = Pick<jsonrpc.SuccessObject, 'id' | 'result'>;

export function isResponse(message: object): message is Response {
return isJSONRPCMessage(message) && ('result' in message || 'error' in message);
export type ErrorResponseDescriptor = Pick<jsonrpc.ErrorObject, 'id' | 'error'>;

export type JsonRpcRequest = jsonrpc.IParsedObjectRequest | jsonrpc.IParsedObjectNotification;
export type JsonRpcResponse = jsonrpc.IParsedObjectSuccess | jsonrpc.IParsedObjectError;

export function isRequest(message: jsonrpc.IParsedObject): message is JsonRpcRequest {
return message.type === 'request' || message.type === 'notification';
}

export function isErrorResponse(response: Response): response is ErrorResponse {
return 'error' in response;
export function isResponse(message: jsonrpc.IParsedObject): message is JsonRpcResponse {
return message.type === 'success' || message.type === 'error';
}

export function isSuccessResponse(response: Response): response is SuccessResponse {
return 'result' in response;
export function isErrorResponse(message: jsonrpc.JsonRpc): message is jsonrpc.ErrorObject {
return message instanceof jsonrpc.ErrorObject;
}

const encoder = new TextEncoder();
export const RPCResponseObserver = new EventTarget();

export async function serverParseError(): Promise<void> {
const rpc: ErrorResponse = {
jsonrpc: '2.0-rc',
id: null,
error: { message: 'Parse error', code: -32700 },
};
export function parseMessage(message: string) {
const parsed = jsonrpc.parse(message);

if (Array.isArray(parsed)) {
throw jsonrpc.error(null, jsonrpc.JsonRpcError.invalidRequest(null));
}

const encoded = encoder.encode(JSON.stringify(rpc));
if (parsed.type === 'invalid') {
throw jsonrpc.error(null, parsed.payload);
}

return parsed;
}

export async function send(message: jsonrpc.JsonRpc): Promise<void> {
const encoded = encoder.encode(message.serialize());
await Deno.stdout.write(encoded);
}

export async function serverMethodNotFound(id: string): Promise<void> {
const rpc: ErrorResponse = {
jsonrpc: '2.0-rc',
id,
error: { message: 'Method not found', code: -32601 },
};
export async function sendInvalidRequestError(): Promise<void> {
const rpc = jsonrpc.error(null, jsonrpc.JsonRpcError.invalidRequest(null));

const encoded = encoder.encode(JSON.stringify(rpc));
await Deno.stdout.write(encoded);
await send(rpc);
}

export async function sendInvalidParamsError(id: jsonrpc.ID): Promise<void> {
const rpc = jsonrpc.error(id, jsonrpc.JsonRpcError.invalidParams(null));

await send(rpc);
}

export async function sendParseError(): Promise<void> {
const rpc = jsonrpc.error(null, jsonrpc.JsonRpcError.parseError(null));

await send(rpc);
}

export async function sendMethodNotFound(id: jsonrpc.ID): Promise<void> {
const rpc = jsonrpc.error(id, jsonrpc.JsonRpcError.methodNotFound(null));

await send(rpc);
}

export async function errorResponse({ error: { message, code = -32000, data }, id }: ErrorResponseDescriptor): Promise<void> {
const rpc: ErrorResponse = {
jsonrpc: '2.0-rc',
id,
error: { message, code, ...(data && { data }) },
};

const encoded = encoder.encode(JSON.stringify(rpc));
Deno.stdout.write(encoded);
const rpc = jsonrpc.error(id, new jsonrpc.JsonRpcError(message, code, data));

await send(rpc);
}

export async function successResponse({ id, result }: SuccessResponseDescriptor): Promise<void> {
const rpc: SuccessResponse = {
jsonrpc: '2.0-rc',
id,
result,
};
const rpc = jsonrpc.success(id, result);

const encoded = encoder.encode(JSON.stringify(rpc));
await Deno.stdout.write(encoded);
await send(rpc);
}

export async function sendRequest(requestDescriptor: RequestDescriptor): Promise<Request> {
const request: Request = {
jsonrpc: '2.0-rc',
id: Math.random().toString(36).slice(2),
...requestDescriptor,
};
export async function sendRequest(requestDescriptor: RequestDescriptor): Promise<jsonrpc.SuccessObject> {
const request = jsonrpc.request(Math.random().toString(36).slice(2), requestDescriptor.method, requestDescriptor.params);

const encoded = encoder.encode(JSON.stringify(request));
await Deno.stdout.write(encoded);
await send(request);

return new Promise((resolve, reject) => {
const handler = (event: Event) => {
Expand All @@ -125,3 +102,9 @@ export async function sendRequest(requestDescriptor: RequestDescriptor): Promise
RPCResponseObserver.addEventListener(`response:${request.id}`, handler);
});
}

export function sendNotification({ method, params }: NotificationDescriptor) {
const request = jsonrpc.notification(method, params);

send(request);
}
54 changes: 32 additions & 22 deletions deno-runtime/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ const require = createRequire(import.meta.url);
// @deno-types='../definition/App.d.ts'
const { App } = require('../definition/App');

async function notifyEngine(notify: Record<string, unknown>): Promise<void> {
const encoder = new TextEncoder();
const encoded = encoder.encode(JSON.stringify(notify));
await Deno.stdout.write(encoded);
return undefined;
}

const ALLOWED_NATIVE_MODULES = ['path', 'url', 'crypto', 'buffer', 'stream', 'net', 'http', 'https', 'zlib', 'util', 'punycode', 'os', 'querystring'];
const ALLOWED_EXTERNAL_MODULES = ['uuid'];

Expand Down Expand Up @@ -95,12 +88,24 @@ async function handlInitializeApp({ id, source }: { id: string; source: string }
return app;
}

async function handleRequest({ method, params, id }: Messenger.Request): Promise<void> {
async function handleRequest({ type, payload }: Messenger.JsonRpcRequest): Promise<void> {
// We're not handling notifications at the moment
if (type === 'notification') {
return Messenger.sendInvalidRequestError();
}

const { id, method, params } = payload;

switch (method) {
case 'construct': {
const [appId, source] = params;
app = await handlInitializeApp({ id: appId, source })
Messenger.successResponse(id, { result: "hooray!" });
const [appId, source] = params as [string, string];

if (!appId || !source) {
return Messenger.sendInvalidParamsError(id);
}

const app = await handlInitializeApp({ id: appId, source })
Messenger.successResponse({ id, result: 'hooray' });
break;
}
default: {
Expand All @@ -113,40 +118,45 @@ async function handleRequest({ method, params, id }: Messenger.Request): Promise
}
}

async function handleResponse(response: Messenger.Response): Promise<void> {
function handleResponse(response: Messenger.JsonRpcResponse): void {
let event: Event;

if (Messenger.isErrorResponse(response)) {
event = new ErrorEvent(`response:${response.id}`, { error: response.error });
if (response.type === 'error') {
event = new ErrorEvent(`response:${response.payload.id}`, { error: response.payload.error });
} else {
event = new CustomEvent(`response:${response.id}`, { detail: response.result });
event = new CustomEvent(`response:${response.payload.id}`, { detail: response.payload.result });
}

Messenger.RPCResponseObserver.dispatchEvent(event);
}

async function main() {
setTimeout(() => notifyEngine({ method: 'ready' }), 1_780);
setTimeout(() => Messenger.sendNotification({ method: 'ready' }), 1_780);

const decoder = new TextDecoder();
let app: typeof App;

for await (const chunk of Deno.stdin.readable) {
const message = decoder.decode(chunk);
let JSONRPCMessage
let JSONRPCMessage;

try {
JSONRPCMessage = JSON.parse(message);
} catch (_) {
return Messenger.serverParseError();
JSONRPCMessage = Messenger.parseMessage(message);
} catch (error) {
if (Messenger.isErrorResponse(error)) {
await Messenger.send(error);
} else {
await Messenger.sendParseError();
}

continue;
}

if (Messenger.isRequest(JSONRPCMessage)) {
await handleRequest(JSONRPCMessage);
}

if (Messenger.isResponse(JSONRPCMessage)) {
await handleResponse(JSONRPCMessage);
handleResponse(JSONRPCMessage);
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@
"dependencies": {
"adm-zip": "^0.5.9",
"cryptiles": "^4.1.3",
"deno-bin": "^1.36.2",
"deno-bin": "1.36.2",
"jose": "^4.11.1",
"jsonrpc-lite": "^2.2.0",
"lodash.clonedeep": "^4.5.0",
"semver": "^5.7.1",
"stack-trace": "0.0.10",
Expand Down
Loading