sor4chi / hono-do

A wrapper of Cloudflare Workers's Durable Object for Hono.
https://www.npmjs.com/package/hono-do
MIT License
73 stars 2 forks source link

Having issues making messages persist #46

Open himanshu-ntml opened 1 week ago

himanshu-ntml commented 1 week ago

@sor4chi Thank you so much for making hono-do library. It makes it so easy to work with DO and websockets.

I am trying to use it to build a real-time quiz app where I want to use DO and web sockets for real-time results. I am trying to implement persistence in hibernate-chat example. But I am running into issues. Could you please help? I am not sure whether my approach is right or wrong or i am missing something.

import { generateHonoObject } from 'hono-do';
import { defineStorage } from 'hono-do/storage';

function uuidv4() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    const r = (Math.random() * 16) | 0,
      v = c == 'x' ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

declare module 'hono-do' {
  interface HonoObjectVars {
    messages: {
      timestamp: string;
      text: string;
    }[];
  }
}

export const Chat = generateHonoObject('/chat', async (app, state, vars) => {
  let { storage } = state;
  const [getValue] = await defineStorage(state.storage, 'value', {});

  // console.log(await state.storage.get('value'), 'aaa');

  vars.messages = [];

  app.get('/messages', async (c) => c.json(await getValue()));

  app.get('/websocket', async (c) => {
    if (c.req.header('Upgrade') === 'websocket') {
      return await handleWebSocketUpgrade();
    }
    return c.text('Not found', 404);
  });

  async function handleWebSocketUpgrade() {
    const [client, server] = Object.values(new WebSocketPair());
    const clientId = uuidv4();
    state.acceptWebSocket(server);
    server.serializeAttachment({ clientId });

    return new Response(null, { status: 101, webSocket: client });
  }
});

Chat.webSocketMessage(async (webSocket, msg, state, vars) => {
  const { clientId: senderClientId } = await webSocket.deserializeAttachment();
  const [getValue, setValue] = await defineStorage(state.storage, 'value', {});
  let oldMessages: any = await getValue();
  console.log('msg', msg, vars, state, webSocket);
  oldMessages.push(msg);
  // await state.storage.put('value', oldMessages);
  setValue(oldMessages);
  state.getWebSockets().forEach((ws) => {
    const { clientId } = ws.deserializeAttachment();
    if (clientId === senderClientId) {
      return;
    }
    try {
      vars.messages.push(JSON.parse(msg.toString()));
      ws.send(msg.toString());
    } catch (error) {
      ws.close();
    }
  });
});
himanshu-ntml commented 1 week ago

I was able to solve... there was some mistake.

import { generateHonoObject } from 'hono-do';
import { defineStorage } from 'hono-do/storage';
import { cors } from 'hono/cors';

function uuidv4() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    const r = (Math.random() * 16) | 0,
      v = c === 'x' ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

declare module 'hono-do' {
  interface HonoObjectVars {
    messages: {
      timestamp: string;
      text: string;
      id: string;
    }[];
  }
}

export const Chat = generateHonoObject('/chat', async (app, state, vars) => {
  const [getValue, setValue] = await defineStorage(state.storage, 'messages', []);

  vars.messages = (await getValue()) || [];

  app.get('/connect', async (c) => {
    if (c.req.header('Upgrade') === 'websocket') {
      return handleWebSocketUpgrade();
    }
    return c.text('Not found', 404);
  });

  app.use(
    '*',
    cors({
      origin: ['http://localhost:3000', 'http://localhost:8008', '*'],
      allowHeaders: ['Content-Type', 'Authorization'],
      allowMethods: ['POST', 'GET', 'OPTIONS', 'DELETE', 'PATCH'],
      exposeHeaders: ['Content-Length'],
      maxAge: 600,
      credentials: true,
    })
  );

  app.options('*', (c) => {
    return c.text('', 204);
  });

  app.delete('/:id', async (c) => {
    const id = c.req.param('id');
    const [getValue, setValue] = await defineStorage(state.storage, 'messages', []);
    let messages = await getValue();

    const initialLength = messages.length;
    messages = messages.filter((message) => message.id !== id);

    if (messages.length === initialLength) {
      return c.text('Message not found', 404);
    }

    await setValue(messages);
    return c.text('Message deleted successfully', 200);
  });

  app.get('/messages', async (c) => {
    const messages = await getValue();
    // setValue([]);
    return c.json(messages);
  });

  async function handleWebSocketUpgrade() {
    const [client, server] = Object.values(new WebSocketPair());
    const clientId = uuidv4();
    state.acceptWebSocket(server);
    server.serializeAttachment({ clientId });

    return new Response(null, { status: 101, webSocket: client });
  }
});

