diff --git a/app.js b/app.js
index 6b395866fb8984c36e2f45ff658d7b5dbae6beff..1e20671885ca3418710dc39927b1ac1ef9d94380 100644
--- a/app.js
+++ b/app.js
@@ -57,9 +57,12 @@ app.use('/api/friend', friendRoutes);
 const meetingRoutes = require('./routes/meetingRoute');
 app.use('/api/meeting', meetingRoutes);
 
-//const chatRoutes = require('./routes/chatRoute');
+const chatRoutes = require('./routes/chatRoute');
 app.use('/api/chat', chatRoutes);
 
+const memberRoutes = require('./routes/memberRoute');
+app.use('/api/member', memberRoutes);
+
 // �ㅼ�以� �대━�� 珥덇린��
 initScheduleCleaner();
 
diff --git a/controllers/chatController.js b/controllers/chatController.js
index 4c61e515f0748955dc020a4e21f4441ffcf7ffe5..41a2d3c6f05d4f021964785d98414452322050ec 100644
--- a/controllers/chatController.js
+++ b/controllers/chatController.js
@@ -1,22 +1,11 @@
 const chatService = require('../services/chatService');
 
-// �대��� 梨꾪똿諛� �앹꽦
-exports.createChatRoomInternal = async (params) => {
+exports.createChatRoom = async (params) => {
   try {
-    return await chatService.createChatRoom(params);
+    const chatRoomId = await chatService.createChatRoom(params);
+    res.json(chatRoomId);
   } catch (err) {
-    console.error('Error in createChatRoomInternal:', err);
-    return { success: false, error: err.message };
-  }
-};
-
-// �� 梨꾪똿諛� �앹꽦
-exports.createChatRoom = async (req, res) => {
-  try {
-    const chatRoomId = await chatService.createChatRoom();
-    res.json({ chatRoomId });
-  } catch (err) {
-    console.error('Error creating room:', err);
+    console.error('Error in createChatRoom:', err);
     res.status(500).json({ error: 'Failed to create room' });
   }
 };
diff --git a/controllers/memberController.js b/controllers/memberController.js
new file mode 100644
index 0000000000000000000000000000000000000000..3ec5af6c151b20eb88baeb2183e595a0bcc92866
--- /dev/null
+++ b/controllers/memberController.js
@@ -0,0 +1,17 @@
+const MemberService = require('../services/memberService');
+
+class MemberController {
+  async registerToken(req, res) {
+    const { email, fcmToken } = req.body;
+
+    try {
+      const result = await MemberService.registerToken(email, fcmToken);
+      res.status(200).json(result);
+    } catch (error) {
+      console.error('Error registering FCM token:', error);
+      res.status(500).json({ message: error.message || 'Internal server error' });
+    }
+  }
+}
+
+module.exports = new MemberController();
\ No newline at end of file
diff --git a/models/chatRooms.js b/models/chatRooms.js
index ca68c3941a49d64f8abd4806c399793c03590301..6c50957f10e2b9950b367b7dbe9a2683239662c2 100644
--- a/models/chatRooms.js
+++ b/models/chatRooms.js
@@ -1,21 +1,23 @@
 const mongoose = require('mongoose');
 
-// MongoDB 梨꾪똿諛� �ㅽ궎留� �섏젙 (�꾩옱 李멸� 以묒씤 �좎� 紐⑸줉 異붽�)
+// MongoDB 梨꾪똿諛� �ㅽ궎留� �섏젙 (FCM �좏겙�� 諛곗뿴濡� 愿�由�)
 const chatRoomsSchema = new mongoose.Schema({
   chatRoomId: { type: String, required: true, unique: true },
+  chatRoomName: { type: String, required: true },
   messages: [{
     sender: String,
     message: String,
     timestamp: Date,
-    type: { type: String, default: 'message' }  // 湲곕낯媛믪� 'message', �ㅻⅨ 媛믪쑝濡� 'join', 'leave' 媛���
+    type: { type: String, default: 'message' }, // 湲곕낯媛믪� 'message', �ㅻⅨ 媛믪쑝濡� 'join', 'leave' 媛���
   }],
-  participants: [{ type: String }],
-  lastReadAt: { type: Map, of: Date }, // 媛� 李멸��먯쓽 留덉�留� �쎌� 硫붿떆吏� �쒓컙 湲곕줉
-  lastReadLogId: { type: Map, of: String },  // 媛� 李멸��먯쓽 留덉�留됱쑝濡� �쎌� logID 湲곕줉
-  isOnline: { type: Map, of: Boolean } // 媛� 李멸��먯쓽 �⑤씪�� �곹깭
+  participants: [{
+    name: { type: String, required: true },
+    fcmTokens: { type: [String], default: [] }, // FCM �좏겙 諛곗뿴
+  }],
+  lastReadAt: { type: Map, of: Date },
+  lastReadLogId: { type: Map, of: String },
+  isOnline: { type: Map, of: Boolean },
 }, { collection: 'chatrooms' });
 
-// 紐⑤뜽�� �대� �뺤쓽�섏뼱 �덈뒗 寃쎌슦 �ъ젙�섑븯吏� �딆쓬
-const ChatRooms = mongoose.models.ChatRooms || mongoose.model('ChatRooms', chatRoomsSchema);
-
-module.exports = ChatRooms;
\ No newline at end of file
+const ChatRoom = mongoose.model('ChatRooms', chatRoomsSchema);
+module.exports = ChatRoom;
\ No newline at end of file
diff --git a/models/fcmToken.js b/models/fcmToken.js
new file mode 100644
index 0000000000000000000000000000000000000000..0691d46f127f677911cda7fce19753581610e262
--- /dev/null
+++ b/models/fcmToken.js
@@ -0,0 +1,29 @@
+const { DataTypes } = require('sequelize');
+const sequelize = require('../config/sequelize');
+const User = require('./User'); // �щ컮瑜� 寃쎈줈 �뺤씤
+
+const FcmToken = sequelize.define('FcmToken', {
+  userId: {
+    type: DataTypes.INTEGER,
+    allowNull: false,
+    references: {
+      model: User, // 臾몄옄�� ���� 紐⑤뜽 媛앹껜瑜� 李몄“
+      key: 'id',
+    },
+  },
+  token: {
+    type: DataTypes.STRING,
+    allowNull: false,
+  },
+}, {
+  tableName: 'FcmTokens',
+  timestamps: true,
+});
+
+// 愿�怨� �ㅼ젙
+FcmToken.belongsTo(User, { foreignKey: 'userId', as: 'user' });
+User.hasMany(FcmToken, { foreignKey: 'userId', as: 'fcmTokenList' });
+
+
+
+module.exports = FcmToken;
\ No newline at end of file
diff --git a/models/index.js b/models/index.js
index a07f2474b8f7d21fa8ae77ded5a573915b9223cd..9ad167a836d1ae5924d17bfd1d1f4671a2ab8916 100644
--- a/models/index.js
+++ b/models/index.js
@@ -5,6 +5,9 @@ const User = require('./user');
 const Friend = require('./Friend');
 const Schedule = require('./Schedule');
 const Meeting = require('./Meeting');
+const MeetingParticipant = require('./MeetingParticipant'); //�대뜑紐낆닔�� 
+const Friend = require('./Friend');
+const FcmToken = require('./fcmToken');
 const MeetingParticipant = require('./MeetingParticipant');
 const ChatRooms = require('./ChatRooms');
 
@@ -35,4 +38,11 @@ module.exports = {
     Meeting,
     MeetingParticipant,
     ChatRooms,
+  sequelize,
+  User,
+  Schedule,
+  Meeting,
+  MeetingParticipant,
+  Friend,
+  FcmToken, 
 };
diff --git a/package-lock.json b/package-lock.json
index 2fa587aa6873d72cb545dfdf61d1a6aad8ac2ba3..0d88e6dd404804a6a24730ef20bf0a8275b47975 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,9 +11,12 @@
       "dependencies": {
         "connect-flash": "^0.1.1",
         "cookie-parser": "^1.4.7",
+        "cors": "^2.8.5",
         "dotenv": "^16.4.5",
         "express": "^4.21.1",
         "express-session": "^1.18.1",
+        "firebase-admin": "^13.0.1",
+        "jest": "^29.7.0",
         "joi": "^17.13.3",
         "morgan": "^1.10.0",
         "multer": "^1.4.5-lts.1",
@@ -2084,6 +2087,112 @@
         "node": ">=14"
       }
     },
