UNPKG

@frames.js/debugger

Version:

Debug your Frames.js applications

220 lines (185 loc) 5.08 kB
import type { NotificationsNamespace, RecordedEvent, StorageInterface, } from "../types"; import { Redis } from "@upstash/redis"; import crypto from "node:crypto"; import { NOTIFICATION_TTL_IN_SECONDS } from "../../constants"; import type { FrameNotificationDetails } from "@farcaster/frame-sdk"; let storageInstance: RedisNotificationsStorage | null = null; export async function getStorage( token: string, url: string, serverUrl: string ) { if (!storageInstance) { const redis = new Redis({ token, url }); storageInstance = new RedisNotificationsStorage(redis, serverUrl); } return storageInstance; } export class RedisNotificationsStorage implements StorageInterface { constructor( private redis: Redis, private serverUrl: string ) {} async registerNamespace( id: string, params: { fid: number; frameAppUrl: string; webhookUrl: string; } ) { const namespace: NotificationsNamespace = { id, fid: params.fid, frameAppUrl: params.frameAppUrl, namespaceUrl: new URL(`/notifications/${id}`, this.serverUrl).toString(), webhookUrl: params.webhookUrl, frame: { status: "removed", }, }; await this.redis.set<NotificationsNamespace>( createNamespaceKey(id), namespace, { ex: NOTIFICATION_TTL_IN_SECONDS, } ); return namespace; } async getNamespace(id: string): Promise<NotificationsNamespace | null> { const namespace = await this.redis.get<NotificationsNamespace>( createNamespaceKey(id) ); if (!namespace) { return null; } await this.redis.expire( createNamespaceKey(id), NOTIFICATION_TTL_IN_SECONDS ); return namespace; } async addFrame(namespace: NotificationsNamespace) { if (namespace.frame.status === "added") { throw new Error("Frame is already added"); } const token = crypto.randomUUID(); const url = new URL( `/notifications/${namespace.id}/send`, this.serverUrl ).toString(); const notificationDetails: FrameNotificationDetails = { url, token, }; await this.redis.set<NotificationsNamespace>( createNamespaceKey(namespace.id), { ...namespace, frame: { status: "added", notificationDetails, }, }, { ex: NOTIFICATION_TTL_IN_SECONDS, } ); return notificationDetails; } async removeFrame(namespace: NotificationsNamespace) { if (namespace.frame.status === "removed") { throw new Error("Frame is already removed"); } await this.redis.set<NotificationsNamespace>( createNamespaceKey(namespace.id), { ...namespace, frame: { status: "removed", }, }, { ex: NOTIFICATION_TTL_IN_SECONDS, } ); } async enableNotifications(namespace: NotificationsNamespace) { if (namespace.frame.status === "removed") { throw new Error("Frame is not added"); } const token = crypto.randomUUID(); const url = new URL( `/notifications/${namespace.id}/send`, this.serverUrl ).toString(); const notificationDetails: FrameNotificationDetails = { token, url, }; await this.redis.set<NotificationsNamespace>( createNamespaceKey(namespace.id), { ...namespace, frame: { status: "added", notificationDetails, }, }, { ex: NOTIFICATION_TTL_IN_SECONDS, } ); return notificationDetails; } async disableNotifications(namespace: NotificationsNamespace) { if (namespace.frame.status === "removed") { throw new Error("Frame is not added"); } await this.redis.set<NotificationsNamespace>( createNamespaceKey(namespace.id), { ...namespace, frame: { status: "added", notificationDetails: null, }, }, { ex: NOTIFICATION_TTL_IN_SECONDS, } ); } async recordEvent(namespaceId: string, event: RecordedEvent): Promise<void> { const eventListStorageKey = createEventListStorageKey(namespaceId); await this.redis .pipeline() .rpush<RecordedEvent>(eventListStorageKey, event) .expire(eventListStorageKey, NOTIFICATION_TTL_IN_SECONDS) .exec(); return; } async listEvents(namespaceId: string): Promise<RecordedEvent[]> { const namespace = await this.getNamespace(namespaceId); if (!namespace || namespace.frame.status === "removed") { return []; } const eventListStorageKey = createEventListStorageKey(namespaceId); const notifications = await this.redis.lpop<RecordedEvent[]>( eventListStorageKey, 100 ); return notifications || []; } } function createEventListStorageKey(namespaceId: string) { return `notifications-namespace:${namespaceId}:events`; } function createNamespaceKey(namespaceId: string) { return `notifications-namespace:${namespaceId}`; }