Chat.webSocketMessage(async (webSocket, msg, state, vars) => {
  const { clientId: senderClientId } = await webSocket.deserializeAttachment();
  const [getValue, setValue] = await defineStorage(state.storage, 'messages', []);
  let oldMessages = await getValue();

  try {
    const parsedMsg = JSON.parse(msg.toString());
    oldMessages.push(parsedMsg);
    await setValue(oldMessages);
    state.getWebSockets().forEach((ws) => {
      const { clientId } = ws.deserializeAttachment();
      if (clientId === senderClientId) {
        return;
      }
      try {
        ws.send(msg.toString());
      } catch (error) {
        ws.close();
      }
    });
  } catch (error) {
    console.error('Error parsing or storing message:', error);
  }
});

Now i am running into another issue. I am not able to fetch all the messages using the get route in another nextjs app. I was running into cors error and tried the above code to solve the cors but now i am getting "Can't modify immtable headers"

✘ [ERROR] TypeError: Can't modify immutable headers.

      at set res
  (file:///Users/nicolethomas/Desktop/Membership/hono/node_modules/hono/dist/context.js:71:24)
      at dispatch
  (file:///Users/nicolethomas/Desktop/Membership/hono/node_modules/hono/dist/compose.js:43:17)
      at async cors2
  (file:///Users/nicolethomas/Desktop/Membership/hono/node_modules/hono/dist/middleware/cors/index.js:70:5)
      at async dispatch
  (file:///Users/nicolethomas/Desktop/Membership/hono/node_modules/hono/dist/compose.js:29:17)
      at null.<anonymous> (async
  file:///Users/nicolethomas/Desktop/Membership/hono/.wrangler/tmp/dev-AmZfOC/index.js:2764:25)
      at async jsonError
  (file:///Users/nicolethomas/Desktop/Membership/hono/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts:22:10)
      at async drainBody
  (file:///Users/nicolethomas/Desktop/Membership/hono/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts:5:10)
  TypeError: Can't modify immutable headers.
      at set res
  (file:///Users/nicolethomas/Desktop/Membership/hono/node_modules/hono/dist/context.js:71:24)
      at dispatch
  (file:///Users/nicolethomas/Desktop/Membership/hono/node_modules/hono/dist/compose.js:43:17)
      at async cors2
  (file:///Users/nicolethomas/Desktop/Membership/hono/node_modules/hono/dist/middleware/cors/index.js:70:5)
      at async dispatch
  (file:///Users/nicolethomas/Desktop/Membership/hono/node_modules/hono/dist/compose.js:29:17)
      at null.<anonymous> (async
  file:///Users/nicolethomas/Desktop/Membership/hono/.wrangler/tmp/dev-AmZfOC/index.js:2764:25)
      at async jsonError
  (file:///Users/nicolethomas/Desktop/Membership/hono/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts:22:10)
      at async drainBody
  (file:///Users/nicolethomas/Desktop/Membership/hono/node_modules/wrangler/templates/middleware/middleware-ensure-req-body-drained.ts:5:10)

[wrangler:inf] GET /chat/connect 500 Internal Server Error (12ms)
[wrangler:inf] GET /chat/messages 500 Internal Server Error (16ms)
[wrangler:inf] GET /chat/messages 500 Internal Server Error (15ms)
sor4chi commented 1 week ago

Thank you for asking, I'll look at it later!

sor4chi commented 1 week ago

Hi, @himanshu-ntml

I think this Can't modify immutable headers. issue can't be solved without also looking at the code of the main workers that proxy Durable Objects. I tried it in my environment to test it and it did not have such an effect.