import { Hono } from "hono"import { DurableObject } from "cloudflare:workers"import { randomUUID } from "node:crypto"import { jwtVerify, createRemoteJWKSet } from "jose"import { createAppAuth } from "@octokit/auth-app"import { Octokit } from "@octokit/rest"import { Resource } from "sst"type Env = {SYNC_SERVER: DurableObjectNamespace<SyncServer>Bucket: R2BucketWEB_DOMAIN: string}export class SyncServer extends DurableObject<Env> {// oxlint-disable-next-line no-useless-constructorconstructor(ctx: DurableObjectState, env: Env) {super(ctx, env)}async fetch() {console.log("SyncServer subscribe")const webSocketPair = new WebSocketPair()const [client, server] = Object.values(webSocketPair)this.ctx.acceptWebSocket(server)const data = await this.ctx.storage.list()Array.from(data.entries()).filter(([key, _]) => key.startsWith("session/")).map(([key, content]) => server.send(JSON.stringify({ key, content })))return new Response(null, {status: 101,webSocket: client,})}async webSocketMessage(_ws, _message) {}async webSocketClose(ws, code, _reason, _wasClean) {ws.close(code, "Durable Object is closing WebSocket")}async publish(key: string, content: any) {const sessionID = await this.getSessionID()if (!key.startsWith(`session/info/${sessionID}`) &&!key.startsWith(`session/message/${sessionID}/`) &&!key.startsWith(`session/part/${sessionID}/`))return new Response("Error: Invalid key", { status: 400 })// store messageawait this.env.Bucket.put(`share/${key}.json`, JSON.stringify(content), {httpMetadata: {contentType: "application/json",},})await this.ctx.storage.put(key, content)const clients = this.ctx.getWebSockets()console.log("SyncServer publish", key, "to", clients.length, "subscribers")for (const client of clients) {client.send(JSON.stringify({ key, content }))}}public async share(sessionID: string) {let secret = await this.getSecret()if (secret) return secretsecret = randomUUID()await this.ctx.storage.put("secret", secret)await this.ctx.storage.put("sessionID", sessionID)return secret}public async getData() {const data = (await this.ctx.storage.list()) as Map<string, any>return Array.from(data.entries()).filter(([key, _]) => key.startsWith("session/")).map(([key, content]) => ({ key, content }))}public async assertSecret(secret: string) {if (secret !== (await this.getSecret())) throw new Error("Invalid secret")}private async getSecret() {return this.ctx.storage.get<string>("secret")}private async getSessionID() {return this.ctx.storage.get<string>("sessionID")}async clear() {const sessionID = await this.getSessionID()const list = await this.env.Bucket.list({prefix: `session/message/${sessionID}/`,limit: 1000,})for (const item of list.objects) {await this.env.Bucket.delete(item.key)}await this.env.Bucket.delete(`session/info/${sessionID}`)await this.ctx.storage.deleteAll()}static shortName(id: string) {return id.substring(id.length - 8)}}export default new Hono<{ Bindings: Env }>().get("/", (c) => c.text("Hello, world!")).post("/share_create", async (c) => {const body = await c.req.json<{ sessionID: string }>()const sessionID = body.sessionIDconst short = SyncServer.shortName(sessionID)const id = c.env.SYNC_SERVER.idFromName(short)const stub = c.env.SYNC_SERVER.get(id)const secret = await stub.share(sessionID)return c.json({secret,url: `https://${c.env.WEB_DOMAIN}/s/${short}`,})}).post("/share_delete", async (c) => {const body = await c.req.json<{ sessionID: string; secret: string }>()const sessionID = body.sessionIDconst secret = body.secretconst id = c.env.SYNC_SERVER.idFromName(SyncServer.shortName(sessionID))const stub = c.env.SYNC_SERVER.get(id)await stub.assertSecret(secret)await stub.clear()return c.json({})}).post("/share_delete_admin", async (c) => {const body = await c.req.json<{ sessionShortName: string; adminSecret: string }>()const sessionShortName = body.sessionShortNameconst adminSecret = body.adminSecretif (adminSecret !== Resource.ADMIN_SECRET.value) throw new Error("Invalid admin secret")const id = c.env.SYNC_SERVER.idFromName(sessionShortName)const stub = c.env.SYNC_SERVER.get(id)await stub.clear()return c.json({})}).post("/share_sync", async (c) => {const body = await c.req.json<{sessionID: stringsecret: stringkey: stringcontent: any}>()const name = SyncServer.shortName(body.sessionID)const id = c.env.SYNC_SERVER.idFromName(name)const stub = c.env.SYNC_SERVER.get(id)await stub.assertSecret(body.secret)await stub.publish(body.key, body.content)return c.json({})}).get("/share_poll", async (c) => {const upgradeHeader = c.req.header("Upgrade")if (!upgradeHeader || upgradeHeader !== "websocket") {return c.text("Error: Upgrade header is required", { status: 426 })}const id = c.req.query("id")console.log("share_poll", id)if (!id) return c.text("Error: Share ID is required", { status: 400 })const stub = c.env.SYNC_SERVER.get(c.env.SYNC_SERVER.idFromName(id))return stub.fetch(c.req.raw)}).get("/share_data", async (c) => {const id = c.req.query("id")console.log("share_data", id)if (!id) return c.text("Error: Share ID is required", { status: 400 })const stub = c.env.SYNC_SERVER.get(c.env.SYNC_SERVER.idFromName(id))const data = await stub.getData()let infoconst messages: Record<string, any> = {}data.forEach((d) => {const [root, type] = d.key.split("/")if (root !== "session") returnif (type === "info") {info = d.contentreturn}if (type === "message") {messages[d.content.id] = {parts: [],...d.content,}}if (type === "part") {messages[d.content.messageID].parts.push(d.content)}})return c.json({ info, messages })}).post("/feishu", async (c) => {const body = (await c.req.json()) as {challenge?: stringevent?: {message?: {message_id?: stringroot_id?: stringparent_id?: stringchat_id?: stringcontent?: string}}}console.log(JSON.stringify(body, null, 2))const challenge = body.challengeif (challenge) return c.json({ challenge })const content = body.event?.message?.contentconst parsed =typeof content === "string" && content.trim().startsWith("{")? (JSON.parse(content) as {text?: string}): undefinedconst text = typeof parsed?.text === "string" ? parsed.text : typeof content === "string" ? content : ""let message = text.trim().replace(/^@_user_\d+\s*/, "")message = message.replace(/^aiden,?\s*/i, "<@759257817772851260> ")if (!message) return c.json({ ok: true })const threadId = body.event?.message?.root_id || body.event?.message?.message_idif (threadId) message = `${message} [${threadId}]`const response = await fetch(`https://discord.com/api/v10/channels/${Resource.DISCORD_SUPPORT_CHANNEL_ID.value}/messages`,{method: "POST",headers: {"Content-Type": "application/json",Authorization: `Bot ${Resource.DISCORD_SUPPORT_BOT_TOKEN.value}`,},body: JSON.stringify({content: `${message}`,}),},)if (!response.ok) {console.error(await response.text())return c.json({ error: "Discord bot message failed" }, { status: 502 })}return c.json({ ok: true })})/*** Used by the GitHub action to get GitHub installation access token given the OIDC token*/.post("/exchange_github_app_token", async (c) => {const EXPECTED_AUDIENCE = "opencode-github-action"const GITHUB_ISSUER = "https://token.actions.githubusercontent.com"const JWKS_URL = `${GITHUB_ISSUER}/.well-known/jwks`// get Authorization headerconst token = c.req.header("Authorization")?.replace(/^Bearer /, "")if (!token) return c.json({ error: "Authorization header is required" }, { status: 401 })// verify tokenconst JWKS = createRemoteJWKSet(new URL(JWKS_URL))let owner, repotry {const { payload } = await jwtVerify(token, JWKS, {issuer: GITHUB_ISSUER,audience: EXPECTED_AUDIENCE,})const sub = payload.sub // e.g. 'repo:my-org/my-repo:ref:refs/heads/main'const parts = sub.split(":")[1].split("/")owner = parts[0]repo = parts[1]} catch (err) {console.error("Token verification failed:", err)return c.json({ error: "Invalid or expired token" }, { status: 403 })}// Create app JWT tokenconst auth = createAppAuth({appId: Resource.GITHUB_APP_ID.value,privateKey: Resource.GITHUB_APP_PRIVATE_KEY.value,})const appAuth = await auth({ type: "app" })// Lookup installationconst octokit = new Octokit({ auth: appAuth.token })const { data: installation } = await octokit.apps.getRepoInstallation({owner,repo,})// Get installation tokenconst installationAuth = await auth({type: "installation",installationId: installation.id,})return c.json({ token: installationAuth.token })})/*** Used by the GitHub action to get GitHub installation access token given user PAT token (used when testing `opencode github run` locally)*/.post("/exchange_github_app_token_with_pat", async (c) => {const body = await c.req.json<{ owner: string; repo: string }>()const owner = body.ownerconst repo = body.repotry {// get Authorization headerconst authHeader = c.req.header("Authorization")const token = authHeader?.replace(/^Bearer /, "")if (!token) throw new Error("Authorization header is required")// Verify permissionsconst userClient = new Octokit({ auth: token })const { data: repoData } = await userClient.repos.get({ owner, repo })if (!repoData.permissions.admin && !repoData.permissions.push && !repoData.permissions.maintain)throw new Error("User does not have write permissions")// Get installation tokenconst auth = createAppAuth({appId: Resource.GITHUB_APP_ID.value,privateKey: Resource.GITHUB_APP_PRIVATE_KEY.value,})const appAuth = await auth({ type: "app" })// Lookup installationconst appClient = new Octokit({ auth: appAuth.token })const { data: installation } = await appClient.apps.getRepoInstallation({owner,repo,})// Get installation tokenconst installationAuth = await auth({type: "installation",installationId: installation.id,})return c.json({ token: installationAuth.token })} catch (e: any) {let error = eif (e instanceof Error) {error = e.message}return c.json({ error }, { status: 401 })}})/*** Used by the opencode CLI to check if the GitHub app is installed*/.get("/get_github_app_installation", async (c) => {const owner = c.req.query("owner")const repo = c.req.query("repo")const auth = createAppAuth({appId: Resource.GITHUB_APP_ID.value,privateKey: Resource.GITHUB_APP_PRIVATE_KEY.value,})const appAuth = await auth({ type: "app" })// Lookup installationconst octokit = new Octokit({ auth: appAuth.token })let installationtry {const ret = await octokit.apps.getRepoInstallation({ owner, repo })installation = ret.data} catch (err) {if (err instanceof Error && err.message.includes("Not Found")) {// not installed} else {throw err}}return c.json({ installation })}).all("*", (c) => c.text("Not Found"))
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。