+    "node_modules/@fastify/busboy": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.0.0.tgz",
+      "integrity": "sha512-83rnH2nCvclWaPQQKvkJ2pdOjG4TZyEVuFDnlOF6KP08lDaaceVyw/W63mDuafQT+MKHCvXIPpE5uYWeM0rT4w==",
+      "license": "MIT"
+    },
+    "node_modules/@firebase/app-check-interop-types": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz",
+      "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@firebase/app-types": {
+      "version": "0.9.3",
+      "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz",
+      "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@firebase/auth-interop-types": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz",
+      "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@firebase/component": {
+      "version": "0.6.11",
+      "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.11.tgz",
+      "integrity": "sha512-eQbeCgPukLgsKD0Kw5wQgsMDX5LeoI1MIrziNDjmc6XDq5ZQnuUymANQgAb2wp1tSF9zDSXyxJmIUXaKgN58Ug==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@firebase/util": "1.10.2",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@firebase/database": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.10.tgz",
+      "integrity": "sha512-sWp2g92u7xT4BojGbTXZ80iaSIaL6GAL0pwvM0CO/hb0nHSnABAqsH7AhnWGsGvXuEvbPr7blZylPaR9J+GSuQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@firebase/app-check-interop-types": "0.3.3",
+        "@firebase/auth-interop-types": "0.2.4",
+        "@firebase/component": "0.6.11",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.10.2",
+        "faye-websocket": "0.11.4",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@firebase/database-compat": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.1.tgz",
+      "integrity": "sha512-IsFivOjdE1GrjTeKoBU/ZMenESKDXidFDzZzHBPQ/4P20ptGdrl3oLlWrV/QJqJ9lND4IidE3z4Xr5JyfUW1vg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@firebase/component": "0.6.11",
+        "@firebase/database": "1.0.10",
+        "@firebase/database-types": "1.0.7",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.10.2",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@firebase/database-types": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.7.tgz",
+      "integrity": "sha512-I7zcLfJXrM0WM+ksFmFdAMdlq/DFmpeMNa+/GNsLyFo5u/lX5zzkPzGe3srVWqaBQBY5KprylDGxOsP6ETfL0A==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@firebase/app-types": "0.9.3",
+        "@firebase/util": "1.10.2"
+      }
+    },
+    "node_modules/@firebase/logger": {
+      "version": "0.4.4",
+      "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz",
+      "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@firebase/util": {
+      "version": "1.10.2",
+      "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.2.tgz",
+      "integrity": "sha512-qnSHIoE9FK+HYnNhTI8q14evyqbc/vHRivfB4TgCIUOl4tosmKSQlp7ltymOlMP4xVIJTg5wrkfcZ60X4nUf7Q==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
     "node_modules/@gar/promisify": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -2091,11 +2200,102 @@
       "license": "MIT",
       "optional": true
     },
+    "node_modules/@google-cloud/firestore": {
+      "version": "7.10.0",
+      "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.10.0.tgz",
+      "integrity": "sha512-VFNhdHvfnmqcHHs6YhmSNHHxQqaaD64GwiL0c+e1qz85S8SWZPC2XFRf8p9yHRTF40Kow424s1KBU9f0fdQa+Q==",
+      "license": "Apache-2.0",
+      "optional": true,
+      "dependencies": {
+        "@opentelemetry/api": "^1.3.0",
+        "fast-deep-equal": "^3.1.1",
+        "functional-red-black-tree": "^1.0.1",
+        "google-gax": "^4.3.3",
+        "protobufjs": "^7.2.6"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/@google-cloud/paginator": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz",
+      "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==",
+      "license": "Apache-2.0",
+      "optional": true,
+      "dependencies": {
+        "arrify": "^2.0.0",
+        "extend": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/@google-cloud/projectify": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz",
+      "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==",
+      "license": "Apache-2.0",
+      "optional": true,
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/@google-cloud/promisify": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz",
+      "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==",
+      "license": "Apache-2.0",
+      "optional": true,
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/@google-cloud/storage": {
+      "version": "7.14.0",
+      "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.14.0.tgz",
+      "integrity": "sha512-H41bPL2cMfSi4EEnFzKvg7XSb7T67ocSXrmF7MPjfgFB0L6CKGzfIYJheAZi1iqXjz6XaCT1OBf6HCG5vDBTOQ==",
+      "license": "Apache-2.0",
+      "optional": true,
+      "dependencies": {
+        "@google-cloud/paginator": "^5.0.0",
+        "@google-cloud/projectify": "^4.0.0",
+        "@google-cloud/promisify": "^4.0.0",
+        "abort-controller": "^3.0.0",
+        "async-retry": "^1.3.3",
+        "duplexify": "^4.1.3",
+        "fast-xml-parser": "^4.4.1",
+        "gaxios": "^6.0.2",
+        "google-auth-library": "^9.6.3",
+        "html-entities": "^2.5.2",
+        "mime": "^3.0.0",
+        "p-limit": "^3.0.1",
+        "retry-request": "^7.0.0",
+        "teeny-request": "^9.0.0",
+        "uuid": "^8.0.0"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/@google-cloud/storage/node_modules/mime": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
+      "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
+      "license": "MIT",
+      "optional": true,
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/@grpc/grpc-js": {
       "version": "1.12.2",
       "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.2.tgz",
       "integrity": "sha512-bgxdZmgTrJZX50OjyVwz3+mNEnCTNkh3cIqGPWVNeW9jX6bn1ZkU80uPd+67/ZpIJIjRQ9qaHCjhavyoWYxumg==",
-      "dev": true,
+      "devOptional": true,
       "license": "Apache-2.0",
       "dependencies": {
         "@grpc/proto-loader": "^0.7.13",
@@ -2109,7 +2309,7 @@
       "version": "0.7.13",
       "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz",
       "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==",
-      "dev": true,
+      "devOptional": true,
       "license": "Apache-2.0",
       "dependencies": {
         "lodash.camelcase": "^4.3.0",
@@ -2128,7 +2328,7 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT",
       "engines": {
         "node": ">=8"
@@ -2138,7 +2338,7 @@
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
       "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT",
       "dependencies": {
         "color-convert": "^2.0.1"
@@ -2154,7 +2354,7 @@
       "version": "8.0.1",
       "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
       "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
-      "dev": true,
+      "devOptional": true,
       "license": "ISC",
       "dependencies": {
         "string-width": "^4.2.0",
@@ -2169,14 +2369,14 @@
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT"
     },
     "node_modules/@grpc/proto-loader/node_modules/string-width": {
       "version": "4.2.3",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT",
       "dependencies": {
         "emoji-regex": "^8.0.0",
@@ -2191,7 +2391,7 @@
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT",
       "dependencies": {
         "ansi-regex": "^5.0.1"
@@ -2204,7 +2404,7 @@
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
       "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT",
       "dependencies": {
         "ansi-styles": "^4.0.0",
@@ -2222,7 +2422,7 @@
       "version": "17.7.2",
       "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
       "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT",
       "dependencies": {
         "cliui": "^8.0.1",
@@ -2241,7 +2441,7 @@
       "version": "21.1.1",
       "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
       "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
-      "dev": true,
+      "devOptional": true,
       "license": "ISC",
       "engines": {
         "node": ">=12"
@@ -3535,7 +3735,7 @@
       "version": "4.4.2",
       "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
       "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT",
       "funding": {
         "type": "opencollective",
@@ -4211,7 +4411,7 @@
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz",
       "integrity": "sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==",
-      "dev": true,
+      "devOptional": true,
       "license": "Apache-2.0",
       "engines": {
         "node": ">=8.0.0"
@@ -5157,35 +5357,35 @@
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
       "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
-      "dev": true,
+      "devOptional": true,
       "license": "BSD-3-Clause"
     },
     "node_modules/@protobufjs/base64": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
       "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
-      "dev": true,
+      "devOptional": true,
       "license": "BSD-3-Clause"
     },
     "node_modules/@protobufjs/codegen": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
       "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
-      "dev": true,
+      "devOptional": true,
       "license": "BSD-3-Clause"
     },
     "node_modules/@protobufjs/eventemitter": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
       "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
-      "dev": true,
+      "devOptional": true,
       "license": "BSD-3-Clause"
     },
     "node_modules/@protobufjs/fetch": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
       "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
-      "dev": true,
+      "devOptional": true,
       "license": "BSD-3-Clause",
       "dependencies": {
         "@protobufjs/aspromise": "^1.1.1",
@@ -5196,35 +5396,35 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
       "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
-      "dev": true,
+      "devOptional": true,
       "license": "BSD-3-Clause"
     },
     "node_modules/@protobufjs/inquire": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
       "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
-      "dev": true,
+      "devOptional": true,
       "license": "BSD-3-Clause"
     },
     "node_modules/@protobufjs/path": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
       "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
-      "dev": true,
+      "devOptional": true,
       "license": "BSD-3-Clause"
     },
     "node_modules/@protobufjs/pool": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
       "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
-      "dev": true,
+      "devOptional": true,
       "license": "BSD-3-Clause"
     },
     "node_modules/@protobufjs/utf8": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
       "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
-      "dev": true,
+      "devOptional": true,
       "license": "BSD-3-Clause"
     },
     "node_modules/@sideway/address": {
@@ -6656,13 +6856,13 @@
       }
     },
     "node_modules/@tootallnate/once": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
-      "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+      "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
       "license": "MIT",
       "optional": true,
       "engines": {
-        "node": ">= 6"
+        "node": ">= 10"
       }
     },
     "node_modules/@tsconfig/node14": {
@@ -6778,6 +6978,16 @@
         "@babel/types": "^7.20.7"
       }
     },
+    "node_modules/@types/body-parser": {
+      "version": "1.19.5",
+      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
+      "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/connect": "*",
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/cacheable-request": {
       "version": "6.0.3",
       "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
@@ -6791,6 +7001,22 @@
         "@types/responselike": "^1.0.0"
       }
     },
+    "node_modules/@types/caseless": {
+      "version": "0.12.5",
+      "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz",
+      "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/@types/connect": {
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+      "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/debug": {
       "version": "4.1.12",
       "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@@ -6800,6 +7026,30 @@
         "@types/ms": "*"
       }
     },
+    "node_modules/@types/express": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
+      "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/body-parser": "*",
+        "@types/express-serve-static-core": "^4.17.33",
+        "@types/qs": "*",
+        "@types/serve-static": "*"
+      }
+    },
+    "node_modules/@types/express-serve-static-core": {
+      "version": "4.19.6",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
+      "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "@types/qs": "*",
+        "@types/range-parser": "*",
+        "@types/send": "*"
+      }
+    },
     "node_modules/@types/graceful-fs": {
       "version": "4.1.9",
       "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
@@ -6817,6 +7067,12 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/http-errors": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
+      "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
+      "license": "MIT"
+    },
     "node_modules/@types/istanbul-lib-coverage": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
@@ -6844,6 +7100,15 @@
         "@types/istanbul-lib-report": "*"
       }
     },
+    "node_modules/@types/jsonwebtoken": {
+      "version": "9.0.7",
+      "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz",
+      "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/keyv": {
       "version": "3.1.4",
       "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
@@ -6854,6 +7119,19 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/long": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
+      "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/@types/mime": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+      "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+      "license": "MIT"
+    },
     "node_modules/@types/ms": {
       "version": "0.7.34",
       "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
@@ -6869,6 +7147,47 @@
         "undici-types": "~6.19.8"
       }
     },
+    "node_modules/@types/qs": {
+      "version": "6.9.17",
+      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz",
+      "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==",
+      "license": "MIT"
+    },
+    "node_modules/@types/range-parser": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+      "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+      "license": "MIT"
+    },
+    "node_modules/@types/request": {
+      "version": "2.48.12",
+      "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz",
+      "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@types/caseless": "*",
+        "@types/node": "*",
+        "@types/tough-cookie": "*",
+        "form-data": "^2.5.0"
+      }
+    },
+    "node_modules/@types/request/node_modules/form-data": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz",
+      "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12",
+        "safe-buffer": "^5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.12"
+      }
+    },
     "node_modules/@types/responselike": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
