decentralised sync engine

Compare changes

Choose any two refs to compare.

+4 -1
src/lib/utils/domains.ts
···
export const isDomain = (str: string) => {
try {
const url = new URL(str.includes("://") ? str : `https://${str}`);
-
return url.hostname.includes(".") && !url.hostname.startsWith(".");
} catch {
return false;
}
···
export const isDomain = (str: string) => {
try {
const url = new URL(str.includes("://") ? str : `https://${str}`);
+
return (
+
(url.hostname.includes(".") && !url.hostname.startsWith(".")) ||
+
url.hostname === "localhost"
+
);
} catch {
return false;
}
-43
src/index.old.ts
···
-
import type { ShardMessage } from "@/lib/types/messages";
-
import { rawDataToString } from "@/lib/utils";
-
import { validateNewMessage } from "@/lib/validators";
-
import type { RawData } from "ws";
-
import WebSocket from "ws";
-
-
const wss = new WebSocket.Server({ port: 8080 });
-
-
const messages: ShardMessage[] = [];
-
const clients = new Set<WebSocket>();
-
-
wss.on("connection", (ws) => {
-
clients.add(ws);
-
-
ws.send(
-
JSON.stringify({
-
type: "shard/history",
-
messages: messages,
-
}),
-
);
-
-
ws.on("message", (data: RawData) => {
-
const jsonText = rawDataToString(data);
-
const jsonData: unknown = JSON.parse(jsonText);
-
-
const shardMessage = validateNewMessage(jsonData);
-
if (!shardMessage) return;
-
-
messages.push(shardMessage);
-
-
clients.forEach((client) => {
-
if (client.readyState === WebSocket.OPEN) {
-
client.send(JSON.stringify(shardMessage));
-
}
-
});
-
});
-
-
ws.on("close", () => {
-
clients.delete(ws);
-
});
-
});
-
-
console.log("Server running on ws://localhost:8080");
···
+191
src/lib/handlers/handshake.ts
···
···
+
import { OWNER_DID, SERVICE_DID } from "@/lib/env";
+
import { issueNewLatticeToken } from "@/lib/sessions";
+
import { HttpGeneralErrorType } from "@/lib/types/http/errors";
+
import { handshakeDataSchema } from "@/lib/types/http/handlers";
+
import { systemsGmstnDevelopmentChannelRecordSchema } from "@/lib/types/lexicon/systems.gmstn.development.channel";
+
import type { RouteHandler } from "@/lib/types/routes";
+
import { stringToAtUri } from "@/lib/utils/atproto";
+
import {
+
getConstellationBacklink,
+
getPdsRecordFromBacklink,
+
} from "@/lib/utils/constellation";
+
import {
+
newErrorResponse,
+
newSuccessResponse,
+
} from "@/lib/utils/http/responses";
+
import { verifyServiceJwt } from "@/lib/utils/verifyJwt";
+
import { z } from "zod";
+
+
export const handshakeHandler: RouteHandler = async (req) => {
+
const {
+
success: handshakeParseSuccess,
+
error: handshakeParseError,
+
data: handshakeData,
+
} = handshakeDataSchema.safeParse(req.body);
+
if (!handshakeParseSuccess) {
+
return newErrorResponse(400, {
+
message: HttpGeneralErrorType.TYPE_ERROR,
+
details: z.treeifyError(handshakeParseError),
+
});
+
}
+
+
const { interServiceJwt, channelAtUris: channelAtUriStrings } =
+
handshakeData;
+
const allowedChannels = channelAtUriStrings.map((channel) => {
+
const res = stringToAtUri(channel);
+
if (!res.ok) return;
+
return res.data;
+
});
+
+
const verifyJwtResult = await verifyServiceJwt(interServiceJwt);
+
if (!verifyJwtResult.ok) {
+
const { error } = verifyJwtResult;
+
return newErrorResponse(
+
401,
+
{
+
message:
+
"JWT authentication failed. Did you submit the right inter-service JWT to the right endpoint with the right signatures?",
+
details: error,
+
},
+
{
+
headers: {
+
"WWW-Authenticate":
+
'Bearer error="invalid_token", error_description="JWT signature verification failed"',
+
},
+
},
+
);
+
}
+
+
// TODO:
+
// if(PRIVATE_SHARD) doAllowCheck()
+
// see the sequence diagram for the proper flow.
+
// not implemented for now because we support public first
+
+
const constellationResponse = await getConstellationBacklink({
+
subject: `at://${OWNER_DID}/systems.gmstn.development.shard/${SERVICE_DID.slice(8)}`,
+
source: {
+
nsid: "systems.gmstn.development.channel",
+
fieldName: "storeAt.uri",
+
},
+
});
+
if (!constellationResponse.ok) {
+
const { error } = constellationResponse;
+
if ("fetchStatus" in error)
+
return newErrorResponse(error.fetchStatus, {
+
message:
+
"Could not fetch backlinks from constellation. Likely something went wrong on our side.",
+
details: error.message,
+
});
+
else
+
return newErrorResponse(400, {
+
message: HttpGeneralErrorType.TYPE_ERROR,
+
details: z.treeifyError(error),
+
});
+
}
+
+
const pdsRecordFetchPromises = constellationResponse.data.records.map(
+
async (backlink) => {
+
const recordResult = await getPdsRecordFromBacklink(backlink);
+
if (!recordResult.ok) {
+
console.error(
+
`something went wrong fetching the record from the given backlink ${JSON.stringify(backlink)}`,
+
);
+
throw new Error(
+
JSON.stringify({ error: recordResult.error, backlink }),
+
);
+
}
+
return recordResult.data;
+
},
+
);
+
+
let pdsChannelRecords;
+
try {
+
pdsChannelRecords = await Promise.all(pdsRecordFetchPromises);
+
} catch (err) {
+
return newErrorResponse(500, {
+
message:
+
"Something went wrong when fetching backlink channel records. Check the Shard logs if possible.",
+
details: err,
+
});
+
}
+
+
const {
+
success: channelRecordsParseSuccess,
+
error: channelRecordsParseError,
+
data: channelRecordsParsed,
+
} = z
+
.array(systemsGmstnDevelopmentChannelRecordSchema)
+
.safeParse(pdsChannelRecords);
+
if (!channelRecordsParseSuccess) {
+
return newErrorResponse(500, {
+
message:
+
"One of the backlinks returned by Constellation did not resolve to a proper lexicon Channel record.",
+
details: z.treeifyError(channelRecordsParseError),
+
});
+
}
+
+
// TODO:
+
// for private shards, ensure that the channels described by constellation backlinks are made
+
// by authorised parties (check owner pds for workspace management permissions)
+
// do another fetch to owner's pds first to grab the records, then cross-reference with the
+
// did of the backlink. if there are any channels described by unauthorised parties, simply drop them.
+
+
let mismatchOrIncorrect = false;
+
const requestingLatticeDid = verifyJwtResult.value.issuer;
+
+
channelRecordsParsed.forEach((channel) => {
+
if (mismatchOrIncorrect) return;
+
+
const { storeAt: storeAtRecord, routeThrough: routeThroughRecord } =
+
channel;
+
const storeAtRecordParseResult = stringToAtUri(storeAtRecord.uri);
+
if (!storeAtRecordParseResult.ok) {
+
mismatchOrIncorrect = true;
+
return;
+
}
+
const storeAtUri = storeAtRecordParseResult.data;
+
+
// FIXME: this assumes that the current shard's SERVICE_DID is a did:web.
+
// we should resolve the full record or add something that can tell us where to find this shard.
+
// likely, we should simply resolve the described shard record, which we can technically do faaaaar earlier on in the request
+
// or even store it in memory upon first boot of a shard.
+
// also incorrectly assumes that the storeAt rkey is a domain when it can in fact be anything.
+
// we should probably just resolve this properly first but for now, i cba.
+
if (storeAtUri.rKey !== SERVICE_DID.slice(8)) {
+
mismatchOrIncorrect = true;
+
return;
+
}
+
+
const routeThroughRecordParseResult = stringToAtUri(
+
routeThroughRecord.uri,
+
);
+
if (!routeThroughRecordParseResult.ok) {
+
mismatchOrIncorrect = true;
+
return;
+
}
+
const routeThroughUri = routeThroughRecordParseResult.data;
+
+
// FIXME: this also assumes that the requesting lattice's DID is a did:web
+
// see above for the rest of the issues.
+
if (routeThroughUri.rKey === requestingLatticeDid.slice(8)) {
+
mismatchOrIncorrect = true;
+
return;
+
}
+
});
+
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+
if (mismatchOrIncorrect)
+
return newErrorResponse(400, {
+
message:
+
"Channels provided during the handshake had a mismatch between the channel values. Ensure that you are only submitting exactly the channels you have access to.",
+
});
+
+
// yipee, it's a valid request :3
+
+
const sessionInfo = issueNewLatticeToken({
+
allowedChannels,
+
clientDid: verifyJwtResult.value.issuer,
+
});
+
+
return newSuccessResponse({ sessionInfo });
+
};
+7
src/lib/utils/crypto.ts
···
···
+
import * as crypto from "node:crypto";
+
+
export const generateNewSecret = () => {
+
return crypto.randomBytes(32).toString("hex");
+
};
+
+
export const SESSIONS_SECRET = generateNewSecret();
+14
src/lib/utils/verifyJwt.ts
···
···
+
import { SERVER_PORT, SERVICE_DID } from "@/lib/env";
+
import { didDocResolver } from "@/lib/utils/atproto";
+
import { ServiceJwtVerifier } from "@atcute/xrpc-server/auth";
+
+
export const verifyServiceJwt = async (jwt: string) => {
+
const serviceDid = SERVICE_DID.startsWith("did:web:localhost")
+
? (`${SERVICE_DID}%3A${SERVER_PORT.toString()}` as `did:${string}:${string}`)
+
: SERVICE_DID;
+
const verifier = new ServiceJwtVerifier({
+
resolver: didDocResolver,
+
serviceDid,
+
});
+
return await verifier.verify(jwt);
+
};
-30
src/routes/testing/route.ts
···
-
import type { Route } from "@/lib/types/routes";
-
import { initiateHandshakeTo } from "@/lib/utils/handshake";
-
import {
-
newErrorResponse,
-
newSuccessResponse,
-
} from "@/lib/utils/http/responses";
-
-
export const testingRoute: Route = {
-
method: "GET",
-
handler: async () => {
-
const sessionInfo = await initiateHandshakeTo({
-
did: "did:web:localhost%3A7337",
-
channels: [
-
{
-
authority: "did:plc:knucpdtudgdpyoeydicvhzel",
-
collection: "systems.gmstn.development.channel",
-
rKey: "3m3tpcwneq22e",
-
},
-
],
-
});
-
if (!sessionInfo.ok)
-
return newErrorResponse(400, {
-
message: "something went wrong with the handshake.",
-
details: sessionInfo.error,
-
});
-
return newSuccessResponse({
-
sessionInfo: sessionInfo.data,
-
});
-
},
-
};
···
+19
src/lib/types/lexicon/systems.gmstn.development.channel.membership.ts
···
···
+
import { comAtprotoRepoStrongRefSchema } from "@/lib/types/atproto";
+
import { z } from "zod";
+
+
export const systemsGmstnDevelopmentChannelMembershipRecordSchema = z.object({
+
$type: z.string(),
+
state: z.union([
+
z.literal("accepted"),
+
z.literal("rejected"),
+
z.literal("left"),
+
]),
+
invite: comAtprotoRepoStrongRefSchema,
+
channel: comAtprotoRepoStrongRefSchema,
+
createdAt: z.coerce.date(),
+
updatedAt: z.coerce.date(),
+
});
+
+
export type SystemsGmstnDevelopmentChannelMembership = z.infer<
+
typeof systemsGmstnDevelopmentChannelMembershipRecordSchema
+
>;
+2 -2
src/lib/utils/constellation.ts
···
ConstellationBacklinkResponse,
} from "@/lib/types/constellation";
import { constellationBacklinkResponseSchema } from "@/lib/types/constellation";
-
import { getRecordFromAtUri } from "@/lib/utils/atproto";
import type { Result } from "@/lib/utils/result";
import type { ZodError } from "zod";
···
backlink: ConstellationBacklink,
): Promise<Result<unknown, unknown>> => {
const atUri = createAtUriFromBacklink(backlink);
-
const atUriRecordResult = await getRecordFromAtUri(atUri);
if (!atUriRecordResult.ok)
return { ok: false, error: atUriRecordResult.error };
return { ok: true, data: atUriRecordResult.data };
···
ConstellationBacklinkResponse,
} from "@/lib/types/constellation";
import { constellationBacklinkResponseSchema } from "@/lib/types/constellation";
+
import { getRecordFromFullAtUri } from "@/lib/utils/atproto";
import type { Result } from "@/lib/utils/result";
import type { ZodError } from "zod";
···
backlink: ConstellationBacklink,
): Promise<Result<unknown, unknown>> => {
const atUri = createAtUriFromBacklink(backlink);
+
const atUriRecordResult = await getRecordFromFullAtUri(atUri);
if (!atUriRecordResult.ok)
return { ok: false, error: atUriRecordResult.error };
return { ok: true, data: atUriRecordResult.data };
+12
src/lib/types/lexicon/systems.gmstn.development.channel.invite.ts
···
···
+
import { comAtprotoRepoStrongRefSchema, didSchema } from "@/lib/types/atproto";
+
import { z } from "zod";
+
+
export const systemsGmstnDevelopmentChannelInviteRecordSchema = z.object({
+
$type: z.string(),
+
channel: comAtprotoRepoStrongRefSchema,
+
recipient: didSchema,
+
createdAt: z.coerce.date(),
+
});
+
export type SystemsGmstnDevelopmentChannelInvite = z.infer<
+
typeof systemsGmstnDevelopmentChannelInviteRecordSchema
+
>;
+5
src/server/index.ts
···
import websocket from "@fastify/websocket";
import Fastify from "fastify";
export const setupServer = async () => {
···
logger: true,
});
await fastify.register(websocket);
return fastify;
···
import websocket from "@fastify/websocket";
+
import cors from "@fastify/cors";
import Fastify from "fastify";
export const setupServer = async () => {
···
logger: true,
});
+
await fastify.register(cors, {
+
origin: true,
+
});
+
await fastify.register(websocket);
return fastify;
+7
src/routes/connect/route.ts
···
···
+
import { connectPreHandler, connectWsHandler } from "@/lib/handlers/connect";
+
import type { WsRoute } from "@/lib/types/routes";
+
+
export const connectRoute: WsRoute = {
+
wsHandler: connectWsHandler,
+
preHandler: connectPreHandler,
+
};
-11
src/lib/utils/ws/index.ts
···
-
import type { RawData } from "ws";
-
-
export const rawDataToString = (data: RawData): string => {
-
if (Buffer.isBuffer(data)) {
-
return data.toString("utf-8");
-
}
-
if (Array.isArray(data)) {
-
return Buffer.concat(data).toString("utf-8");
-
}
-
return new TextDecoder().decode(data);
-
};
···
+45
src/lib/utils/ws/validate.ts
···
···
+
import type { WebsocketMessage } from "@/lib/types/messages";
+
import { websocketMessageSchema } from "@/lib/types/messages";
+
import type { Result } from "@/lib/utils/result";
+
import { z } from "zod";
+
import type { RawData } from "ws";
+
+
export const rawDataToString = (data: RawData): string => {
+
if (Buffer.isBuffer(data)) {
+
return data.toString("utf-8");
+
}
+
if (Array.isArray(data)) {
+
return Buffer.concat(data).toString("utf-8");
+
}
+
return new TextDecoder().decode(data);
+
};
+
+
export const validateWsMessageString = (
+
data: unknown,
+
): Result<string, unknown> => {
+
const { success, error, data: message } = z.string().safeParse(data);
+
if (!success) {
+
console.error("Error decoding websocket message");
+
console.error(error);
+
return { ok: false, error: z.treeifyError(error) };
+
}
+
return { ok: true, data: message };
+
};
+
+
export const validateWsMessageType = (
+
data: unknown,
+
): Result<WebsocketMessage, unknown> => {
+
const {
+
success: wsMessageSuccess,
+
error: wsMessageError,
+
data: wsMessage,
+
} = websocketMessageSchema.safeParse(data);
+
if (!wsMessageSuccess) {
+
console.error(
+
"Error parsing websocket message. The data might be the wrong shape.",
+
);
+
console.error(wsMessageError);
+
return { ok: false, error: z.treeifyError(wsMessageError) };
+
}
+
return { ok: true, data: wsMessage };
+
};
+4 -4
src/lib/sessions.ts
···
return sessionInfo;
};
-
export const activeLatticeSessions = new Map<LatticeSessionInfo, WebSocket>();
export const isValidSession = (sessionInfo: LatticeSessionInfo) => {
return (
···
} catch {
return { ok: false };
}
-
activeLatticeSessions.set(sessionInfo, socket);
return { ok: true, data: { sessionSocket: socket } };
};
export const deleteSession = (
sessionInfo: LatticeSessionInfo,
): Result<undefined, undefined> => {
-
if (!activeLatticeSessions.has(sessionInfo)) return { ok: false };
try {
-
activeLatticeSessions.delete(sessionInfo);
} catch {
return { ok: false };
}
···
return sessionInfo;
};
+
export const clientSessions = new Map<LatticeSessionInfo, WebSocket>();
export const isValidSession = (sessionInfo: LatticeSessionInfo) => {
return (
···
} catch {
return { ok: false };
}
+
clientSessions.set(sessionInfo, socket);
return { ok: true, data: { sessionSocket: socket } };
};
export const deleteSession = (
sessionInfo: LatticeSessionInfo,
): Result<undefined, undefined> => {
+
if (!clientSessions.has(sessionInfo)) return { ok: false };
try {
+
clientSessions.delete(sessionInfo);
} catch {
return { ok: false };
}
+44 -2
src/lib/handlers/connect.ts
···
import {
createNewSession,
issuedLatticeTokens,
isValidSession,
} from "@/lib/sessions";
-
import { shardMessageSchema } from "@/lib/types/messages";
import type { PreHandler, WsRouteHandler } from "@/lib/types/routes";
import { stringToAtUri } from "@/lib/utils/atproto";
-
import { sendToChannelClients, storeMessageInShard } from "@/lib/utils/gmstn";
import {
rawDataToString,
validateWsMessageType,
···
sendToChannelClients({ channelAtUri, message: shardMessage });
storeMessageInShard({ channelAtUri, message: shardMessage });
}
}
});
};
···
import {
createNewSession,
+
deleteSession,
issuedLatticeTokens,
isValidSession,
} from "@/lib/sessions";
+
import {
+
requestHistoryMessageSchema,
+
shardMessageSchema,
+
} from "@/lib/types/messages";
import type { PreHandler, WsRouteHandler } from "@/lib/types/routes";
import { stringToAtUri } from "@/lib/utils/atproto";
+
import {
+
sendHistoryRequestToShard,
+
sendToChannelClients,
+
storeMessageInShard,
+
} from "@/lib/utils/gmstn";
import {
rawDataToString,
validateWsMessageType,
···
sendToChannelClients({ channelAtUri, message: shardMessage });
storeMessageInShard({ channelAtUri, message: shardMessage });
+
break;
+
}
+
case "shard/requestHistory": {
+
const {
+
success,
+
error,
+
data: requestHistoryMessage,
+
} = requestHistoryMessageSchema.safeParse(
+
validateTypeResult.data,
+
);
+
if (!success) {
+
console.error(
+
"could not parse",
+
validateTypeResult.data,
+
"as a valid history request message.",
+
);
+
console.error(z.treeifyError(error));
+
return;
+
}
+
+
const { channel } = requestHistoryMessage;
+
+
const atUriParseResult = stringToAtUri(channel);
+
if (!atUriParseResult.ok) return;
+
const { data: channelAtUri } = atUriParseResult;
+
+
sendHistoryRequestToShard({
+
channelAtUri,
+
message: requestHistoryMessage,
+
});
}
}
});
+
+
socket.on("close", () => {
+
deleteSession(sessionInfo);
+
});
};
+54
src/lib/listeners/shard-history.ts
···
···
+
import { clientSessions } from "@/lib/sessions";
+
import { historyMessageSchema } from "@/lib/types/messages";
+
import {
+
rawDataToString,
+
validateWsMessageType,
+
} from "@/lib/utils/ws/validate";
+
import type WebSocket from "ws";
+
import { z } from "zod";
+
+
export const attachHistoryFromShardListener = (socket: WebSocket) => {
+
socket.on("message", (rawData) => {
+
const event = rawDataToString(rawData);
+
+
const data: unknown = JSON.parse(event);
+
const validateTypeResult = validateWsMessageType(data);
+
if (!validateTypeResult.ok) return;
+
+
console.log("received", validateTypeResult.data, "from shard")
+
+
const { type: messageType } = validateTypeResult.data;
+
if (messageType !== "shard/history") return;
+
const {
+
success,
+
error,
+
data: historyMessage,
+
} = historyMessageSchema.safeParse(validateTypeResult.data);
+
if (!success) {
+
console.error(
+
"could not parse",
+
validateTypeResult.data,
+
"as a valid history message.",
+
);
+
console.error(z.treeifyError(error));
+
return;
+
}
+
const { forClient: intendedRecipient } = historyMessage;
+
const clientSessionInfo = clientSessions
+
.keys()
+
.find((sessionInfo) => sessionInfo.clientDid === intendedRecipient);
+
if (!clientSessionInfo) {
+
console.error("Could not client session info in sessions map.");
+
return;
+
}
+
const clientSocket = clientSessions.get(clientSessionInfo);
+
if (!clientSocket) {
+
console.error(
+
"Could find session info in map but somehow couldn't find socket? This should not happen.",
+
);
+
return;
+
}
+
clientSocket.send(JSON.stringify(historyMessage));
+
console.log("sent off", historyMessage, "to client")
+
});
+
};
+40 -1
src/lib/utils/gmstn.ts
···
import { clientSessions } from "@/lib/sessions";
import { shardSessions } from "@/lib/state";
import type { AtUri, Did } from "@/lib/types/atproto";
import type { ShardSessionInfo } from "@/lib/types/handshake";
-
import type { ShardMessage } from "@/lib/types/messages";
import { getEndpointFromDid } from "@/lib/utils/atproto";
import WebSocket from "ws";
···
endpoint.searchParams.append("token", token);
const ws = new WebSocket(endpoint);
shardSessions.set(sessionInfo, ws);
return ws;
};
···
);
});
};
···
+
import { attachHistoryFromShardListener } from "@/lib/listeners/shard-history";
import { clientSessions } from "@/lib/sessions";
import { shardSessions } from "@/lib/state";
import type { AtUri, Did } from "@/lib/types/atproto";
import type { ShardSessionInfo } from "@/lib/types/handshake";
+
import type { RequestHistoryMessage, ShardMessage } from "@/lib/types/messages";
import { getEndpointFromDid } from "@/lib/utils/atproto";
import WebSocket from "ws";
···
endpoint.searchParams.append("token", token);
const ws = new WebSocket(endpoint);
shardSessions.set(sessionInfo, ws);
+
attachHistoryFromShardListener(ws);
return ws;
};
···
);
});
};
+
+
export const sendHistoryRequestToShard = ({
+
channelAtUri,
+
message,
+
}: {
+
channelAtUri: AtUri;
+
message: RequestHistoryMessage;
+
}) => {
+
const shardSessionInfo = shardSessions
+
.keys()
+
.find((sessionInfo) =>
+
sessionInfo.allowedChannels.some(
+
(allowedChannel) => allowedChannel.rKey === channelAtUri.rKey,
+
),
+
);
+
if (!shardSessionInfo) return;
+
+
const shardSocket = shardSessions.get(shardSessionInfo);
+
if (!shardSocket) {
+
console.error(
+
"Could find session info object in map, but socket could not be retrieved from map. Race condition?",
+
);
+
return;
+
}
+
const messageToSendToShard = {
+
...message,
+
};
+
if (shardSocket.readyState === WebSocket.OPEN)
+
shardSocket.send(JSON.stringify(messageToSendToShard));
+
+
console.log(
+
"Sent off message",
+
message,
+
"to shard located at",
+
shardSocket.url,
+
);
+
};
+9
src/lib/handlers/getOwnerDid.ts
···
···
+
import { OWNER_DID } from "@/lib/env";
+
import { getRegistrationState } from "@/lib/state";
+
import type { RouteHandler } from "@/lib/types/routes";
+
import { newSuccessResponse } from "@/lib/utils/http/responses";
+
+
export const getOwnerHandler: RouteHandler = () => {
+
const { registered } = getRegistrationState();
+
return newSuccessResponse({ registered, ownerDid: OWNER_DID });
+
};
+5
src/routes/index.ts
···
import { didWebDocRoute } from "@/routes/dot-well-known/did-dot-json/route";
import { handshakeRoute } from "@/routes/handshake/route";
import { indexRoute } from "@/routes/route";
export const routes: Record<string, Route | WsRoute> = {
"/": indexRoute,
"/.well-known/did.json": didWebDocRoute,
"/handshake": handshakeRoute,
"/connect": connectRoute,
};
···
import { didWebDocRoute } from "@/routes/dot-well-known/did-dot-json/route";
import { handshakeRoute } from "@/routes/handshake/route";
import { indexRoute } from "@/routes/route";
+
import { healthRoute } from "@/routes/xrpc/_health/route";
+
import { systemsGmstnDevelopmentShardGetOwnerRoute } from "@/routes/xrpc/systems.gmstn.development.lattice.getOwner/route";
export const routes: Record<string, Route | WsRoute> = {
"/": indexRoute,
"/.well-known/did.json": didWebDocRoute,
"/handshake": handshakeRoute,
"/connect": connectRoute,
+
"/xrpc/_health": healthRoute,
+
"/xrpc/systems.gmstn.development.lattice.getOwner":
+
systemsGmstnDevelopmentShardGetOwnerRoute,
};
+10
src/routes/xrpc/_health/route.ts
···
···
+
import type { Route } from "@/lib/types/routes";
+
+
export const healthRoute: Route = {
+
method: "GET",
+
handler: () => {
+
return new Response("this lattice is running at 0.0.1", {
+
headers: { "content-type": "text/plain; charset=utf-8" },
+
});
+
},
+
};
+7
src/routes/xrpc/systems.gmstn.development.lattice.getOwner/route.ts
···
···
+
import { getOwnerHandler } from "@/lib/handlers/getOwnerDid";
+
import type { Route } from "@/lib/types/routes";
+
+
export const systemsGmstnDevelopmentShardGetOwnerRoute: Route = {
+
method: "GET",
+
handler: getOwnerHandler,
+
};
+1
.gitignore
···
/node_modules
/dist
.env
*.tsbuildinfo
···
/node_modules
/dist
.env
+
.docker.env
*.tsbuildinfo
+1 -1
src/index.ts
···
}
}
-
server.listen({ port: SERVER_PORT }).catch((err: unknown) => {
server.log.error(err);
process.exit(1);
});
···
}
}
+
server.listen({ port: SERVER_PORT, host: "::" }).catch((err: unknown) => {
server.log.error(err);
process.exit(1);
});
+2 -1
src/lib/setup.ts
···
channels: channelAtUris,
});
if (!handshakeResult.ok) {
-
console.error(handshakeResult.error);
continue;
}
const sessionInfo = handshakeResult.data;
···
channels: channelAtUris,
});
if (!handshakeResult.ok) {
+
console.error("Handshake to", shardDid, "failed.");
+
console.error(JSON.stringify(handshakeResult.error));
continue;
}
const sessionInfo = handshakeResult.data;
+1 -1
src/lib/env.ts
···
"Environment variable SERVICE_DID not set. Defaulting to `did:web:localhost`",
);
}
-
export const SERVICE_DID = serviceDidParsed ?? "did:web:localhost";
const constellationUrl = process.env.CONSTELLATION_URL;
let constellationUrlParsed: URL | undefined;
···
"Environment variable SERVICE_DID not set. Defaulting to `did:web:localhost`",
);
}
+
export const SERVICE_DID = serviceDidParsed ?? `did:web:localhost%3A${SERVER_PORT.toString()}`;
const constellationUrl = process.env.CONSTELLATION_URL;
let constellationUrlParsed: URL | undefined;