paint-brush
বিল্ডিং ChatPlus: ওপেন সোর্স PWA যা একটি মোবাইল অ্যাপের মতো মনে হয়দ্বারা@aladinyo
1,148 পড়া
1,148 পড়া

বিল্ডিং ChatPlus: ওপেন সোর্স PWA যা একটি মোবাইল অ্যাপের মতো মনে হয়

দ্বারা Alaa Eddine Boubekeur29m2024/07/10
Read on Terminal Reader

অতিদীর্ঘ; পড়তে

চ্যাটপ্লাস হল আমার তৈরি সবচেয়ে বড় অ্যাপ্লিকেশনগুলির মধ্যে একটি, এটি তার প্রাণবন্ত ইউজার ইন্টারফেস এবং একাধিক মেসেজিং এবং কল কার্যকারিতা দিয়ে মনোযোগ আকর্ষণ করে যা সমস্ত ওয়েবে প্রয়োগ করা হয়, এটি একটি ক্রস প্ল্যাটফর্ম অভিজ্ঞতা দেয় কারণ এর ফ্রন্টএন্ডটি একটি PWA অ্যাপ্লিকেশন হিসাবে ডিজাইন করা হয়েছে যা পুশ নোটিফিকেশনের মতো বৈশিষ্ট্য সহ একটি স্বতন্ত্র অ্যাপের মতো কাজ করে সর্বত্র ইনস্টল করার ক্ষমতা রয়েছে, চ্যাটপ্লাস হল একটি হালকা অ্যাপের সংজ্ঞা যা ওয়েব প্রযুক্তি সহ একটি মোবাইল অ্যাপের সম্পূর্ণ বৈশিষ্ট্য দিতে পারে।
featured image - বিল্ডিং ChatPlus: ওপেন সোর্স PWA যা একটি মোবাইল অ্যাপের মতো মনে হয়
Alaa Eddine Boubekeur HackerNoon profile picture
0-item
1-item

চ্যাটপ্লাস চ্যাট করার জন্য একটি দুর্দান্ত PWA 💬✨🤩

চ্যাটপ্লাস হল একটি প্রগতিশীল ওয়েব অ্যাপ যা রিঅ্যাক্ট, নোডজেএস, ফায়ারবেস এবং অন্যান্য পরিষেবাগুলির সাথে তৈরি করা হয়েছে।

আপনি রিয়েল টাইমে আপনার সমস্ত বন্ধুদের সাথে কথা বলতে পারেন 🗣️✨🧑‍🤝‍🧑❤️

আপনি আপনার বন্ধুদের কল করতে পারেন এবং তাদের সাথে ভিডিও এবং অডিও কল করতে পারেন 🎥🔉🤩৷

আপনার বন্ধুদের ইমেজ পাঠান এবং অডিও বার্তাও পাঠান এবং আপনার কাছে একটি AI আছে যা আপনি ফ্রেঞ্চ, ইংরেজি বা স্প্যানিশ বলুন না কেন আপনার বক্তৃতাকে পাঠ্যে রূপান্তরিত করে 🤖✨

ওয়েব অ্যাপটি যে কোনো ডিভাইসে ইনস্টল করা যেতে পারে এবং বিজ্ঞপ্তি পেতে পারে ⬇️🔔🎉

আমি আপনার সমর্থনের অনেক প্রশংসা করব, Github সংগ্রহস্থলে আমাদের একটি তারকা ছেড়ে দিন এবং আপনার বন্ধুদের সাথে ভাগ করুন ⭐✨

সম্পূর্ণ ইনস্টলেশন এবং স্থাপনার ডকুমেন্টেশনের জন্য এই Github সংগ্রহস্থলটি দেখুন: https://github.com/aladinyo/ChatPlus

সাধারণ UI এবং UX পরিষ্কার করুন

চ্যাটপ্লাস মোবাইল ভিউ


চ্যাটপ্লাস ডেস্কটপ ভিউ


তাহলে চ্যাটপ্লাস কি?

চ্যাটপ্লাস হল আমার তৈরি সবচেয়ে বড় অ্যাপ্লিকেশনগুলির মধ্যে একটি, এটি তার প্রাণবন্ত ইউজার ইন্টারফেস এবং একাধিক মেসেজিং এবং কল কার্যকারিতা দিয়ে মনোযোগ আকর্ষণ করে যা সমস্ত ওয়েবে প্রয়োগ করা হয়, এটি একটি ক্রস প্ল্যাটফর্ম অভিজ্ঞতা দেয় কারণ এর ফ্রন্টএন্ডটি একটি PWA অ্যাপ্লিকেশন হিসাবে ডিজাইন করা হয়েছে যা পুশ নোটিফিকেশনের মতো বৈশিষ্ট্য সহ একটি স্বতন্ত্র অ্যাপের মতো কাজ করে সর্বত্র ইনস্টল করার ক্ষমতা রয়েছে, চ্যাটপ্লাস হল একটি হালকা অ্যাপের সংজ্ঞা যা ওয়েব প্রযুক্তি সহ একটি মোবাইল অ্যাপের সম্পূর্ণ বৈশিষ্ট্য দিতে পারে।

সফটওয়্যার আর্কিটেকচার