@@ -6879,6 +7198,27 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/send": {
+      "version": "0.17.4",
+      "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
+      "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/mime": "^1",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/serve-static": {
+      "version": "1.15.7",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
+      "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/http-errors": "*",
+        "@types/node": "*",
+        "@types/send": "*"
+      }
+    },
     "node_modules/@types/stack-utils": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
@@ -6886,6 +7226,13 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/tough-cookie": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+      "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+      "license": "MIT",
+      "optional": true
+    },
     "node_modules/@types/validator": {
       "version": "13.12.2",
       "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz",
@@ -7020,6 +7367,19 @@
         "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
       }
     },
+    "node_modules/abort-controller": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+      "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "event-target-shim": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=6.5"
+      }
+    },
     "node_modules/accepts": {
       "version": "1.3.8",
       "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -7073,7 +7433,6 @@
       "version": "7.1.1",
       "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
       "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "debug": "^4.3.4"
@@ -7086,7 +7445,6 @@
       "version": "4.3.7",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
       "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "ms": "^2.1.3"
@@ -7104,7 +7462,6 @@
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
       "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/agentkeepalive": {
@@ -7410,6 +7767,16 @@
         "node": ">=8"
       }
     },
+    "node_modules/arrify": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+      "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/arrivals": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/arrivals/-/arrivals-2.1.2.tgz",
@@ -7917,11 +8284,31 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/async-retry": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz",
+      "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "retry": "0.13.1"
+      }
+    },
+    "node_modules/async-retry/node_modules/retry": {
+      "version": "0.13.1",
+      "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
+      "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 4"
+      }
+    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
       "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT"
     },
     "node_modules/at-least-node": {
@@ -8279,7 +8666,6 @@
       "version": "9.1.2",
       "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
       "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": "*"
@@ -8515,7 +8901,6 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
       "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
-      "dev": true,
       "license": "BSD-3-Clause"
     },
     "node_modules/buffer-from": {
@@ -9358,7 +9743,7 @@
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
       "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT",
       "dependencies": {
         "delayed-stream": "~1.0.0"
@@ -9535,6 +9920,19 @@
       "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
       "license": "MIT"
     },
+    "node_modules/cors": {
+      "version": "2.8.5",
+      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+      "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+      "license": "MIT",
+      "dependencies": {
+        "object-assign": "^4",
+        "vary": "^1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
     "node_modules/crc-32": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
@@ -9881,7 +10279,7 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT",
       "engines": {
         "node": ">=0.4.0"
@@ -10212,6 +10610,34 @@
         "present": "^0.0.3"
       }
     },
+    "node_modules/duplexify": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz",
+      "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "end-of-stream": "^1.4.1",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.1.1",
+        "stream-shift": "^1.0.2"
+      }
+    },
+    "node_modules/duplexify/node_modules/readable-stream": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/eastasianwidth": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -10222,7 +10648,6 @@
       "version": "1.0.11",
       "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
       "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
-      "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
         "safe-buffer": "^5.0.1"
@@ -10726,6 +11151,16 @@
         "es5-ext": "~0.10.14"
       }
     },
+    "node_modules/event-target-shim": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+      "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/eventemitter3": {
       "version": "4.0.7",
       "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@@ -10928,7 +11363,6 @@
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
       "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/external-editor": {
@@ -10959,11 +11393,20 @@
         "node": ">=0.6.0"
       }
     },
+    "node_modules/farmhash-modern": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz",
+      "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
     "node_modules/fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT"
     },
     "node_modules/fast-glob": {
@@ -11043,6 +11486,18 @@
         "reusify": "^1.0.4"
       }
     },
