2026-06-08 17:43:01 +00:00

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-demo-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);
});