আমাদের অ্যাপটি MVC সফ্টওয়্যার আর্কিটেকচার অনুসরণ করে, MVC (মডেল-ভিউ-কন্ট্রোলার) হল সফ্টওয়্যার ডিজাইনের একটি প্যাটার্ন যা সাধারণত ইউজার ইন্টারফেস, ডেটা এবং কন্ট্রোলিং লজিক প্রয়োগ করতে ব্যবহৃত হয়। এটি সফ্টওয়্যারের ব্যবসায়িক যুক্তি এবং প্রদর্শনের মধ্যে একটি বিচ্ছেদের উপর জোর দেয়। এই "উদ্বেগের বিচ্ছেদ" শ্রমের একটি ভাল বিভাজন এবং উন্নত রক্ষণাবেক্ষণের জন্য প্রদান করে।

ইউজার ইন্টারফেস লেয়ার (দেখুন)

এটি একাধিক প্রতিক্রিয়া ইন্টারফেস উপাদান নিয়ে গঠিত:

  • MaterialUI: Material UI হল একটি ওপেন-সোর্স রিঅ্যাক্ট কম্পোনেন্ট লাইব্রেরি যা Google-এর মেটেরিয়াল ডিজাইন বাস্তবায়ন করে। এটি ব্যাপক এবং বাক্সের বাইরে উৎপাদনে ব্যবহার করা যেতে পারে, আমরা এটি আইকন বোতাম এবং অডিও স্লাইডারের মতো ইন্টারফেসের কিছু উপাদানের জন্য ব্যবহার করব।

  • LoginView: একটি সাধারণ লগইন ভিউ যা ব্যবহারকারীকে তাদের ব্যবহারকারীর নাম এবং পাসওয়ার্ড রাখতে বা গুগলের সাথে লগইন করতে দেয়।

  • সাইডবারভিউ : 'সাইডবার__হেডার', 'সাইডবার__সার্চ', 'সাইডবার মেনু' এবং 'সাইডবারচ্যাটস' এর মতো একাধিক ভিউ উপাদান, এতে ব্যবহারকারীর তথ্যের জন্য একটি শিরোনাম, ব্যবহারকারীদের অনুসন্ধানের জন্য একটি অনুসন্ধান বার, আপনার চ্যাট, আপনার গ্রুপগুলির মধ্যে নেভিগেট করার জন্য একটি সাইডবার মেনু থাকে। এবং ব্যবহারকারীরা, এবং একটি সাইডবারচ্যাটস উপাদান আপনার সমস্ত সাম্প্রতিক চ্যাটগুলি প্রদর্শন করতে যা আপনি মেসেজ করেছেন, এটি ব্যবহারকারীর নাম এবং ফটো এবং শেষ বার্তা দেখায়।

  • ChatViews: এটি নিম্নলিখিত হিসাবে অনেক উপাদান নিয়ে গঠিত:

    1 .'chat__header' যে ব্যবহারকারীর সাথে আপনি কথা বলছেন তার তথ্য, তার অনলাইন স্থিতি, প্রোফাইল ছবি এবং এটি অডিও কল এবং ভিডিও কলের জন্য বোতাম প্রদর্শন করে, ব্যবহারকারী টাইপ করছে কিনা তাও এটি প্রদর্শন করে।

    2 .'chat__body--container' এটিতে অন্যান্য ব্যবহারকারীদের সাথে আমাদের বার্তাগুলির তথ্য রয়েছে এতে বার্তাগুলির উপাদান রয়েছে যা পাঠ্য বার্তা, তাদের বার্তা সহ চিত্র এবং অডিও বার্তা যেমন অডিও সময় এবং অডিওটি চালানো হয়েছিল কিনা এবং অডিওটি বাজানো হয়েছিল তা সহ অডিও বার্তাগুলি প্রদর্শন করে। এই উপাদানটির শেষে আমাদের কাছে 'দেখা' উপাদান রয়েছে যা প্রদর্শন করে যে বার্তাগুলি দেখা হয়েছে কিনা।

    3.'AudioPlayer ': একটি প্রতিক্রিয়া উপাদান যা আমাদের কাছে একটি স্লাইডার সহ একটি অডিও প্রদর্শন করতে পারে যা এটিকে নেভিগেট করতে পারে, অডিওটির পুরো সময় এবং বর্তমান সময় প্রদর্শন করে, এই ভিউ উপাদানটি 'chat__body-- কন্টেইনার'-এর মধ্যে লোড করা হয়।

    4.'চ্যাটফুটার ': এটিতে একটি বার্তা টাইপ করার জন্য একটি ইনপুট রয়েছে একটি বার্তা পাঠানোর জন্য একটি বোতাম ইনপুটে টাইপ করার সময় অন্যথায় বোতামটি আপনাকে অডিও রেকর্ড করার অনুমতি দেবে, ছবি এবং ফাইল আমদানি করার জন্য একটি বোতাম।

    5 .'মিডিয়াপ্রিভিউ': একটি প্রতিক্রিয়া উপাদান যা আমাদের চ্যাটে পাঠানোর জন্য আমরা যে ছবি বা ফাইলগুলিকে বেছে নিয়েছি তার পূর্বরূপ দেখার অনুমতি দেয়, সেগুলি একটি ক্যারোজেলে প্রদর্শিত হয় এবং ব্যবহারকারী ছবি বা ফাইলগুলি স্লাইড করতে পারে এবং প্রতিটির জন্য একটি নির্দিষ্ট বার্তা টাইপ করতে পারে

    6 .'ইমেজপ্রিভিউ': আমাদের চ্যাটে পাঠানো ছবি থাকলে এই উপাদানটি একটি মসৃণ অ্যানিমেশন সহ পূর্ণ স্ক্রিনে ছবিগুলি প্রদর্শন করবে, একটি ছবিতে ক্লিক করার পরে উপাদানটি মাউন্ট হবে।

  • স্কেলপেজ: একটি ভিউ ফাংশন যা পূর্ণ HD স্ক্রীন এবং 4K স্ক্রীনের মত বড় স্ক্রীনে প্রদর্শিত হলে আমাদের ওয়েব অ্যাপের আকার বৃদ্ধি করে।

  • CallViews: একগুচ্ছ প্রতিক্রিয়া উপাদান যা সমস্ত কল ভিউ উপাদান ধারণ করে, তারা আমাদের সমস্ত স্ক্রিনে টেনে আনার ক্ষমতা রাখে এবং এতে রয়েছে:

    1 'বোতাম': এটির একটি লাল সংস্করণ সহ একটি কল বোতাম এবং একটি সবুজ ভিডিও কল বোতাম৷

    2 'AudioCallView': একটি ভিউ কম্পোনেন্ট যা ইনকামিং অডিও কলের উত্তর দিতে এবং একটি টাইমার দিয়ে কলটি প্রদর্শন করতে দেয় এবং এটি কলটি বাতিল করতে দেয়।

    3 'StartVideoCallView': একটি ভিউ কম্পোনেন্ট যা স্থানীয় MediaAPI এর সাথে সংযোগ করে নিজেদের একটি ভিডিও প্রদর্শন করে এবং এটি অন্য ব্যবহারকারীর কলটি গ্রহণ করার জন্য অপেক্ষা করে বা এটি একটি ইনকামিং ভিডিও কলের উত্তর দেওয়ার জন্য আমাদের জন্য একটি বোতাম প্রদর্শন করে৷

    4 'ভিডিওকলভিউ': একটি ভিউ কম্পোনেন্ট যা আমাদের এবং অন্য ব্যবহারকারীর একটি ভিডিও প্রদর্শন করে এটি ক্যামেরা স্যুইচ করতে, ক্যামেরা এবং অডিও অক্ষম করতে দেয়, এটি ফুলস্ক্রিনেও যেতে পারে।

  • RouteViews: স্থানীয় উপাদান নেভিগেশন তৈরি করার জন্য আমাদের সমস্ত মতামত ধারণ করে এমন উপাদানগুলির প্রতিক্রিয়া জানাতে, আমরা 'VideoCallRoute', 'SideBarMenuRoute' এবং 'ChatsRoute' পেয়েছি।