+    "node_modules/faye-websocket": {
+      "version": "0.11.4",
+      "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
+      "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "websocket-driver": ">=0.5.1"
+      },
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
     "node_modules/fb-watchman": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
@@ -11171,6 +11626,44 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/firebase-admin": {
+      "version": "13.0.1",
+      "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.0.1.tgz",
+      "integrity": "sha512-sKQ/Yw8o/WdC9qTKvuLMBjTbdcBISIXW4+M9PXk0bNjxEbZf1Er7EVq47eRb5+bnKof10xlns6zAIbj4tmSexg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@fastify/busboy": "^3.0.0",
+        "@firebase/database-compat": "^2.0.0",
+        "@firebase/database-types": "^1.0.6",
+        "@types/node": "^22.8.7",
+        "farmhash-modern": "^1.1.0",
+        "google-auth-library": "^9.14.2",
+        "jsonwebtoken": "^9.0.0",
+        "jwks-rsa": "^3.1.0",
+        "node-forge": "^1.3.1",
+        "uuid": "^11.0.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@google-cloud/firestore": "^7.10.0",
+        "@google-cloud/storage": "^7.14.0"
+      }
+    },
+    "node_modules/firebase-admin/node_modules/uuid": {
+      "version": "11.0.3",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz",
+      "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "license": "MIT",
+      "bin": {
+        "uuid": "dist/esm/bin/uuid"
+      }
+    },
     "node_modules/follow-redirects": {
       "version": "1.15.9",
       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
@@ -11344,6 +11837,13 @@
       "dev": true,
       "license": "ISC"
     },
+    "node_modules/functional-red-black-tree": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+      "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+      "license": "MIT",
+      "optional": true
+    },
     "node_modules/gauge": {
       "version": "4.0.4",
       "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
@@ -11417,6 +11917,103 @@
         "node": ">=8"
       }
     },
+    "node_modules/gaxios": {
+      "version": "6.7.1",
+      "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
+      "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "extend": "^3.0.2",
+        "https-proxy-agent": "^7.0.1",
+        "is-stream": "^2.0.0",
+        "node-fetch": "^2.6.9",
+        "uuid": "^9.0.1"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/gaxios/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/gaxios/node_modules/https-proxy-agent": {
+      "version": "7.0.5",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
+      "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
+      "license": "MIT",
+      "dependencies": {
+        "agent-base": "^7.0.2",
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/gaxios/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/gaxios/node_modules/uuid": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "license": "MIT",
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
+    "node_modules/gcp-metadata": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
+      "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
+      "license": "Apache-2.0",
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "gaxios": "^5.0.0",
+        "json-bigint": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/gcp-metadata/node_modules/gaxios": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz",
+      "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==",
+      "license": "Apache-2.0",
+      "optional": true,
+      "peer": true,
+      "dependencies": {
+        "extend": "^3.0.2",
+        "https-proxy-agent": "^5.0.0",
+        "is-stream": "^2.0.0",
+        "node-fetch": "^2.6.9"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/generate-function": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
@@ -11612,6 +12209,74 @@
         "node": ">=0.6.0"
       }
     },
+    "node_modules/google-auth-library": {
+      "version": "9.15.0",
+      "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.0.tgz",
+      "integrity": "sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "base64-js": "^1.3.0",
+        "ecdsa-sig-formatter": "^1.0.11",
+        "gaxios": "^6.1.1",
+        "gcp-metadata": "^6.1.0",
+        "gtoken": "^7.0.0",
+        "jws": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/google-auth-library/node_modules/gcp-metadata": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz",
+      "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "gaxios": "^6.0.0",
+        "json-bigint": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/google-gax": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz",
+      "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==",
+      "license": "Apache-2.0",
+      "optional": true,
+      "dependencies": {
+        "@grpc/grpc-js": "^1.10.9",
+        "@grpc/proto-loader": "^0.7.13",
+        "@types/long": "^4.0.0",
+        "abort-controller": "^3.0.0",
+        "duplexify": "^4.0.0",
+        "google-auth-library": "^9.3.0",
+        "node-fetch": "^2.7.0",
+        "object-hash": "^3.0.0",
+        "proto3-json-serializer": "^2.0.2",
+        "protobufjs": "^7.3.2",
+        "retry-request": "^7.0.0",
+        "uuid": "^9.0.1"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/google-gax/node_modules/uuid": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
     "node_modules/google-protobuf": {
       "version": "3.6.1",
       "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.6.1.tgz",
@@ -11663,6 +12328,19 @@
       "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
       "license": "ISC"
     },
+    "node_modules/gtoken": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
+      "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
+      "license": "MIT",
+      "dependencies": {
+        "gaxios": "^6.0.0",
+        "jws": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
     "node_modules/has-flag": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -11791,6 +12469,23 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/html-entities": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",
+      "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/mdevils"
+        },
+        {
+          "type": "patreon",
+          "url": "https://patreon.com/mdevils"
+        }
+      ],
+      "license": "MIT",
+      "optional": true
+    },
     "node_modules/html-escaper": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -11841,6 +12536,12 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/http-parser-js": {
+      "version": "0.5.8",
+      "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
+      "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==",
+      "license": "MIT"
+    },
     "node_modules/http-proxy-agent": {
       "version": "7.0.2",
       "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
@@ -14601,6 +15302,15 @@
         "@sideway/pinpoint": "^2.0.0"
       }
     },
+    "node_modules/jose": {
+      "version": "4.15.9",
+      "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
+      "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/panva"
+      }
+    },
     "node_modules/js-beautify": {
       "version": "1.15.1",
       "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz",
@@ -14686,7 +15396,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
       "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "bignumber.js": "^9.0.0"
@@ -14767,7 +15476,6 @@
       "version": "9.0.2",
       "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
       "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "jws": "^3.2.2",
@@ -14790,7 +15498,6 @@
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
       "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "buffer-equal-constant-time": "1.0.1",
@@ -14802,7 +15509,6 @@
       "version": "3.2.2",
       "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
       "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "jwa": "^1.4.1",
@@ -14813,14 +15519,12 @@
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
       "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/jwa": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
       "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "buffer-equal-constant-time": "1.0.1",
@@ -14828,11 +15532,50 @@
         "safe-buffer": "^5.0.1"
       }
     },
+    "node_modules/jwks-rsa": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz",
+      "integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/express": "^4.17.17",
+        "@types/jsonwebtoken": "^9.0.2",
+        "debug": "^4.3.4",
+        "jose": "^4.14.6",
+        "limiter": "^1.1.5",
+        "lru-memoizer": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/jwks-rsa/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/jwks-rsa/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
     "node_modules/jws": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
       "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "jwa": "^2.0.0",
@@ -14938,6 +15681,11 @@
         "url": "https://github.com/sponsors/antonk52"
       }
     },
+    "node_modules/limiter": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
+      "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
+    },
     "node_modules/lines-and-columns": {
       "version": "1.2.4",
       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -14971,7 +15719,13 @@
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
       "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
-      "dev": true,
+      "devOptional": true,
+      "license": "MIT"
+    },
+    "node_modules/lodash.clonedeep": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+      "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
       "license": "MIT"
     },
     "node_modules/lodash.defaults": {
@@ -14999,42 +15753,36 @@
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
       "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/lodash.isboolean": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
       "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/lodash.isinteger": {
       "version": "4.0.4",
       "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
       "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/lodash.isnumber": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
       "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/lodash.isplainobject": {
       "version": "4.0.6",
       "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
       "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/lodash.isstring": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
       "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/lodash.merge": {
@@ -15048,7 +15796,6 @@
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
       "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/lodash.union": {
@@ -15109,6 +15856,28 @@
         "node": ">=12"
       }
     },
+    "node_modules/lru-memoizer": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz",
+      "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==",
+      "license": "MIT",
+      "dependencies": {
+        "lodash.clonedeep": "^4.5.0",
+        "lru-cache": "6.0.0"
+      }
+    },
+    "node_modules/lru-memoizer/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "license": "ISC",
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/lru-queue": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
@@ -16074,6 +16843,57 @@
         "node": ">=6.0.0"
       }
     },
