From 8fb33f86905e736098d6f8a378208c236a83bd46 Mon Sep 17 00:00:00 2001 From: Alex Risch Date: Tue, 30 Apr 2024 18:32:55 -0600 Subject: [PATCH] Initial Commit Initial Commit --- .gitignore | 177 +++++++++++++++++++++++++++++++++++++++++ .vscode/settings.json | 18 +++++ README.md | 15 ++++ bun.lockb | Bin 0 -> 67771 bytes index.ts | 68 ++++++++++++++++ lib/client.ts | 14 ++++ lib/listSubscribers.ts | 13 +++ package.json | 27 +++++++ tsconfig.json | 22 +++++ 9 files changed, 354 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100755 bun.lockb create mode 100644 index.ts create mode 100644 lib/client.ts create mode 100644 lib/listSubscribers.ts create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32270cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,177 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + +.env.* diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..52a06bf --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.tabSize": 2, + "editor.detectIndentation": false, + "files.insertFinalNewline": true +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c98c29 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# broadcast-api + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.0.25. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..eb3ffacc815596ff6262460b8396e9e19115477d GIT binary patch literal 67771 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 D*LUIr literal 0 HcmV?d00001 diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..2f98ffc --- /dev/null +++ b/index.ts @@ -0,0 +1,68 @@ +import express, { type Request, type Response } from "express"; +import cors from "cors"; +import dotenv from "dotenv"; +import { xmtpClient } from "./lib/client"; + +const envPath = `.env.${process.env.NODE_ENV}`; +dotenv.config({ path: envPath }); +const PORT = process.env.PORT; +const app = express(); +app.use(express.json()); +app.use(cors()); + +app.post("/lookup", async (req: Request, res: Response) => { + const { address } = req.body; + console.log(req.body); + if (typeof address !== "string") { + console.log(req.body); + res.status(400).send("Address must be a string"); + return; + } + const client = await xmtpClient; + const canMessage = await client.canMessage(address); + res.json({ onNetwork: canMessage }).status(200); +}); + +app.post("/subscribe", async (req: Request, res: Response) => { + const { address, signature } = req.body; + if (typeof address !== "string") { + res.status(400).send("Address must be a string"); + return; + } + + if (typeof signature !== "string") { + res.status(400).send("Signature must be a string"); + return; + } + try { + const client = await xmtpClient; + // TODO: Set Signature on new conversation + const conversation = await client.conversations.newConversation(address); + await conversation.send("Welcome to Good Morning!"); + res.status(200).send({ topic: conversation.topic }); + } catch (err) { + console.error(err); + res.status(500).send("Internal Server Error"); + } +}); + +app.get("/subscriptions", async (req: Request, res: Response) => {}); + +app.post("/broadcast", async (req: Request, res: Response) => { + const { message } = req.body; + // Supporting sending only Text Content, but can be updated to send different types of content + if (typeof message !== "string") { + res.status(400).send("Message must be a string"); + return; + } + const client = await xmtpClient; + const conversations = await client.conversations.list(); + for (const conversation of conversations) { + await conversation.send(message); + } + res.status(200).send("Broadcasted message to all conversations"); +}); + +app.listen(PORT, () => { + console.log(`Listening on port ${PORT}...`); +}); diff --git a/lib/client.ts b/lib/client.ts new file mode 100644 index 0000000..67e0e04 --- /dev/null +++ b/lib/client.ts @@ -0,0 +1,14 @@ +import { Wallet } from "ethers"; +import { Client, type XmtpEnv } from "@xmtp/xmtp-js"; +import { GrpcApiClient } from "@xmtp/grpc-api-client"; +import { FsPersistence } from "@xmtp/fs-persistence"; + +const signer = process.env.KEY + ? new Wallet(process.env.KEY) + : Wallet.createRandom(); + +export const xmtpClient = Client.create(signer, { + // apiClientFactory: GrpcApiClient.fromOptions, + basePersistence: new FsPersistence("/tmp/xmtp"), + env: process.env.XMTP_ENV as XmtpEnv, +}); diff --git a/lib/listSubscribers.ts b/lib/listSubscribers.ts new file mode 100644 index 0000000..9c368fa --- /dev/null +++ b/lib/listSubscribers.ts @@ -0,0 +1,13 @@ +import { xmtpClient } from "./client"; + +interface Subscriber { + address: string; + canMessage: boolean; +} + +export const listSubscribers = async (): Promise => { + const client = await xmtpClient; + + return []; + // subscribers: conversation.subscribers, +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..5706461 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "broadcast-api", + "module": "index.ts", + "type": "module", + "scripts": { + "start": "bun run index.ts", + "dev": "bun --env.NODE_ENV=dev --watch run index.ts" + }, + "devDependencies": { + "@types/bun": "latest", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "@xmtp/fs-persistence": "^0.0.4", + "@xmtp/grpc-api-client": "^0.2.4", + "@xmtp/xmtp-js": "^11.5.1", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "ethers": "^6.12.0", + "express": "^4.19.2", + "viem": "^2.9.29" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..dcd8fc5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + /* Linting */ + "skipLibCheck": true, + "strict": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true + } +}