ক্লায়েন্ট সাইড মডেল (মডেল)

ক্লায়েন্ট সাইড মডেল হল যুক্তি যা আমাদের ফ্রন্টএন্ডকে ডেটাবেস এবং একাধিক স্থানীয় এবং সার্ভারসাইড API-এর সাথে ইন্টারঅ্যাক্ট করতে দেয় এবং এতে রয়েছে:

  • Firebase SDK: এটি একটি SDK যা আমাদের ওয়েব অ্যাপের ডাটাবেস তৈরি করতে ব্যবহৃত হয়।
  • AppModel: একটি মডেল যা প্রমাণীকরণের পরে একজন ব্যবহারকারী তৈরি করে এবং এটি নিশ্চিত করে যে আমাদের ওয়েব সম্পদের সর্বশেষ সংস্করণ রয়েছে।
  • চ্যাটমডেলস: এতে ডেটাবেসে বার্তা পাঠানোর মডেল লজিক রয়েছে, নতুন বার্তা শোনার জন্য শ্রোতাদের প্রতিষ্ঠা করা, অন্য ব্যবহারকারী অনলাইনে আছে কিনা এবং সে টাইপ করছে কিনা তা শোনা, এটি আমাদের মিডিয়া যেমন ছবি এবং অডিওগুলিকে ডেটাবেস স্টোরেজে পাঠায়। সাইডবারচ্যাটস মডেল: লজিক যা ব্যবহারকারীদের সর্বশেষ বার্তা শোনে এবং ব্যবহারকারীদের থেকে আপনার সমস্ত নতুন বার্তার একটি অ্যারে দেয়, এটি ব্যবহারকারীদের অপঠিত বার্তা এবং অনলাইন স্থিতিও দেয়, এটি শেষ বার্তার সময়ের উপর ভিত্তি করে ব্যবহারকারীদের সংগঠিত করে।
  • UsersSearchModel: লজিক যা আমাদের ডাটাবেসে ব্যবহারকারীদের জন্য অনুসন্ধান করে, এটি অ্যালগোলিয়া অনুসন্ধান ব্যবহার করে যা সার্ভারে আমাদের ডাটাবেসের সাথে লিঙ্ক করে আমাদের ব্যবহারকারীদের একটি তালিকা রয়েছে
  • কলমডেল: যুক্তি যা দৈনিক SDK ব্যবহার করে আমাদের ওয়েব অ্যাপে একটি কল তৈরি করে এবং আমাদের সার্ভারে ডেটা পাঠায় এবং DailyAPI-এর সাথে ইন্টারঅ্যাক্ট করে।