+    "node_modules/node-fetch": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+      "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+      "license": "MIT",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/node-fetch/node_modules/tr46": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+      "license": "MIT"
+    },
+    "node_modules/node-fetch/node_modules/webidl-conversions": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+      "license": "BSD-2-Clause"
+    },
+    "node_modules/node-fetch/node_modules/whatwg-url": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+      "license": "MIT",
+      "dependencies": {
+        "tr46": "~0.0.3",
+        "webidl-conversions": "^3.0.0"
+      }
+    },
+    "node_modules/node-forge": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
+      "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
+      "license": "(BSD-3-Clause OR GPL-2.0)",
+      "engines": {
+        "node": ">= 6.13.0"
+      }
+    },
     "node_modules/node-gyp": {
       "version": "10.2.0",
       "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz",
@@ -16442,6 +17262,16 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/object-hash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+      "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/object-inspect": {
       "version": "1.13.3",
       "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
@@ -17429,11 +18259,24 @@
       "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
       "license": "ISC"
     },
+    "node_modules/proto3-json-serializer": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz",
+      "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==",
+      "license": "Apache-2.0",
+      "optional": true,
+      "dependencies": {
+        "protobufjs": "^7.2.5"
+      },
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
     "node_modules/protobufjs": {
       "version": "7.4.0",
       "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
       "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==",
-      "dev": true,
+      "devOptional": true,
       "hasInstallScript": true,
       "license": "BSD-3-Clause",
       "dependencies": {
@@ -17964,6 +18807,21 @@
       "integrity": "sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA==",
       "license": "MIT"
     },
+    "node_modules/retry-request": {
+      "version": "7.0.2",
+      "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz",
+      "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@types/request": "^2.48.8",
+        "extend": "^3.0.2",
+        "teeny-request": "^9.0.0"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
     "node_modules/reusify": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -18774,6 +19632,16 @@
         "semver": "^7.3.5"
       }
     },
+    "node_modules/sqlite3/node_modules/@tootallnate/once": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
+      "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/sqlite3/node_modules/abbrev": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -19231,6 +20099,23 @@
         "npm": ">=6"
       }
     },
+    "node_modules/stream-events": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz",
+      "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "stubs": "^3.0.0"
+      }
+    },
+    "node_modules/stream-shift": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
+      "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
+      "license": "MIT",
+      "optional": true
+    },
     "node_modules/streamsearch": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -19417,6 +20302,13 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/stubs": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
+      "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==",
+      "license": "MIT",
+      "optional": true
+    },
     "node_modules/stylus-lookup": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-5.0.1.tgz",
@@ -19709,6 +20601,90 @@
         "bintrees": "1.0.2"
       }
     },
+    "node_modules/teeny-request": {
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz",
+      "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==",
+      "license": "Apache-2.0",
+      "optional": true,
+      "dependencies": {
+        "http-proxy-agent": "^5.0.0",
+        "https-proxy-agent": "^5.0.0",
+        "node-fetch": "^2.6.9",
+        "stream-events": "^1.0.5",
+        "uuid": "^9.0.0"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/teeny-request/node_modules/agent-base": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+      "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 6.0.0"
+      }
+    },
+    "node_modules/teeny-request/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/teeny-request/node_modules/http-proxy-agent": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+      "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@tootallnate/once": "2",
+        "agent-base": "6",
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/teeny-request/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/teeny-request/node_modules/uuid": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+      "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
     "node_modules/temp": {
       "version": "0.9.4",
       "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz",
@@ -20624,6 +21600,29 @@
         "node": ">=12"
       }
     },
+    "node_modules/websocket-driver": {
+      "version": "0.7.4",
+      "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
+      "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "http-parser-js": ">=0.5.1",
+        "safe-buffer": ">=5.1.0",
+        "websocket-extensions": ">=0.1.1"
+      },
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/websocket-extensions": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
+      "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
     "node_modules/whatwg-encoding": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
