Skip to content

Commit

Permalink
+
Browse files Browse the repository at this point in the history
  • Loading branch information
BlowaterNostr committed Apr 8, 2024
1 parent 15e7efd commit 9e17017
Show file tree
Hide file tree
Showing 15 changed files with 644 additions and 221 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,6 @@ You can now utilize the GraphQL Playground to communicate with the server.

Relay url is `ws://localhost:8000`.

### Database
### Join our community

If you are using MacOS, the directory might be `~/Library/Caches/deno/location_data`.
wss://relayed.deno.dev
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"fresh",
"recommended"
],
"exclude": ["require-await", "require-yield", "no-unused-vars"]
"exclude": ["require-await", "require-yield", "no-unused-vars", "no-empty"]
}
},
"exclude": [
Expand Down
7 changes: 7 additions & 0 deletions deploy/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ const relay = await run({
allowed_kinds: "all", // or none,
},
password: Deno.env.get("relayed_pw"),
default_information: {
name: "Relayed Example",
description: "A lightweight relay written in Deno.",
pubkey: "",
contact: "",
icon: "",
},
});
if (relay instanceof Error) {
console.error(relay);
Expand Down
12 changes: 12 additions & 0 deletions graphql-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const typeDefs = gql`
events(pubkey: String, offset: Int, limit: Int): Events
event(id: String): Event
policies: [Policy]
relayInformation: RelayInformation
}
type Mutation {
Expand All @@ -13,6 +14,7 @@ export const typeDefs = gql`
add_allow(kind: Int, pubkey: String, ): Policy!
remove_allow(kind: Int, pubkey: String, ): Policy!
set_policy(kind: Int, read: Boolean, write: Boolean): Policy!
set_relay_information(name: String, description: String, pubkey: String, contact: String, icon: String): RelayInformation!
}
type Events {
Expand Down Expand Up @@ -40,4 +42,14 @@ export const typeDefs = gql`
allow: [String!]!
block: [String!]!
}
type RelayInformation {
name: String
description: String
pubkey: String
contact: String
supported_nips: [Int!]
software: String
version: String
icon: String
}
`;
209 changes: 115 additions & 94 deletions main.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
// deno-lint-ignore-file
import { typeDefs } from "./graphql-schema.ts";
import { SubscriptionMap, ws_handler } from "./ws.ts";
import Error404 from "./routes/_404.tsx";
import { render } from "https://esm.sh/[email protected]";
import { Mutation, RootResolver } from "./resolvers/root.ts";
import { RootResolver } from "./resolvers/root.ts";
import * as gql from "https://esm.sh/[email protected]";

import { get_policies, Policy, PolicyResolver } from "./resolvers/policy.ts";
import { Policy } from "./resolvers/policy.ts";
import { func_ResolvePolicyByKind } from "./resolvers/policy.ts";
import { func_GetEventsByKinds, func_WriteEvent } from "./resolvers/event.ts";
import { WriteEvent } from "./resolvers/event.ts";
import { func_GetEventsByIDs } from "./resolvers/event.ts";
import { GetEventsByIDs } from "./resolvers/event.ts";
import { GetEventsByKinds } from "./resolvers/event.ts";
import { EventStore, func_GetEventsByIDs } from "./resolvers/event.ts";
import { NostrEvent, NostrKind, parseJSON, PublicKey, verifyEvent } from "./_libs.ts";

import Dataloader from "https://esm.sh/[email protected]";
import { PolicyStore } from "./resolvers/policy.ts";
import { Policies } from "./resolvers/policy.ts";
import { interface_GetEventsByAuthors } from "./resolvers/event.ts";
import Landing from "./routes/landing.tsx";
import Error404 from "./routes/_404.tsx";
import { RelayInformation, RelayInformationStore } from "./resolvers/nip11.ts";
import { func_GetEventsByFilter } from "./resolvers/event.ts";

const schema = gql.buildSchema(gql.print(typeDefs));

Expand All @@ -26,20 +26,25 @@ export type DefaultPolicy = {
export type Relay = {
server: Deno.HttpServer;
url: string;
password: string;
shutdown: () => Promise<void>;
set_policy: (args: {
kind: NostrKind;
read?: boolean | undefined;
write?: boolean | undefined;
}) => Promise<Policy>;
block?: Set<string>;
}) => Promise<Policy | Error>;
get_policy: (kind: NostrKind) => Promise<Policy>;
set_relay_information: (args: RelayInformation) => Promise<RelayInformation>;
get_relay_information: () => Promise<RelayInformation>;
default_policy: DefaultPolicy;
};