ক্লায়েন্ট সাইড কন্ট্রোলার (নিয়ন্ত্রক)

এটি প্রতিক্রিয়া উপাদানগুলি নিয়ে গঠিত যা আমাদের মতামতকে তাদের নির্দিষ্ট মডেলের সাথে লিঙ্ক করে:

  • অ্যাপ কন্ট্রোলার: প্রমাণীকৃত ব্যবহারকারীকে সমস্ত উপাদানের সাথে লিঙ্ক করে এবং আমাদের অ্যাপের আকার সামঞ্জস্য করতে স্কেলপেজ ফাংশন চালায়, এটি ফায়ারবেস লোড করে এবং সমস্ত উপাদান সংযুক্ত করে, আমরা এটিকে আমাদের উপাদানগুলির একটি মোড়ক হিসাবে বিবেচনা করতে পারি।
  • সাইডবার কন্ট্রোলার: ব্যবহারকারীদের ডেটা এবং তার সর্বশেষ চ্যাটের তালিকা লিঙ্ক করে, এটি আমাদের মেনুগুলিকে তাদের মডেল যুক্তির সাথে লিঙ্ক করে, এটি অ্যালগোলিয়া অনুসন্ধান API এর সাথে অনুসন্ধান বারকেও লিঙ্ক করে।
  • চ্যাটকন্ট্রোলার: এটি একটি খুব বড় নিয়ামক যা বেশিরভাগ মেসেজিং এবং চ্যাট বৈশিষ্ট্যগুলিকে লিঙ্ক করে।
  • কল কন্ট্রোলার: কল মডেলটিকে তার দৃষ্টিভঙ্গির সাথে লিঙ্ক করে।

সার্ভার সাইড মডেল

সমস্ত বৈশিষ্ট্য ফ্রন্টএন্ডে করা হয় না কারণ আমরা যে SDKগুলি ব্যবহার করি তার জন্য কিছু সার্ভার সাইড কার্যকারিতার প্রয়োজন হয় এবং সেগুলি রয়েছে:

  • CallServerModel: যুক্তি যা আমাদের ডেইলি API এর সাথে ইন্টারঅ্যাক্ট করে এবং আমাদের ফায়ারস্টোর ডাটাবেস আপডেট করে কলের জন্য রুম তৈরি করতে দেয়।
  • ট্রান্সক্রিপ্ট মডেল: সার্ভারে লজিক যা একটি অডিও ফাইল গ্রহণ করে এবং Google ক্লাউড স্পিচ থেকে টেক্সট API-এর সাথে ইন্টারঅ্যাক্ট করে এবং এটি অডিও বার্তাগুলির জন্য একটি প্রতিলিপি দেয়।
  • অনলাইন স্ট্যাটাস হ্যান্ডলার: একজন শ্রোতা যে ব্যবহারকারীদের অনলাইন স্ট্যাটাস শোনে এবং সেই অনুযায়ী ডেটাবেস আপডেট করে নোটিফিকেশন মডেল: একটি পরিষেবা যা অন্য ব্যবহারকারীদের কাছে বিজ্ঞপ্তি পাঠায়।
  • অ্যালগোলিয়াসেভার: একজন শ্রোতা যে আমাদের ডাটাবেসে নতুন ব্যবহারকারীদের কথা শোনে এবং সেই অনুযায়ী অ্যালগোলিয়া আপডেট করে যাতে আমরা ফ্রন্টএন্ডে অনুসন্ধান বৈশিষ্ট্যের জন্য এটি ব্যবহার করতে পারি।
  • সার্ভার সাইড কন্ট্রোলার: কল সার্ভার: একটি API এন্ডপয়েন্ট যেখানে কলমডেল ওয়ার্কার রয়েছে: একটি কর্মী পরিষেবা যা আমাদের সমস্ত ফায়ারবেস হ্যান্ডলিং পরিষেবা চালায়।

চ্যাট ফ্লো চার্ট

চ্যাট ফ্লো চার্ট


সাইডবার ফ্লো চার্ট

সাইডবার ফ্লো চার্ট

মডেল কল ফ্লো চার্ট:

মডেল কল ফ্লো চার্ট

কল ফ্লো চার্ট দেখুন

কল ফ্লো চার্ট দেখুন

ব্যাকএন্ড ওয়ার্কার ফ্লো চার্ট

ব্যাকএন্ড ওয়ার্কার ফ্লো চার্ট

ডাটাবেস ডিজাইন

আমাদের ওয়েব অ্যাপ ফায়ারস্টোর ব্যবহার করে আমাদের ডেটাবেস সংরক্ষণ করার জন্য যা একটি Firebase NoSql ডাটাবেস, আমরা ব্যবহারকারীদের তথ্য সংরক্ষণ করি, আমরা সমস্ত বার্তার একটি তালিকা সংরক্ষণ করি, আমরা চ্যাটের তালিকা সংরক্ষণ করি এবং আমরা কক্ষগুলিতে চ্যাটগুলিও সংরক্ষণ করি, এইগুলি আমাদের ডেটাবেস তথ্যশালা:

  • প্রমাণীকরণের পরে ব্যবহারকারীর ডেটা।
  • বার্তার সমস্ত বিবরণ ধারণ করে এমন কক্ষ।
  • প্রতিটি ব্যবহারকারীর জন্য সর্বশেষ চ্যাটের তালিকা।
  • পাঠানোর জন্য বিজ্ঞপ্তিগুলির তালিকা৷
  • প্রতিলিপি করা অডিও তালিকা.

