tawn33y / whatsapp-cloud-api

A Node.js library for creating bots and sending/receiving messages using the Whatsapp Cloud API.
https://www.npmjs.com/package/whatsapp-cloud-api
GNU General Public License v3.0
182 stars 51 forks source link

How to use this module, to handle different WhatsApp cloud API tokens from different users at the same time. #35

Closed RyzorBent closed 1 year ago

RyzorBent commented 1 year ago

Greetings,

My MERN SAAS app where I'm using this module on the backend nodejs is getting completed. But now I'm not sure on how the backend nodejs is going to handle multiple users with different [ phone numbers, WhatsApp API tokens, etc. ]. are going to use the SAAS platform, Because the module needs the tokens at start-up time and keeps them in memory throughout.

What architecture approach do you suggest that I can use to easily handle this situation? any suggestions will be highly appreciated.

tawn33y commented 1 year ago

Not sure I fully understand your question, but the following comes to mind:

// file 1: for the server
import express from 'express';

const app = express();
app.listen(3000, () => console.log('Running...'));

export { app };

// file 2: for the connections
import { createBot } from 'whatsapp-cloud-api';
import { app } from './file1';

const bot1 = createBot('PHONE_NUMBER_ID_1', 'TOKEN_1');
await bot1.startExpressServer({
  app,
  webhookVerifyToken: 'VERIFY_TOKEN_1',
  webhookPath: '/webhook/bot/1',
});
bot1.on('message', (msg) => {
  console.log(msg);
});

const bot2 = createBot('PHONE_NUMBER_ID_2', 'TOKEN_2');
await bot2.startExpressServer({
  app,
  webhookVerifyToken: 'VERIFY_TOKEN_2',
  webhookPath: '/webhook/bot/2',
});
bot2.on('message', (msg) => {
  console.log(msg);
});

Thoughts?

RyzorBent commented 1 year ago

This is getting me closer to the desired solution, Let me test out this approach, will feedback. Thank you for this.

tawn33y commented 1 year ago

Marking this as completed. @RyzorBent, feel free to reopen if need be 😊

amit-onappr commented 1 year ago

Not sure I fully understand your question, but the following comes to mind:

// file 1: for the server
import express from 'express';

const app = express();
app.listen(3000, () => console.log('Running...'));

export { app };

// file 2: for the connections
import { createBot } from 'whatsapp-cloud-api';
import { app } from './file1';

const bot1 = createBot('PHONE_NUMBER_ID_1', 'TOKEN_1');
await bot1.startExpressServer({
  app,
  webhookVerifyToken: 'VERIFY_TOKEN_1',
  webhookPath: '/webhook/bot/1',
});
bot1.on('message', (msg) => {
  console.log(msg);
});

const bot2 = createBot('PHONE_NUMBER_ID_2', 'TOKEN_2');
await bot2.startExpressServer({
  app,
  webhookVerifyToken: 'VERIFY_TOKEN_2',
  webhookPath: '/webhook/bot/2',
});
bot2.on('message', (msg) => {
  console.log(msg);
});

Thoughts?

This solution works fine except handling one use case. If in multi-tenant system, I need to store these webhook messages. I don't have any reference of bot properties such as PHONE_NUMBER_ID or at least webhook url path.

Here is the code.

var bots = {};
// Init
for(var botSettings in allBots) {
const bot = createBot(botSettings['PHONE_ID'], botSettings['TOKEN']);
await bot.startExpressServer({
app,
webhookVerifyToken: botSettings['VERIFY_TOKEN'],
webhookPath: /webhook/bot/${botSettings['PHONE_ID']},
});
bot.on('message', (msg) => {
console.log(msg);
// Save these messages against botSettings['PHONE_ID']
});
bots[botSettings['PHONE_ID']] = bot;
}

Let me know if there is any possible solution

amit-onappr commented 1 year ago

@tawn33y Please check this

amit-onappr commented 1 year ago

Marking this as completed. @RyzorBent, feel free to reopen if need be 😊

The solution is having some problems.. as it instantiate multiple listeners and multiple on event getting triggered due to multi tenant system.

techpet commented 1 year ago

Any news on this? All bots are listening on the bot.on("message") event

tawn33y commented 1 year ago

Very sorry @amit-onappr, I didn't see your messages.