export async function run(args: {
port: number;
admin?: PublicKey;
password?: string;
default_information?: RelayInformation;
default_policy: DefaultPolicy;
kv?: Deno.Kv;
}): Promise<Error | Relay> {
Expand All @@ -55,16 +60,21 @@ export async function run(args: {
args.kv = await Deno.openKv();
}

const { port, default_policy } = args;
const { port, default_policy, default_information } = args;

let resolve_hostname;
const hostname = new Promise<string>((resolve) => {
resolve_hostname = resolve;
});

const getter = get_policies(args.kv);
// @ts-ignore
const loader = new Dataloader<NostrKind, Policy | null>((kinds) => getter(kinds));
const get_all_policies = Policies(args.kv);
const policyStore = new PolicyStore(default_policy, args.kv, await get_all_policies());
const relayInformationStore = new RelayInformationStore(
args.kv,
default_information,
);

const eventStore = await EventStore.New(args.kv);

const server = Deno.serve(
{
Expand All @@ -79,48 +89,30 @@ export async function run(args: {
...args,
password,
connections,
resolvePolicyByKind: async (kind: NostrKind) => {
const policy = await loader.load(kind);
if (policy == null) {
let allow_this_kind: boolean;
if (default_policy.allowed_kinds == "all") {
allow_this_kind = true;
} else if (default_policy.allowed_kinds == "none") {
allow_this_kind = false;
} else if (default_policy.allowed_kinds.includes(kind)) {
allow_this_kind = true;
} else {
allow_this_kind = false;
}
return {
kind: kind,
read: allow_this_kind,
write: allow_this_kind,
allow: new Set(),
block: new Set(),
};
}
return policy;
},
write_event: WriteEvent(args.kv),
get_events_by_IDs: GetEventsByIDs(args.kv),
get_events_by_kinds: GetEventsByKinds(args.kv),
resolvePolicyByKind: policyStore.resolvePolicyByKind,
write_event: eventStore.write_event.bind(eventStore),
get_events_by_IDs: eventStore.get_events_by_IDs.bind(eventStore),
get_events_by_kinds: eventStore.get_events_by_kinds.bind(eventStore),
get_events_by_authors: eventStore.get_events_by_authors.bind(eventStore),
get_events_by_filter: eventStore.get_events_by_filter.bind(eventStore),
policyStore,
relayInformationStore,
kv: args.kv,
}),
);
const resolvePolicyByKind = PolicyResolver(args.default_policy, args.kv);
const mutation_resolver = Mutation({ ...args, resolvePolicyByKind, kv: args.kv });

return {
server,
password,
url: `ws://${await hostname}:${port}`,
shutdown: async () => {
await server.shutdown();
args.kv?.close();
},
set_policy: mutation_resolver.set_policy,
get_policy: (kind: NostrKind) => {
return resolvePolicyByKind(kind);
},
set_policy: policyStore.set_policy,
get_policy: policyStore.resolvePolicyByKind,
set_relay_information: relayInformationStore.set_relay_information,
get_relay_information: relayInformationStore.resolveRelayInformation,
default_policy: args.default_policy,
};
}
Expand All @@ -129,82 +121,111 @@ export type EventReadWriter = {
write_event: func_WriteEvent;
get_events_by_IDs: func_GetEventsByIDs;
get_events_by_kinds: func_GetEventsByKinds;
};
get_events_by_filter: func_GetEventsByFilter;
} & interface_GetEventsByAuthors;

const root_handler = (
args: {
password: string;
information?: RelayInformation;
connections: Map<WebSocket, SubscriptionMap>;
default_policy: DefaultPolicy;
resolvePolicyByKind: func_ResolvePolicyByKind;
policyStore: PolicyStore;
relayInformationStore: RelayInformationStore;
kv: Deno.Kv;
} & EventReadWriter,
) =>
async (req: Request, info: Deno.ServeHandlerInfo) => {
console.log(info.remoteAddr);

const { pathname } = new URL(req.url);
const { pathname, protocol } = new URL(req.url);
if (pathname == "/api") {
return graphql_handler(args)(req);
}
if (pathname == "/") {
if (protocol == "http:" || protocol == "https:") {
if (req.headers.get("accept")?.includes("text/html")) {
return landing_handler(args);
}
if (req.headers.get("accept")?.includes("application/nostr+json")) {
return information_handler(args);
}
}
return ws_handler(args)(req, info);
}
const resp = new Response(render(Error404()), { status: 404 });
resp.headers.set("content-type", "html");
return resp;
};

const graphql_handler =
(args: { password: string; kv: Deno.Kv; resolvePolicyByKind: func_ResolvePolicyByKind }) =>
async (req: Request) => {
const { password, kv, resolvePolicyByKind } = args;
if (req.method == "POST") {
const query = await req.json();
const nip42 = req.headers.get("nip42");
console.log("nip42 header", nip42);
const graphql_handler = (
args: {
password: string;
kv: Deno.Kv;
policyStore: PolicyStore;
relayInformationStore: RelayInformationStore;
},
) =>
async (req: Request) => {
const { password, policyStore } = args;
if (req.method == "POST") {
const query = await req.json();
const nip42 = req.headers.get("nip42");
console.log("nip42 header", nip42);

const pw = req.headers.get("password");
if (pw != password) {
return new Response(`{"errors":"incorrect password"}`);
}

const pw = req.headers.get("password");
if (pw != password) {
return new Response(`{"errors":"incorrect password"}`);
if (nip42) {
const auth_event = parseJSON<NostrEvent>(nip42);
if (auth_event instanceof Error) {
return new Response(`{errors:["no auth"]}`);
}

if (nip42) {
const auth_event = parseJSON<NostrEvent>(nip42);
if (auth_event instanceof Error) {
return new Response(`{errors:["no auth"]}`);
}
const ok = await verifyEvent(auth_event);
if (!ok) {
return new Response(`{"errors":["no auth"]}`);
}
const ok = await verifyEvent(auth_event);
if (!ok) {
return new Response(`{"errors":["no auth"]}`);
}
const result = await gql.graphql({
schema: schema,
source: query.query,
variableValues: query.variables,
rootValue: RootResolver(args),
});
console.log(result);
return new Response(JSON.stringify(result));
} else if (req.method == "GET") {
const res = new Response(graphiql);
res.headers.set("content-type", "html");
return res;
} else {
return new Response(undefined, { status: 405 });
}
};
const result = await gql.graphql({
schema: schema,
source: query.query,
variableValues: query.variables,
rootValue: RootResolver(args),
});
console.log(result);
return new Response(JSON.stringify(result));
} else if (req.method == "GET") {
const res = new Response(graphiql);
res.headers.set("content-type", "html");
return res;
} else {
return new Response(undefined, { status: 405 });
}
};

export const supported_nips = [1, 2];
export const software = "https://github.com/BlowaterNostr/relayed";

export type RelayInformation = {
name?: string;
description?: string;
pubkey?: string;
contact?: string;
supported_nips?: number[];
software?: string;
version?: string;
icon?: string;
const landing_handler = async (args: { relayInformationStore: RelayInformationStore }) => {
const resp = new Response(
render(Landing(await args.relayInformationStore.resolveRelayInformation()), { status: 200 }),
);
resp.headers.set("content-type", "html");
return resp;
};

const information_handler = async (args: { relayInformationStore: RelayInformationStore }) => {
const resp = new Response(JSON.stringify(await args.relayInformationStore.resolveRelayInformation()), {
status: 200,
});
resp.headers.set("content-type", "application/json; charset=utf-8");
resp.headers.set("Access-Control-Allow-Origin", "*");
resp.headers.set("Access-Control-Allow-Methods", "GET");
resp.headers.set("Access-Control-Allow-Headers", "accept,content-type");
return resp;
};

// export const kv = await Deno.openKv("./test-kv");
Expand Down
4 changes: 3 additions & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ fmt:
deno fmt

test: fmt
deno task test
deno test --allow-net --unstable --allow-read --allow-write \
--filter main \
--coverage test.ts
12 changes: 12 additions & 0 deletions queries/getRelayInformation.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
query getRelayInformation {
relayInformation {
name
contact
description
icon
pubkey
software
supported_nips
version
}
}
Loading

0 comments on commit 9e17017

Please sign in to comment.