ডাটাবেস ইউএমএল

ম্যাজিকাল কোডের ব্যাখ্যা 🔮

পরবর্তী অধ্যায়গুলিতে আমি চ্যাটপ্লাসের কিছু কার্যকারিতা সম্পর্কে একটি দ্রুত ব্যাখ্যা এবং টিউটোরিয়াল দিতে যাচ্ছি, আমি আপনাকে JS কোড দেখাব এবং এর পিছনের অ্যালগরিদম ব্যাখ্যা করব এবং আপনার কোডটি লিঙ্ক করার জন্য সঠিক ইন্টিগ্রেশন টুল প্রদান করব ডাটাবেস

অনলাইন স্ট্যাটাস হ্যান্ডলিং

ফ্রন্টএন্ডে “.info/connected”-এর সাথে সংযোগ স্থাপন করে এবং সেই অনুযায়ী ফায়ারস্টোর এবং ডাটাবেস উভয় আপডেট করে ফায়ারবেস ডাটাবেস সংযোগ বৈশিষ্ট্য ব্যবহার করে ব্যবহারকারীদের অনলাইন স্থিতি কার্যকর করা হয়েছিল:

 var disconnectRef; function setOnlineStatus(uid) { try { console.log("setting up online status"); const isOfflineForDatabase = { state: 'offline', last_changed: createTimestamp2, id: uid, }; const isOnlineForDatabase = { state: 'online', last_changed: createTimestamp2, id: uid }; const userStatusFirestoreRef = db.collection("users").doc(uid); const userStatusDatabaseRef = db2.ref('/status/' + uid); // Firestore uses a different server timestamp value, so we'll // create two more constants for Firestore state. const isOfflineForFirestore = { state: 'offline', last_changed: createTimestamp(), }; const isOnlineForFirestore = { state: 'online', last_changed: createTimestamp(), }; disconnectRef = db2.ref('.info/connected').on('value', function (snapshot) { console.log("listening to database connected info") if (snapshot.val() === false) { // Instead of simply returning, we'll also set Firestore's state // to 'offline'. This ensures that our Firestore cache is aware // of the switch to 'offline.' userStatusFirestoreRef.set(isOfflineForFirestore, { merge: true }); return; }; userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function () { userStatusDatabaseRef.set(isOnlineForDatabase); // We'll also add Firestore set here for when we come online. userStatusFirestoreRef.set(isOnlineForFirestore, { merge: true }); }); }); } catch (error) { console.log("error setting onlins status: ", error); } };

এবং আমাদের ব্যাকএন্ডে আমরা একটি শ্রোতাও সেট আপ করি যেটি আমাদের ডাটাবেসের পরিবর্তনগুলি শোনে এবং সেই অনুযায়ী ফায়ারস্টোর আপডেট করে, এই ফাংশনটি আমাদের রিয়েল টাইমে ব্যবহারকারীর অনলাইন স্ট্যাটাসও দিতে পারে যাতে আমরা এর ভিতরে অন্যান্য ফাংশনও চালাতে পারি:

 async function handleOnlineStatus(data, event) { try { console.log("setting online status with event: ", event); // Get the data written to Realtime Database const eventStatus = data.val(); // Then use other event data to create a reference to the // corresponding Firestore document. const userStatusFirestoreRef = db.doc(`users/${eventStatus.id}`); // It is likely that the Realtime Database change that triggered // this event has already been overwritten by a fast change in // online / offline status, so we'll re-read the current data // and compare the timestamps. const statusSnapshot = await data.ref.once('value'); const status = statusSnapshot.val(); // If the current timestamp for this data is newer than // the data that triggered this event, we exit this function. if (eventStatus.state === "online") { console.log("event status: ", eventStatus) console.log("status: ", status) } if (status.last_changed <= eventStatus.last_changed) { // Otherwise, we convert the last_changed field to a Date eventStatus.last_changed = new Date(eventStatus.last_changed); //handle the call delete handleCallDelete(eventStatus); // ... and write it to Firestore. await userStatusFirestoreRef.set(eventStatus, { merge: true }); console.log("user: " + eventStatus.id + " online status was succesfully updated with data: " + eventStatus.state); } else { console.log("next status timestamp is newer for user: ", eventStatus.id); } } catch (error) { console.log("handle online status crashed with error :", error) } }

বিজ্ঞপ্তি

বিজ্ঞপ্তিগুলি একটি দুর্দান্ত বৈশিষ্ট্য এবং সেগুলি ফায়ারবেস মেসেজিং ব্যবহার করে প্রয়োগ করা হয়, আমাদের ফ্রন্টএন্ডে যদি ব্যবহারকারীর ব্রাউজার বিজ্ঞপ্তিগুলি সমর্থন করে তবে আমরা এটি কনফিগার করি এবং ব্যবহারকারীর ফায়ারবেস মেসেজিং টোকেন পুনরুদ্ধার করি:

 const configureNotif = (docID) => { messaging.getToken().then((token) => { console.log(token); db.collection("users").doc(docID).set({ token: token }, { merge: true }) }).catch(e => { console.log(e.message); db.collection("users").doc(docID).set({ token: "" }, { merge: true }); }); }

যখনই একজন ব্যবহারকারী একটি বার্তা পাঠায়, আমরা আমাদের ডাটাবেসে একটি বিজ্ঞপ্তি যোগ করি:

 db.collection("notifications").add({ userID: user.uid, title: user.displayName, body: inputText, photoURL: user.photoURL, token: token, });


এবং আমাদের ব্যাকএন্ডে আমরা বিজ্ঞপ্তি সংগ্রহ শুনি এবং ব্যবহারকারীকে পাঠাতে ফায়ারবেস মেসেজিং ব্যবহার করি

 let listening = false; db.collection("notifications").onSnapshot(snap => { if (!listening) { console.log("listening for notifications..."); listening = true; } const docs = snap.docChanges(); if (docs.length > 0) { docs.forEach(async change => { if (change.type === "added") { const data = change.doc.data(); if (data) { const message = { data: data, token: data.token }; await db.collection("notifications").doc(change.doc.id).delete(); try { const response = await messaging.send(message); console.log("notification successfully sent :", data); } catch (error) { console.log("error sending notification ", error); }; }; }; }); }; });

এআই অডিও ট্রান্সক্রিপশন

আমাদের ওয়েব অ্যাপ্লিকেশন ব্যবহারকারীদের একে অপরের কাছে অডিও বার্তা পাঠাতে অনুমতি দেয়, এবং এর বৈশিষ্ট্যগুলির মধ্যে একটি হল এই অডিওটিকে ইংরেজি, ফ্রেঞ্চ এবং স্প্যানিশ ভাষায় রেকর্ড করা অডিওর জন্য একটি পাঠ্যে রূপান্তর করার ক্ষমতা এবং এই বৈশিষ্ট্যটি Google ক্লাউড স্পিচ টু টেক্সট বৈশিষ্ট্যের সাথে প্রয়োগ করা হয়েছিল, আমাদের ব্যাকএন্ড Firestore-এ যোগ করা নতুন ট্রান্সক্রিপ্ট শোনে এবং সেগুলিকে ট্রান্সক্রিপ্ট করে তারপর ডাটাবেসে লেখে:

 db.collection("transcripts").onSnapshot(snap => { const docs = snap.docChanges(); if (docs.length > 0) { docs.forEach(async change => { if (change.type === "added") { const data = change.doc.data(); if (data) { db.collection("transcripts").doc(change.doc.id).delete(); try { const text = await textToAudio(data.audioName, data.short, data.supportWebM); const roomRef = db.collection("rooms").doc(data.roomID).collection("messages").doc(data.messageID); db.runTransaction(async transaction => { const roomDoc = await transaction.get(roomRef); if (roomDoc.exists && !roomDoc.data()?.delete) { transaction.update(roomRef, { transcript: text }); console.log("transcript added with text: ", text); return; } else { console.log("room is deleted"); return; } }) db.collection("rooms").doc(data.roomID).collection("messages").doc(data.messageID).update({ transcript: text }); } catch (error) { console.log("error transcripting audio: ", error); }; }; }; }); }; });

স্পষ্টতই, আপনার চোখ সেই textToAudio ফাংশনটির দিকে তাকিয়ে আছে এবং আপনি ভাবছেন আমি কীভাবে এটি তৈরি করেছি, চিন্তা করবেন না আমি আপনাকে পেয়েছি:

 // Imports the Google Cloud client library const speech = require('@google-cloud/speech').v1p1beta1; const { gcsUriLink } = require("./configKeys") // Creates a client const client = new speech.SpeechClient({ keyFilename: "./audio_transcript.json" }); async function textToAudio(audioName, isShort) { // The path to the remote LINEAR16 file const gcsUri = gcsUriLink + "/audios/" + audioName; // The audio file's encoding, sample rate in hertz, and BCP-47 language code const audio = { uri: gcsUri, }; const config = { encoding: "MP3", sampleRateHertz: 48000, languageCode: 'en-US', alternativeLanguageCodes: ['es-ES', 'fr-FR'] }; console.log("audio config: ", config); const request = { audio: audio, config: config, }; // Detects speech in the audio file if (isShort) { const [response] = await client.recognize(request); return response.results.map(result => result.alternatives[0].transcript).join('\n'); } const [operation] = await client.longRunningRecognize(request); const [response] = await operation.promise().catch(e => console.log("response promise error: ", e)); return response.results.map(result => result.alternatives[0].transcript).join('\n'); }; module.exports = textToAudio;

ভিডিও কল ফিচার

আমাদের ওয়েব অ্যাপ রিয়েল টাইম ওয়েব RTC সংযোগ বাস্তবায়নের জন্য Daily API ব্যবহার করে, এটি ব্যবহারকারীদের একে অপরের সাথে ভিডিও কল করার অনুমতি দেয় তাই প্রথমে আমরা একটি ব্যাকএন্ড কল সার্ভার সেট আপ করি যাতে দৈনিক রুম তৈরি এবং মুছে ফেলার জন্য অনেকগুলি এপিআই এন্ট্রি পয়েন্ট রয়েছে:

 const app = express(); app.use(cors()); app.use(express.json()); app.delete("/delete-call", async (req, res) => { console.log("delete call data: ", req.body); deleteCallFromUser(req.body.id1); deleteCallFromUser(req.body.id2); try { fetch("https://api.daily.co/v1/rooms/" + req.body.roomName, { headers: { Authorization: `Bearer ${dailyApiKey}`, "Content-Type": "application/json" }, method: "DELETE" }); } catch(e) { console.log("error deleting room for call delete!!"); console.log(e); } res.status(200).send("delete-call success !!"); }); app.post("/create-room/:roomName", async (req, res) => { var room = await fetch("https://api.daily.co/v1/rooms/", { headers: { Authorization: `Bearer ${dailyApiKey}`, "Content-Type": "application/json" }, method: "POST", body: JSON.stringify({ name: req.params.roomName }) }); room = await room.json(); console.log(room); res.json(room); }); app.delete("/delete-room/:roomName", async (req, res) => { var deleteResponse = await fetch("https://api.daily.co/v1/rooms/" + req.params.roomName, { headers: { Authorization: `Bearer ${dailyApiKey}`, "Content-Type": "application/json" }, method: "DELETE" }); deleteResponse = await deleteResponse.json(); console.log(deleteResponse); res.json(deleteResponse); }) app.listen(process.env.PORT || 7000, () => { console.log("call server is running"); }); const deleteCallFromUser = userID => db.collection("users").doc(userID).collection("call").doc("call").delete();

এবং আমাদের ফ্রন্টএন্ডে আমরা এই API কল করার জন্য একাধিক ফাংশন পেয়েছি:

 import { callAPI as api } from "../../configKeys"; //const api = "http://localhost:7000" export async function createRoom(roomName) { var room = await fetch(`${api}/create-room/${roomName}`, { method: "POST", }); room = await room.json(); console.log(room); return room; } export async function deleteRoom(roomName) { var deletedRoom = await fetch(`${api}/delete-room/${roomName}`, { method: "DELETE", }); deletedRoom = await deletedRoom.json(); console.log(deletedRoom); console.log("deleted"); }; export function deleteCall() { window.callDelete && fetch(`${api}/delete-call`, { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify(window.callDelete) }); };

দুর্দান্ত, এখন শুধু কল রুম তৈরি করার এবং এই রুমে সংযোগ করতে এবং তাদের থেকে ডেটা পাঠাতে ও গ্রহণ করতে দৈনিক JS SDK ব্যবহার করার সময় এসেছে:

 export default async function startVideoCall(dispatch, receiverQuery, userQuery, id, otherID, userName, otherUserName, sendNotif, userPhoto, otherPhoto, audio) { var room = null; const call = new DailyIframe.createCallObject(); const roomName = nanoid(); window.callDelete = { id1: id, id2: otherID, roomName } dispatch({ type: "set_other_user_name", otherUserName }); console.log("audio: ", audio); if (audio) { dispatch({ type: "set_other_user_photo", photo: otherPhoto }); dispatch({ type: "set_call_type", callType: "audio" }); } else { dispatch({ type: "set_other_user_photo", photo: null }); dispatch({ type: "set_call_type", callType: "video" }); } dispatch({ type: "set_caller", caller: true }); dispatch({ type: "set_call", call }); dispatch({ type: "set_call_state", callState: "state_creating" }); try { room = await createRoom(roomName); console.log("created room: ", room); dispatch({ type: "set_call_room", callRoom: room }); } catch (error) { room = null; console.log('Error creating room', error); await call.destroy(); dispatch({ type: "set_call_room", callRoom: null }); dispatch({ type: "set_call", call: null }); dispatch({ type: "set_call_state", callState: "state_idle" }); window.callDelete = null; //destroy the call object; }; if (room) { dispatch({ type: "set_call_state", callState: "state_joining" }); dispatch({ type: "set_call_queries", callQueries: { userQuery, receiverQuery } }); try { await db.runTransaction(async transaction => { console.log("runing transaction"); var userData = (await transaction.get(receiverQuery)).data(); //console.log("user data: ", userData); if (!userData || !userData?.callerID || userData?.otherUserLeft) { console.log("runing set"); transaction.set(receiverQuery, { room, callType: audio ? "audio" : "video", isCaller: false, otherUserLeft: false, callerID: id, otherID, otherUserName: userName, otherUserRatio: window.screen.width / window.screen.height, photo: audio ? userPhoto : "" }); transaction.set(userQuery, { room, callType: audio ? "audio" : "video", isCaller: true, otherUserLeft: false, otherUserJoined: false, callerID: id, otherID }); } else { console.log('transaction failed'); throw userData; } }); if (sendNotif) { sendNotif(); const notifTimeout = setInterval(() => { sendNotif(); }, 1500); dispatch({ type: "set_notif_tiemout", notifTimeout }); } call.join({ url: room.url, videoSource: !audio }); } catch (userData) { //delete the room we made deleteRoom(roomName); await call.destroy(); if (userData.otherID === id) { console.log("you and the other user are calling each other at the same time"); joinCall(dispatch, receiverQuery, userQuery, userData.room, userName, audio ? userPhoto : "", userData.callType); } else { console.log("other user already in a call"); dispatch({ type: "set_call_room", callRoom: null }); dispatch({ type: "set_call", call: null }); dispatch({ type: "set_call_state", callState: "state_otherUser_calling" }); } }; }; };

OtherUserQuery এবং UserQuery হল শুধুমাত্র ফায়ারবেস ফায়ারস্টোর ডকুমেন্ট পাথ, এখন অ্যাপের বাকি অংশগুলির ভিউ উপাদান রয়েছে যা উপরের এই ফাংশন দ্বারা ট্রিগার হওয়া রাষ্ট্রীয় পরিবর্তনগুলিতে প্রতিক্রিয়া জানায় এবং আমাদের কল UI উপাদানগুলি সেই অনুযায়ী উপস্থিত হবে৷

কল এলিমেন্টের চারপাশে সরান:

এই পরবর্তী ফাংশনটি হল ম্যাজিক যা আপনাকে সমস্ত পৃষ্ঠায় কল উপাদানটি টেনে আনতে দেয়:

 export function dragElement(elmnt, page) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0, top, left, prevTop = 0, prevLeft = 0, x, y, maxTop, maxLeft; const widthRatio = page.width / window.innerWidth; const heightRatio = page.height / window.innerHeight; //clear element's mouse listeners closeDragElement(); // setthe listener elmnt.addEventListener("mousedown", dragMouseDown); elmnt.addEventListener("touchstart", dragMouseDown, { passive: false }); function dragMouseDown(e) { e = e || window.event; // get the mouse cursor position at startup: if (e.type === "touchstart") { if (typeof(e.target.className) === "string") { if (!e.target.className.includes("btn")) { e.preventDefault(); } } else if (!typeof(e.target.className) === "function") { e.stopPropagation(); } pos3 = e.touches[0].clientX * widthRatio; pos4 = e.touches[0].clientY * heightRatio; } else { e.preventDefault(); pos3 = e.clientX * widthRatio; pos4 = e.clientY * heightRatio; }; maxTop = elmnt.offsetParent.offsetHeight - elmnt.offsetHeight; maxLeft = elmnt.offsetParent.offsetWidth - elmnt.offsetWidth; document.addEventListener("mouseup", closeDragElement); document.addEventListener("touchend", closeDragElement, { passive: false }); // call a function whenever the cursor moves: document.addEventListener("mousemove", elementDrag); document.addEventListener("touchmove", elementDrag, { passive: false }); } function elementDrag(e) { e = e || window.event; e.preventDefault(); // calculate the new cursor position: if (e.type === "touchmove") { x = e.touches[0].clientX * widthRatio; y = e.touches[0].clientY * heightRatio; } else { e.preventDefault(); x = e.clientX * widthRatio; y = e.clientY * heightRatio; }; pos1 = pos3 - x; pos2 = pos4 - y; pos3 = x pos4 = y; // set the element's new position: top = elmnt.offsetTop - pos2; left = elmnt.offsetLeft - pos1; //prevent the element from overflowing the viewport if (top >= 0 && top <= maxTop) { elmnt.style.top = top + "px"; } else if ((top > maxTop && pos4 < prevTop) || (top < 0 && pos4 > prevTop)) { elmnt.style.top = top + "px"; }; if (left >= 0 && left <= maxLeft) { elmnt.style.left = left + "px"; } else if ((left > maxLeft && pos3 < prevLeft) || (left < 0 && pos3 > prevLeft)) { elmnt.style.left = left + "px"; }; prevTop = y; prevLeft = x; } function closeDragElement() { // stop moving when mouse button is released: document.removeEventListener("mouseup", closeDragElement); document.removeEventListener("touchend", closeDragElement); document.removeEventListener("mousemove", elementDrag); document.removeEventListener("touchmove", elementDrag); }; return function() { elmnt.removeEventListener("mousedown", dragMouseDown); elmnt.removeEventListener("touchstart", dragMouseDown); closeDragElement(); }; };

ছবি টেনে আনুন

আপনি আপনার চ্যাটে ছবি টেনে আনতে পারেন এবং অন্য ব্যবহারকারীর কাছে পাঠাতে পারেন, এই কার্যকারিতা এটি চালানোর মাধ্যমে সম্ভব হয়েছে:

 useEffect(() => { const dropArea = document.querySelector(".chat"); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropArea.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); }, false); }); ['dragenter', 'dragover'].forEach(eventName => { dropArea.addEventListener(eventName, () => setShowDrag(true), false) }); ['dragleave', 'drop'].forEach(eventName => { dropArea.addEventListener(eventName, () => setShowDrag(false), false) }); dropArea.addEventListener('drop', e => { if (window.navigator.onLine) { if (e.dataTransfer?.files) { const dropedFile = e.dataTransfer.files; console.log("dropped file: ", dropedFile); const { imageFiles, imagesSrc } = mediaIndexer(dropedFile); setSRC(prevImages => [...prevImages, ...imagesSrc]); setImage(prevFiles => [...prevFiles, ...imageFiles]); setIsMedia("images_dropped"); }; }; }, false); }, []);

মিডিয়া ইনডেক্সার হল একটি সাধারণ ফাংশন যা আমরা এটিতে যে ছবিগুলি প্রদান করি তার ব্লবকে সূচী করে:

 function mediaIndexer(files) { const imagesSrc = []; const filesArray = Array.from(files); filesArray.forEach((file, index) => { imagesSrc[index] = URL.createObjectURL(file); }); return { imagesSrc, imageFiles: filesArray }; }