@amit-onappr, @techpet:

I see the problem: currently, the on event subscribes to all bots instead of a specific bot. When I first created this, I added this line in the README in anticipation of this problem.

Opening a PR to address this problem

tawn33y commented 1 year ago

You can now use the following functionality to remove a listener for a specific bot:

(async () => {
  const bot = createBot('from', 'token');
  await bot.startExpressServer({ webhookVerifyToken: 'xx' });

  const subscriptionToken = bot.on('message', async (msg) => {
    console.log(msg);
  });

  // to unsubscribe:  
  bot.unsubscribe(subscriptionToken);
})();
RyzorBent commented 1 year ago

Hi @tawn33y, does this mean now the on event for each bot is now for each subscriptionToken { meaning one can run multiple bots for different subscriptionTokens }, ... as you are now able to bot.unsubscribe(subscriptionToken);?

tawn33y commented 1 year ago

Yes, Each on event is for a specific bot.

Here's an example of a simple implementation:

// file 1: for the server
import express from 'express';

const app = express();
app.listen(3000, () => console.log('Running...'));

export { app };

// file 2: for the connections
import { createBot } from 'whatsapp-cloud-api';
import { app } from './file1';

(async () => {
  // bot 1
  const bot1 = createBot('PHONE_NUMBER_ID_1', 'TOKEN_1');
  await bot1.startExpressServer({
    app,
    webhookVerifyToken: 'VERIFY_TOKEN_1',
    webhookPath: '/webhook/bot/1',
  });

  const subscriptionToken1 = bot1.on('message', async (msg) => {
    console.log(msg);
  });

  bot1.unsubscribe(subscriptionToken1);

  // bot 2
  const bot2 = createBot('PHONE_NUMBER_ID_2', 'TOKEN_2');
  await bot2.startExpressServer({
    app,
    webhookVerifyToken: 'VERIFY_TOKEN_2',
    webhookPath: '/webhook/bot/2',
  });

  const subscriptionToken2 = bot2.on('message', async (msg) => {
    console.log(msg);
  });

  bot2.unsubscribe(subscriptionToken2);
})();

Here is a much more complex example:

// file 1: for the server
import express from 'express';

const app = express();
app.listen(3000, () => console.log('Running...'));

export { app };

// file 2: configs
type BotConfig = {
  phoneNumberId: string;
  token: string;
  webhookVerifyToken: string;
  webhookPath: string;
}

export const botConfigs: BotConfig[] = [
  { phoneNumberId: 'xx', token: 'xx', webhookVerifyToken: 'xx', webhookPath: 'xx' },
  { phoneNumberId: 'yy', token: 'yy', webhookVerifyToken: 'yy', webhookPath: 'yy' },
];

// file 2: for the connections
import { createBot, type Bot } from 'whatsapp-cloud-api';
import { app } from './file1';
import { botConfigs } from './file2';

type BotStorage = {
  [phoneNumberId: string]: {
    bot: Bot;
    subscriptionTokens: {
      [event: string]: string;
    };
  };
}

let botsStorage: BotStorage = {};

(async () => {
  for (let i = 0; i < botConfigs.length; i += 1) {
    const { phoneNumberId, token, webhookVerifyToken, webhookPath } = botConfigs[i];

    const bot = createBot(phoneNumberId, token);
    await bot.startExpressServer({
      app,
      webhookVerifyToken,
      webhookPath,
    });

    const subscriptionTokenForMessages = bot.on('message', async (msg) => {
      console.log(msg);
    });
    const subscriptionTokenForImages = bot.on('image', async (msg) => {
      console.log(msg);
    });

    // store these values
    botsStorage = {
      [phoneNumberId]: {
        bot,
        subscriptionTokens: {
          'message': subscriptionTokenForMessages,
          'image': subscriptionTokenForImages,
        },
      }
    };
  }

  // later, later on

  // remove 'message' subscription listener for 'xx'
  const botXX = botsStorage['xx'];
  botXX.bot.unsubscribe(botXX.subscriptionTokens['message']);

  // remove 'image' subscription listener for 'yy'
  const botYY = botsStorage['yy'];
  botYY.bot.unsubscribe(botYY.subscriptionTokens['image']);
})()

I haven't tried these out, but I believe they should work :)