diff --git a/package.json b/package.json
index ef71981458f515f0d739371d51a744398a8513fc..346adb30a86ed84f9edfcd1e3b6b4bc2acc37c0f 100644
--- a/package.json
+++ b/package.json
@@ -15,9 +15,12 @@
   "dependencies": {
     "connect-flash": "^0.1.1",
     "cookie-parser": "^1.4.7",
+    "cors": "^2.8.5",
     "dotenv": "^16.4.5",
     "express": "^4.21.1",
     "express-session": "^1.18.1",
+    "firebase-admin": "^13.0.1",
+    "jest": "^29.7.0",
     "joi": "^17.13.3",
     "morgan": "^1.10.0",
     "multer": "^1.4.5-lts.1",
diff --git a/routes/memberRoute.js b/routes/memberRoute.js
new file mode 100644
index 0000000000000000000000000000000000000000..fe5cacc87b90e4f9c17cf0ea6bc6fed02ecae5ba
--- /dev/null
+++ b/routes/memberRoute.js
@@ -0,0 +1,8 @@
+const express = require('express');
+const router = express.Router();
+const MemberController = require('../controllers/memberController');
+
+// FCM �좏겙 ����
+router.post('/register-token', MemberController.registerToken);
+
+module.exports = router;
\ No newline at end of file
diff --git a/services/chatService.js b/services/chatService.js
index 079a400ac0cdd1fc50382e1153ea4d14cde37714..e53ceaa0c5dd4e4c329fe5d6765de8de7efd6c99 100644
--- a/services/chatService.js
+++ b/services/chatService.js
@@ -2,58 +2,46 @@ const ChatRoom = require('../models/ChatRooms');
 const { v4: uuidv4 } = require('uuid');
 
 class ChatService {
-
   // 梨꾪똿諛� �앹꽦
-  async createChatRoom({ meeting_id, participants }) {
-    try {
-      const chatRoomId = uuidv4();
-      const newRoom = new ChatRoom({
-        chatRoomId: chatRoomId,
-        meeting_id,
-        participants,
-        messages: [],
-        lastReadAt: participants.reduce((acc, user) => {
-          acc[user] = new Date();
-          return acc;
-        }, {}),
-        lastReadLogId: participants.reduce((acc, user) => {
-          acc[user] = null;
-          return acc;
-        }, {}),
-        isOnline: participants.reduce((acc, user) => {
-          acc[user] = true;
-          return acc;
-        }, {}),
-      });
-
-      const joinMessage = {
-        message: `${participants[0]}�섏씠 踰덇컻 紐⑥엫�� �앹꽦�덉뒿�덈떎.`,
-        timestamp: new Date(),
-        type: 'join',
-      };
+  async createChatRoom({ meeting_id, participants, chatRoomName }) {
+  try {
+    const chatRoomId = uuidv4();
+    const newRoom = new ChatRoom({
+      chatRoomId,
+      chatRoomName,
+      meeting_id,
+      messages: [],
+    });
 
-      newRoom.messages.push(joinMessage);
-      await newRoom.save();
+    const joinMessage = {
+      message: `${participants[0].name}�섏씠 踰덇컻 紐⑥엫�� �앹꽦�덉뒿�덈떎.`,
+      timestamp: new Date(),
+      type: 'join',
+    };
 
-      return { success: true, chatRoomId };
-    } catch (err) {
-      console.error('Error creating chat room:', err);
-      throw new Error('Failed to create chat room');
-    }
+    newRoom.messages.push(joinMessage);
+    await newRoom.save();
+
+    return { success: true, chatRoomId };
+  } catch (err) {
+    console.error('Error creating chat room:', err);
+    throw new Error('Failed to create chat room');
   }
+}
 
   // 梨꾪똿諛� 紐⑸줉 議고쉶
   async getChatRooms() {
-    const rooms = await ChatRoom.find({}, { chatRoomId: 1, messages: { $slice: -1 } });
+    const rooms = await ChatRoom.find({}, { chatRoomId: 1, chatRoomName: 1, messages: { $slice: -1 } });
     return rooms.map(room => {
       const lastMessage = room.messages[0] || {};
       return {
         chatRoomId: room.chatRoomId,
+        chatRoomName: room.chatRoomName,
         lastMessage: {
           sender: lastMessage.sender || '�놁쓬',
           message: lastMessage.message || '硫붿떆吏� �놁쓬',
           timestamp: lastMessage.timestamp || null,
-        }
+        },
       };
     });
   }
@@ -61,7 +49,7 @@ class ChatService {
   // �ъ슜�� �곹깭 �낅뜲�댄듃
   async updateStatus(chatRoomId, nickname, isOnline) {
     await ChatRoom.updateOne(
-      { chatRoomId },
+      { chatRoomId, "participants.name": nickname },
       { $set: { [`isOnline.${nickname}`]: isOnline } }
     );
   }
@@ -70,14 +58,14 @@ class ChatService {
   async updateReadStatus(chatRoomId, nickname) {
     const now = new Date();
     await ChatRoom.updateOne(
-      { chatRoomId },
+      { chatRoomId, "participants.name": nickname },
       { $set: { [`lastReadAt.${nickname}`]: now } }
     );
   }
 
   // �쎌� �딆� 硫붿떆吏� 議고쉶
   async getUnreadMessages(nickname) {
-    const chatRooms = await ChatRoom.find({ participants: nickname });
+    const chatRooms = await ChatRoom.find({ "participants.name": nickname });
     return await Promise.all(chatRooms.map(async (chatRoom) => {
       const lastReadAt = chatRoom.lastReadAt.get(nickname) || new Date(0);
       const unreadMessagesCount = chatRoom.messages.filter(message => 
@@ -98,31 +86,49 @@ class ChatService {
     }
 
     const unreadCounts = chatRoom.participants
-      .filter(user => chatRoom.lastReadLogId.get(user))
-      .map(user => chatRoom.lastReadLogId.get(user))
+      .filter(participant => chatRoom.lastReadLogId.has(participant.name)) // Map�� 議댁옱�섎뒗 �ㅻ쭔 泥섎━
+      .map(participant => chatRoom.lastReadLogId.get(participant.name)) // lastReadLogId 媛� 異붿텧
       .reduce((acc, logId) => {
-        acc[logId] = (acc[logId] || 0) + 1;
+        acc[logId] = (acc[logId] || 0) + 1; // logId 湲곗��쇰줈 �깆옣 �잛닔 �꾩쟻
         return acc;
       }, {});
 
     let count = 0;
-    return Object.entries(unreadCounts)
-      .sort(([logId1], [logId2]) => logId1.localeCompare(logId2))
+    const sortedUnreadCounts = Object.entries(unreadCounts)
+      .sort(([logId1], [logId2]) => logId1.localeCompare(logId2)) // logId 湲곗� �ㅻ쫫李⑥닚 �뺣젹
       .reduce((acc, [logId, value]) => {
-        count += value;
-        acc[count] = logId;
+        count += value; // �꾩쟻 �⑷퀎
+        acc[count] = logId; // �꾩쟻 �⑷퀎瑜� �ㅻ줈 ����
         return acc;
       }, {});
+
+    return sortedUnreadCounts;
   }
 
   // �쎌� 硫붿떆吏� 濡쒓렇 ID �낅뜲�댄듃
   async updateReadLogId(chatRoomId, nickname, logId) {
     await ChatRoom.updateOne(
-      { chatRoomId },
+      { chatRoomId, "participants.name": nickname },
       { $set: { [`lastReadLogId.${nickname}`]: logId } }
     );
   }
 
+  // FCM �좏겙 �낅뜲�댄듃
+  async updateFcmToken(chatRoomId, nickname, fcmToken) {
+    const chatRoom = await ChatRoom.findOne({ chatRoomId, "participants.name": nickname });
+    if (!chatRoom) {
+      throw new Error('Chat room or participant not found');
+    }
+
+    const participant = chatRoom.participants.find(p => p.name === nickname);
+    if (participant) {
+      if (!participant.fcmTokens.includes(fcmToken)) {
+        participant.fcmTokens.push(fcmToken);
+        await chatRoom.save();
+      }
+    }
+  }
+
   // �곹깭�� 濡쒓렇 ID �숈떆 �낅뜲�댄듃
   async updateStatusAndLogId(chatRoomId, nickname, isOnline, logId) {
     let finalLogId = logId;
@@ -135,7 +141,7 @@ class ChatService {
     }
 
     await ChatRoom.updateOne(
-      { chatRoomId },
+      { chatRoomId, "participants.name": nickname },
       {
         $set: {
           [`isOnline.${nickname}`]: isOnline,
@@ -144,6 +150,65 @@ class ChatService {
       }
     );
   }
+
+  // 硫붿떆吏� �꾩넚
+  async sendMessage(chatRoomId, sender, messageContent) {
+    try {
+      // 梨꾪똿諛� 議고쉶
+      const chatRoom = await ChatRoom.findOne({ chatRoomId });
+      if (!chatRoom) {
+        throw new Error('Chat room not found');
+      }
+
+      // 硫붿떆吏� 異붽�
+      const newMessage = {
+        sender,
+        message: messageContent,
+        timestamp: new Date(),
+        type: 'message',
+      };
+      chatRoom.messages.push(newMessage);
+      await chatRoom.save();
+
+      // �ㅽ봽�쇱씤 �ъ슜�� 李얘린
+      const offlineParticipants = chatRoom.participants.filter(
+        participant => !chatRoom.isOnline[participant.name]
+      );
+
+      // �ㅽ봽�쇱씤 �ъ슜�먮뱾�먭쾶 FCM �몄떆 �뚮┝ �꾩넚
+      for (const participant of offlineParticipants) {
+        const tokens = participant.fcmTokens || [];
+        if (tokens.length > 0) {
+          const message = {
+            notification: {
+              title: `�� 硫붿떆吏�: ${chatRoom.chatRoomName}`,
+              body: `${sender}: ${messageContent}`,
+            },
+            tokens,
+          };
+
+          try {
+            const response = await admin.messaging().sendMulticast(message);
+            console.log(
+              `�몄떆 �뚮┝ �꾩넚 �깃났 (${participant.name}):`,
+              response.successCount
+            );
+          } catch (error) {
+            console.error(
+              `�몄떆 �뚮┝ �꾩넚 �ㅽ뙣 (${participant.name}):`,
+              error
+            );
+          }
+        }
+      }
+
+      return newMessage;
+    } catch (error) {
+      console.error('Error sending message:', error);
+      throw new Error('Failed to send message');
+    }
+  }
+
 }
 
-module.exports = new ChatService();
+module.exports = new ChatService();
\ No newline at end of file
diff --git a/services/meetingService.js b/services/meetingService.js
index 389942dd439ba2b9e5c61bd1dcc61708d3beedf2..d7b6023a2ecfebfe98d55ca4a7d43496c3bf3f2b 100644
--- a/services/meetingService.js
+++ b/services/meetingService.js
@@ -1,3 +1,7 @@
+
+const { Meeting, MeetingParticipant, User, Schedule } = require('../models');
+const ChatRoom = require('../models/chatRooms');
+const FcmToken = require('../models/fcmToken');
 // services/meetingService.js
 const { v4: uuidv4 } = require('uuid');
 const { Op } = require('sequelize');
@@ -7,7 +11,8 @@ const ChatRooms = require('../models/ChatRooms');
 const MeetingResponseDTO = require('../dtos/MeetingResponseDTO');
 const MeetingDetailResponseDTO = require('../dtos/MeetingDetailResponseDTO');
 const CreateMeetingRequestDTO = require('../dtos/CreateMeetingRequestDTO');
-const ScheduleService = require('./scheduleService');
+const ScheduleService = require('./scheduleService'); // ScheduleService �꾪룷��
+const chatService = require('./chatService');
 
 class MeetingService {
     /**
@@ -27,7 +32,6 @@ class MeetingService {
     }
 
     async createMeeting(meetingData) {
-        // DTO瑜� �ъ슜�섏뿬 �붿껌 �곗씠�� 寃�利�
         const createMeetingDTO = new CreateMeetingRequestDTO(meetingData);
         createMeetingDTO.validate();
 
@@ -43,26 +47,31 @@ class MeetingService {
             max_num,
         } = meetingData;
 
-        // �ъ슜�� 議댁옱 �щ� �뺤씤
-        const user = await User.findOne({ where: { id: created_by } });
-        if (!user) {
-            throw new Error('�ъ슜�먮� 李얠쓣 �� �놁뒿�덈떎.');
+        // �ъ슜�먯� FCM �좏겙 議고쉶
+        const user = await this._findUserWithFcmTokens(created_by);
+        const userFcmTokens = user.fcmTokenList.map((fcmToken) => fcmToken.token);
+
+        // �ㅼ�以� 異⑸룎 �뺤씤
+        const hasConflict = await ScheduleService.checkScheduleOverlap(
+            created_by,
+            new Date(start_time),
+            new Date(end_time)
+        );
+
+        if (hasConflict) {
+            throw new Error('�ㅼ�以꾩씠 寃뱀묩�덈떎. �ㅻⅨ �쒓컙�� �좏깮�댁<�몄슂.');
         }
 
         // �몃옖��뀡�� �ъ슜�섏뿬 紐⑥엫 �앹꽦怨� �ㅼ�以� 異붽�瑜� �먯옄�곸쑝濡� 泥섎━
-        const result = await sequelize.transaction(async (transaction) => {
-            // 梨꾪똿諛� �앹꽦 (MongoDB)
-            const chatRoomId = uuidv4(); // 怨좎쑀�� 梨꾪똿諛� ID �앹꽦
-            const chatRoomData = {
-                chatRoomId,
-                participants: [user.name],
-                messages: [],
-                lastReadAt: {},
-                lastReadLogId: {},
-                isOnline: {},
-            };
-            const chatRoom = new ChatRooms(chatRoomData);
-            await chatRoom.save();
+        return await Meeting.sequelize.transaction(async (transaction) => {
+            const chatRoomData = this._constructChatRoomData(title, user, userFcmTokens);
+            const chatRoomResponse = await chatService.createChatRoom(chatRoomData);
+
+            if (!chatRoomResponse.success) {
+                throw new Error('梨꾪똿諛� �앹꽦 �ㅽ뙣');
+            }
+
+            const chatRoomId = chatRoomResponse.chatRoomId;
 
             // 紐⑥엫 �앹꽦
             const newMeeting = await Meeting.create(
@@ -113,11 +122,23 @@ class MeetingService {
                 time_idx_start,
                 time_idx_end,
             }, transaction);
+            await ScheduleService.createSchedule({
+                userId: created_by,
+                title: `踰덇컻 紐⑥엫: ${title}`,
+                start_time: new Date(start_time),
+                end_time: new Date(end_time),
+                is_fixed: true,
+            });
+
+            const chatRoom = await ChatRoom.findOne({ chatRoomId: chatRoomId });
+
+            if (chatRoom) {
+                console.log("梨꾪똿諛� 李얠쓬");
+                this._addParticipantToChatRoom(chatRoom, user, userFcmTokens);
+            }
 
             return { meeting_id: newMeeting.id, chatRoomId, invitedFriendIds };
         });
-
-        return result;
     }
 
     async sendInvites({ meetingId, creatorId, time_idx_start, time_idx_end }, transaction) {
@@ -394,8 +415,40 @@ class MeetingService {
 
           // �꾩옱 �몄썝 �� 利앷�
           await meeting.increment("cur_num", { by: 1, transaction });
+        await Meeting.sequelize.transaction(async (transaction) => {
+            const hasConflict = await ScheduleService.checkScheduleOverlap(
+                userId,
+                new Date(meeting.start_time),
+                new Date(meeting.end_time)
+            );
+            if (hasConflict) {
+                throw new Error('�ㅼ�以꾩씠 寃뱀묩�덈떎. �ㅻⅨ 紐⑥엫�� 李멸��섏꽭��.');
+            }
+
+            await MeetingParticipant.create({ meeting_id: meetingId, user_id: userId }, { transaction });
+
+            await ScheduleService.createSchedule({
+                userId,
+                title: `踰덇컻 紐⑥엫: ${meeting.title}`,
+                start_time: new Date(meeting.start_time),
+                end_time: new Date(meeting.end_time),
+                is_fixed: true,
+            });
+
+            // �ъ슜�먯� FCM �좏겙 議고쉶
+            const user = await this._findUserWithFcmTokens(userId);
+            const userFcmTokens = user.fcmTokenList.map((fcmToken) => fcmToken.token);
+
+            const chatRoom = await ChatRoom.findOne({ chatRoomId: meeting.chatRoomId });
+
+            if (chatRoom) {
+                console.log("梨꾪똿諛� 李얠쓬");
+                this._addParticipantToChatRoom(chatRoom, user, userFcmTokens);
+            }
         });
+    });
     }
+    
 
     
     async getMeetingDetail(meetingId) {
@@ -426,6 +479,79 @@ class MeetingService {
 
         return new MeetingDetailResponseDTO(meeting);
     }
+
+    /**
+     * 踰덇컻 紐⑥엫 留덇컧
+     */
+    async closeMeeting(meetingId) {
+        const meeting = await Meeting.findByPk(meetingId);
+
+        if (!meeting) {
+            throw new Error('紐⑥엫�� 李얠쓣 �� �놁뒿�덈떎.');
+        }
+
+        if (meeting.type === 'CLOSE') {
+            throw new Error('�대� 留덇컧�� 紐⑥엫�낅땲��.');
+        }
+
+        meeting.type = 'CLOSE';
+        await meeting.save();
+        return meeting;
+    }
+
+    // Helper functions
+    async _findUserWithFcmTokens(userId) {
+        const user = await User.findOne({
+            where: { id: userId },
+            include: [
+                {
+                    model: FcmToken,
+                    as: 'fcmTokenList',
+                    attributes: ['token'],
+                },
+            ],
+        });
+
+        if (!user) {
+            throw new Error('�ъ슜�먮� 李얠쓣 �� �놁뒿�덈떎.');
+        }
+
+        return user;
+    }
+
+    _constructChatRoomData(title, user, userFcmTokens) {
+        return {
+            meeting_id: null,
+            participants: [
+                {
+                    name: user.name,
+                    fcmTokens: userFcmTokens || [],
+                    isOnline: true,
+                    lastReadAt: new Date(),
+                    lastReadLogId: null,
+                },
+            ],
+            chatRoomName: title,
+        };
+    }
+
+    _addParticipantToChatRoom(chatRoom, user, userFcmTokens) {
+        // Map �꾨뱶媛� 珥덇린�붾릺吏� �딆� 寃쎌슦 湲곕낯媛� �ㅼ젙
+        if (!chatRoom.isOnline) chatRoom.isOnline = new Map();
+        if (!chatRoom.lastReadAt) chatRoom.lastReadAt = new Map();
+        if (!chatRoom.lastReadLogId) chatRoom.lastReadLogId = new Map();
+
+        // 李멸��� 異붽� 濡쒖쭅
+        if (!chatRoom.participants.some(participant => participant.name === user.name)) {
+            chatRoom.participants.push({ name: user.name, fcmTokens: userFcmTokens });
+            chatRoom.isOnline.set(user.name, true);
+            chatRoom.lastReadAt.set(user.name, new Date());
+            chatRoom.lastReadLogId.set(user.name, null);
+        }
+
+        // ����
+        chatRoom.save();
+    }
 }
 
-module.exports = new MeetingService();
+module.exports = new MeetingService();
\ No newline at end of file
diff --git a/services/memberService.js b/services/memberService.js
new file mode 100644
index 0000000000000000000000000000000000000000..bf99182e396b34a9e3d81b65d692dca133065734
--- /dev/null
+++ b/services/memberService.js
@@ -0,0 +1,47 @@
+const User = require('../models/User');
+const FcmToken = require('../models/fcmToken');
+const ChatRoom = require('../models/chatRooms');
+
+class MemberService {
+  async registerToken(email, fcmToken) {
+    console.log(`Registering FCM token for email: ${email}, token: ${fcmToken}`);
+
+    // 1. RDB�먯꽌 �ъ슜�� 寃���
+    const user = await User.findOne({ where: { email } });
+    if (!user) throw new Error('User not found');
+
+    console.log(`User found: ${user.name}`);
+
+    // 2. RDB�� FcmTokens �뚯씠釉붿뿉 ����
+    const existingToken = await FcmToken.findOne({
+      where: { userId: user.id, token: fcmToken },
+    });
+
+    if (!existingToken) {
+      await FcmToken.create({ userId: user.id, token: fcmToken });
+      console.log(`FCM token ${fcmToken} saved to FcmTokens table`);
+    } else {
+      console.log(`FCM token ${fcmToken} already exists for user ${user.name}`);
+    }
+
+    // 3. MongoDB�먯꽌 愿��� 梨꾪똿諛⑹쓽 FCM �좏겙 �낅뜲�댄듃
+    const existingChatRooms = await ChatRoom.find({ "participants.name": user.name });
+    for (const room of existingChatRooms) {
+      room.participants = room.participants.map((participant) => {
+        if (participant.name === user.name) {
+          const currentFcmTokens = participant.fcmTokens || [];
+          if (!currentFcmTokens.includes(fcmToken)) {
+            participant.fcmTokens = Array.from(new Set([...currentFcmTokens, fcmToken]));
+          }
+        }
+        return participant;
+      });
+      await room.save();
+    }
+
+    console.log(`FCM token registration process completed for email: ${email}`);
+    return { message: 'FCM token registered successfully' };
+  }
+}
+
+module.exports = new MemberService();
\ No newline at end of file
diff --git a/wsServer.js b/wsServer.js
index 3f303b5db4c2b4aadd965b7adead567e547a4efc..12ef05ac45bf8e16ca44f419ce6974bffbb9f3d1 100644
--- a/wsServer.js
+++ b/wsServer.js
@@ -2,8 +2,21 @@ const http = require('http');
 const crypto = require('crypto');
 // const ChatRoom = require('./models/chatRoom.js');
 const mongoose = require('mongoose');
+const admin = require('firebase-admin');
+const dotenv = require('dotenv');
 const ChatRoom = require('./models/chatRooms');
 
+// .env �뚯씪 濡쒕뱶
+dotenv.config();
+
+// �쒕퉬�� 怨꾩젙 �� �뚯씪 寃쎈줈瑜� �섍꼍 蹂��섏뿉�� 媛��몄삤湲�
+const serviceAccountPath = process.env.FIREBASE_CREDENTIAL_PATH;
+
+// Firebase Admin SDK 珥덇린��
+admin.initializeApp({
+  credential: admin.credential.cert(require(serviceAccountPath)),
+});
+
 // WebSocket 愿��� �곗씠��
 let clients = [];
 let chatRooms = {};
@@ -85,8 +98,6 @@ function startWebSocketServer() {
         if (type === 'join') {
           chatRoomId = clientChatRoomId;
           nickname = clientNickname;
-          console.log("join�� chatRoomId", chatRoomId);
-          console.log("join�� nickname", nickname);
 
           await ChatRoom.updateOne(
             { chatRoomId },
@@ -103,32 +114,42 @@ function startWebSocketServer() {
           }
 
           const chatRoom = await ChatRoom.findOne({ chatRoomId });
-          console.log("join�� chatRoom", chatRoom);
-          if (!chatRoom) {
-            console.error(`ChatRoom�� 李얠쓣 �� �놁뒿�덈떎: chatRoomId = ${chatRoomId}`);
-          } else {
-            console.log(`ChatRoom 議고쉶 �깃났: ${chatRoom}`);
-          }
 
-          const isAlreadyParticipant = chatRoom.participants.includes(nickname);
-          if (!isAlreadyParticipant) {
+          // 李멸��� �뺤씤
+          const participantIndex = chatRoom.participants.findIndex(participant => participant.name === nickname);
+          if (participantIndex !== -1) {
+            const existingParticipant = chatRoom.participants[participantIndex];
+
+            // 李멸��� �곹깭 �낅뜲�댄듃
+            existingParticipant.isOnline = true;
+            existingParticipant.lastReadAt = new Date();
+
+            await chatRoom.save();
+          } else {
+            // �� 李멸��� 異붽�
             const joinMessage = {
               message: `${nickname}�섏씠 李멸��덉뒿�덈떎.`,
               timestamp: new Date(),
               type: 'join'
             };
 
-            chatRooms[chatRoomId].push(joinMessage);
-
-            await ChatRoom.updateOne({ chatRoomId }, {
-              $push: { messages: joinMessage, participants: nickname }
+            chatRoom.participants.push({
+              name: nickname,
+              fcmTokens: parsedData.fcmToken ? [parsedData.fcmToken] : [],
+              lastReadAt: new Date(),
+              lastReadLogId: null,
+              isOnline: true,
             });
 
+            chatRoom.messages.push(joinMessage);
+
+            await chatRoom.save();
+
             clients.forEach(client => {
               client.write(constructReply(JSON.stringify(joinMessage)));
             });
-          } else {
-            console.log(`${nickname}�� �대� 梨꾪똿諛⑹뿉 李멸� 以묒엯�덈떎.`);
+
+            console.log(`${nickname} �� 李멸��먮줈 異붽�`);
           }
 
           try {
@@ -177,12 +198,63 @@ function startWebSocketServer() {
               client.write(constructReply(JSON.stringify(messageData)));
               console.log('梨꾪똿 硫붿떆吏� �꾩넚:', messageData);
             });
-  
+
+            // �ㅽ봽�쇱씤 �ъ슜�먯뿉寃� FCM �몄떆 �뚮┝ �꾩넚
+            const chatRoom = await ChatRoom.findOne({ chatRoomId });
+            const offlineParticipants = chatRoom.participants.filter(participant => {
+            // isOnline �곹깭瑜� Map�먯꽌 媛��몄삤湲�
+            const isOnline = chatRoom.isOnline.get(participant.name);
+              return isOnline === false; // �뺥솗�� false�� �ъ슜�먮쭔 �꾪꽣留�
+            });
+
+
+            for (const participant of offlineParticipants) {
+              const tokens = participant.fcmTokens || [];
+              // console("�몄떆 �뚮┝ 蹂대궡�� �좏겙", tokens);
+              if (tokens.length > 0) {
+                const message = {
+                  tokens, // FCM �좏겙 諛곗뿴
+                  notification: {
+                    title: `${chatRoom.chatRoomName}`,
+                    body: `${nickname}: ${text}`,
+                  },
+                  data: {
+                    key1: 'value1',
+                    key2: 'value2',
+                  },
+                  android: {
+                    priority: 'high',
+                  },
+                  apns: {
+                    payload: {
+                      aps: {
+                        sound: 'default',
+                      },
+                    },
+                  },
+                };
+
+                try {
+                  console.log(`�몄떆 �뚮┝ �꾩넚 以� (${participant.name}):`, message); // �붾쾭源� 濡쒓렇 異붽�
+                  const response = await admin.messaging().sendEachForMulticast(message);
+                  console.log(`�몄떆 �뚮┝ �꾩넚 �깃났 (${participant.name}):`, response.successCount);
+                } catch (error) {
+                  console.error(`�몄떆 �뚮┝ �꾩넚 �ㅽ뙣 (${participant.name}):`, error);
+                }
+              } else {
+                console.log(`�ъ슜�� ${participant.name}�� FCM �좏겙�� �놁뒿�덈떎.`);
+              }
+            }
           } catch (err) {
             console.error('MongoDB 梨꾪똿 硫붿떆吏� ���� �ㅻ쪟:', err);
           }
         } else if (type === 'leave') {
-          const leaveMessage = { message: `${nickname}�섏씠 �댁옣�덉뒿�덈떎.`, timestamp: new Date() };
+          const leaveMessage = { 
+            message: `${nickname}�섏씠 �댁옣�덉뒿�덈떎.`, 
+            timestamp: new Date(),
+            type: 'leave'
+          };
+          
           chatRooms[chatRoomId].push(leaveMessage);
 
           await ChatRoom.updateOne(