166 lines
4.2 KiB
TypeScript
166 lines
4.2 KiB
TypeScript
|
|
import { connect as connectNats, jwtAuthenticator, type NatsConnection } from 'nats';
|
||
|
|
import {
|
||
|
|
MatrixActor,
|
||
|
|
MatrixRuntime,
|
||
|
|
NatsTransport,
|
||
|
|
TopicRouter,
|
||
|
|
exchangeHiveCastActorCredential,
|
||
|
|
} from '@open-matrix/sdk';
|
||
|
|
|
||
|
|
class FreshServerEchoActor extends MatrixActor {
|
||
|
|
static accepts = {
|
||
|
|
echo: {},
|
||
|
|
getStatus: {},
|
||
|
|
};
|
||
|
|
|
||
|
|
onEcho(payload: { message?: string }) {
|
||
|
|
return {
|
||
|
|
ok: true,
|
||
|
|
message: payload.message ?? null,
|
||
|
|
actor: 'FreshServerEchoActor',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
onGetStatus() {
|
||
|
|
return {
|
||
|
|
ok: true,
|
||
|
|
actor: 'FreshServerEchoActor',
|
||
|
|
mode: 'server',
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function env(name: string): string | undefined {
|
||
|
|
const value = process.env[name];
|
||
|
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
function requiredEnv(name: string): string {
|
||
|
|
const value = env(name);
|
||
|
|
if (!value) throw new Error(`${name}_REQUIRED`);
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
|
||
|
|
function connectWithJwt(input: {
|
||
|
|
readonly natsUrl: string;
|
||
|
|
readonly root: string;
|
||
|
|
readonly jwt: string;
|
||
|
|
readonly seed: string;
|
||
|
|
}): Promise<NatsConnection> {
|
||
|
|
const encoder = new TextEncoder();
|
||
|
|
return connectNats({
|
||
|
|
servers: [input.natsUrl],
|
||
|
|
name: `matrix-sdk-fresh-server-${input.root}-${process.pid}`,
|
||
|
|
timeout: 5000,
|
||
|
|
authenticator: jwtAuthenticator(
|
||
|
|
() => input.jwt,
|
||
|
|
() => encoder.encode(input.seed),
|
||
|
|
),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async function request(
|
||
|
|
transport: NatsTransport,
|
||
|
|
mount: string,
|
||
|
|
op: string,
|
||
|
|
payload: unknown,
|
||
|
|
): Promise<unknown> {
|
||
|
|
const correlationId = `fresh-server-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||
|
|
const replyTo = `$replies.${correlationId}`;
|
||
|
|
let unsubscribe = () => {};
|
||
|
|
const response = new Promise<unknown>((resolve, reject) => {
|
||
|
|
const timeout = setTimeout(() => {
|
||
|
|
unsubscribe();
|
||
|
|
reject(new Error(`REQUEST_TIMEOUT: ${mount}.${op}`));
|
||
|
|
}, 5000);
|
||
|
|
unsubscribe = transport.subscribe(replyTo, (value) => {
|
||
|
|
clearTimeout(timeout);
|
||
|
|
unsubscribe();
|
||
|
|
resolve(value);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
transport.publish(TopicRouter.inbox(mount), {
|
||
|
|
op,
|
||
|
|
payload,
|
||
|
|
replyTo,
|
||
|
|
correlationId,
|
||
|
|
});
|
||
|
|
|
||
|
|
return response;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function main(): Promise<void> {
|
||
|
|
const check = process.argv.includes('--check');
|
||
|
|
const cloud = requiredEnv('MATRIX_CLOUD');
|
||
|
|
const apiKey = requiredEnv('MATRIX_API_KEY');
|
||
|
|
const space = requiredEnv('MATRIX_SPACE');
|
||
|
|
const mount = env('MATRIX_MOUNT') ?? 'fresh.server.echo';
|
||
|
|
|
||
|
|
const exchanged = await exchangeHiveCastActorCredential({
|
||
|
|
cloud,
|
||
|
|
apiKey,
|
||
|
|
space,
|
||
|
|
}, {
|
||
|
|
mount,
|
||
|
|
actorId: 'FreshServerEchoActor',
|
||
|
|
packageId: 'matrix-sdk-example-fresh-server-actor',
|
||
|
|
runtimeId: `fresh-server-${process.pid}`,
|
||
|
|
accepts: FreshServerEchoActor.accepts,
|
||
|
|
ttlSeconds: 300,
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!exchanged.transport.natsUrl) {
|
||
|
|
throw new Error('HiveCast actor credential response did not include transport.natsUrl');
|
||
|
|
}
|
||
|
|
|
||
|
|
const connection = await connectWithJwt({
|
||
|
|
natsUrl: exchanged.transport.natsUrl,
|
||
|
|
root: exchanged.root,
|
||
|
|
jwt: exchanged.credentials.jwt,
|
||
|
|
seed: exchanged.credentials.seed,
|
||
|
|
});
|
||
|
|
const transport = new NatsTransport(connection, { root: exchanged.root });
|
||
|
|
const runtime = new MatrixRuntime({ transport, logging: false });
|
||
|
|
|
||
|
|
try {
|
||
|
|
await runtime.create(FreshServerEchoActor, mount);
|
||
|
|
await connection.flush();
|
||
|
|
const result = check
|
||
|
|
? await request(transport, mount, 'echo', { message: 'hello-from-fresh-server-demo' })
|
||
|
|
: undefined;
|
||
|
|
|
||
|
|
console.log(JSON.stringify({
|
||
|
|
ok: true,
|
||
|
|
demo: 'fresh-server-actor',
|
||
|
|
cloud,
|
||
|
|
space,
|
||
|
|
root: exchanged.root,
|
||
|
|
mount,
|
||
|
|
natsUrl: exchanged.transport.natsUrl,
|
||
|
|
credentialPipeline: exchanged.credentialPipeline,
|
||
|
|
...(result ? { check: result } : {}),
|
||
|
|
}, null, 2));
|
||
|
|
|
||
|
|
if (!check) {
|
||
|
|
process.once('SIGINT', () => {
|
||
|
|
void runtime.shutdown().finally(() => process.exit(0));
|
||
|
|
});
|
||
|
|
process.once('SIGTERM', () => {
|
||
|
|
void runtime.shutdown().finally(() => process.exit(0));
|
||
|
|
});
|
||
|
|
await new Promise(() => undefined);
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
if (check) {
|
||
|
|
await runtime.shutdown();
|
||
|
|
if (!connection.isClosed()) await connection.close();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
main().catch((error) => {
|
||
|
|
console.error(error instanceof Error ? error.stack : error);
|
||
|
|
process.exit(1);
|
||
|
|
});
|