diff --git a/client/mobile/package.json b/client/mobile/package.json index 276b76e7..3978bb8a 100644 --- a/client/mobile/package.json +++ b/client/mobile/package.json @@ -22,6 +22,8 @@ "react-native-reanimated": "^2.14.4", "react-native-ui-lib": "^6.29.1", "react-native-webview": "^11.26.1", + "socket.io-client": "^4.6.1", + "socket.io-msgpack-parser": "^3.0.2", "tailchat-client-sdk": "^1.0.0", "url-regex": "^5.0.0", "zustand": "^4.3.2" diff --git a/client/mobile/src/lib/inject/message-handler.ts b/client/mobile/src/lib/inject/message-handler.ts index 16bb2f3e..eb728dc1 100644 --- a/client/mobile/src/lib/inject/message-handler.ts +++ b/client/mobile/src/lib/inject/message-handler.ts @@ -2,6 +2,8 @@ import type WebView from 'react-native-webview'; import { generateInstallPluginScript } from '.'; import { useUIStore } from '../../store/ui'; import { showNotification } from '../notifications'; +import type { UserBaseInfo } from '../../types'; +import { bindSocketEvent, createSocket } from '../socket'; export function handleTailchatMessage( type: string, @@ -30,4 +32,15 @@ export function handleTailchatMessage( return; } + + if (type === 'bindWebsocket') { + const serviceUrl: string = payload.url; + const token: string = payload.token; + const userInfo = payload.userInfo as UserBaseInfo; + + createSocket(serviceUrl, token).then((socket) => { + console.log('[createSocket]', 'socket', socket, 'userInfo', userInfo); + bindSocketEvent(socket); + }); + } } diff --git a/client/mobile/src/lib/socket/index.ts b/client/mobile/src/lib/socket/index.ts new file mode 100644 index 00000000..753a5fa8 --- /dev/null +++ b/client/mobile/src/lib/socket/index.ts @@ -0,0 +1,46 @@ +import { io, Socket } from 'socket.io-client'; +import msgpackParser from 'socket.io-msgpack-parser'; +import _isNil from 'lodash/isNil'; +import type { InboxItem } from '../../types'; +import { showNotification } from '../notifications'; + +let _socket: Socket; +/** + * 创建Socket连接 + * 如果已经有Socket连接则关闭上一个 + * @param token Token + */ +export function createSocket(url: string, token: string): Promise { + if (!_isNil(_socket)) { + _socket.close(); + } + + return new Promise((resolve, reject) => { + _socket = io(url, { + transports: ['websocket'], + auth: { + token, + }, + forceNew: true, + parser: msgpackParser, + }); + _socket.once('connect', () => { + // 连接成功 + resolve(_socket); + }); + _socket.once('error', () => { + reject(); + }); + }); +} + +export function bindSocketEvent(socket: Socket): void { + socket.on('notify:chat.inbox.append', (inboxItem: InboxItem) => { + if (inboxItem.type === 'message') { + showNotification({ + title: inboxItem.message?.converseId ?? '', + body: inboxItem.message?.messageSnippet ?? '', + }); + } + }); +} diff --git a/client/mobile/src/types.ts b/client/mobile/src/types.ts new file mode 100644 index 00000000..d43662e0 --- /dev/null +++ b/client/mobile/src/types.ts @@ -0,0 +1 @@ +export * from '../../../packages/types/index'; diff --git a/client/mobile/tsconfig.json b/client/mobile/tsconfig.json index 45a6c707..ba9f3e3e 100644 --- a/client/mobile/tsconfig.json +++ b/client/mobile/tsconfig.json @@ -1,3 +1,6 @@ { - "extends": "@tsconfig/react-native/tsconfig.json" + "extends": "@tsconfig/react-native/tsconfig.json", + "compilerOptions": { + "importsNotUsedAsValues": "error" + } } diff --git a/client/mobile/yarn.lock b/client/mobile/yarn.lock index 60f68ce5..c7ed1d50 100644 --- a/client/mobile/yarn.lock +++ b/client/mobile/yarn.lock @@ -1742,6 +1742,11 @@ dependencies: "@sinonjs/commons" "^2.0.0" +"@socket.io/component-emitter@~3.1.0": + version "3.1.0" + resolved "https://registry.npmmirror.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" + integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + "@tsconfig/react-native@^2.0.2": version "2.0.3" resolved "https://registry.npmmirror.com/@tsconfig/react-native/-/react-native-2.0.3.tgz#79ad8efc6d3729152da6cb23725b6c364a7349b2" @@ -2856,7 +2861,7 @@ commons-validator-js@^1.0.237: punycode "^1.4.1" wnpm-ci "^8.0.131" -component-emitter@^1.2.1: +component-emitter@^1.2.1, component-emitter@~1.3.0: version "1.3.0" resolved "https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== @@ -3115,7 +3120,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -3323,6 +3328,22 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" +engine.io-client@~6.4.0: + version "6.4.0" + resolved "https://registry.npmmirror.com/engine.io-client/-/engine.io-client-6.4.0.tgz#88cd3082609ca86d7d3c12f0e746d12db4f47c91" + integrity sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.0.3" + ws "~8.11.0" + xmlhttprequest-ssl "~2.0.0" + +engine.io-parser@~5.0.3: + version "5.0.6" + resolved "https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-5.0.6.tgz#7811244af173e157295dec9b2718dfe42a64ef45" + integrity sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw== + entities@^2.0.0: version "2.2.0" resolved "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" @@ -6089,6 +6110,11 @@ normalize-url@^3.0.0: resolved "https://registry.npmmirror.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== +notepack.io@~2.2.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/notepack.io/-/notepack.io-2.2.0.tgz#d7ea71d1cb90094f88c6f3c8d84277c2d0cd101c" + integrity sha512-9b5w3t5VSH6ZPosoYnyDONnUTF8o0UkBw7JLA6eBlYJWyGT1Q3vQa8Hmuj1/X6RYvHjjygBDgw6fJhe0JEojfw== + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -7603,6 +7629,32 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +socket.io-client@^4.6.1: + version "4.6.1" + resolved "https://registry.npmmirror.com/socket.io-client/-/socket.io-client-4.6.1.tgz#80d97d5eb0feca448a0fb6d69a7b222d3d547eab" + integrity sha512-5UswCV6hpaRsNg5kkEHVcbBIXEYoVbMQaHJBXJCyEQ+CiFPV1NIOY0XOFWG4XR4GZcB8Kn6AsRs/9cy9TbqVMQ== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.4.0" + socket.io-parser "~4.2.1" + +socket.io-msgpack-parser@^3.0.2: + version "3.0.2" + resolved "https://registry.npmmirror.com/socket.io-msgpack-parser/-/socket.io-msgpack-parser-3.0.2.tgz#34c4d69acd0fa2a0eede98c14e29fa9f6a5b880a" + integrity sha512-1e76bJ1PCKi9H+JiYk+S29PBJvknHjQWM7Mtj0hjF2KxDA6b6rQxv3rTsnwBoz/haZOhlCDIMQvPATbqYeuMxg== + dependencies: + component-emitter "~1.3.0" + notepack.io "~2.2.0" + +socket.io-parser@~4.2.1: + version "4.2.2" + resolved "https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-4.2.2.tgz#1dd384019e25b7a3d374877f492ab34f2ad0d206" + integrity sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" @@ -8430,6 +8482,16 @@ ws@^7, ws@^7.5.1: resolved "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== +ws@~8.11.0: + version "8.11.0" + resolved "https://registry.npmmirror.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" + integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== + +xmlhttprequest-ssl@~2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" + integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== + xtend@~4.0.1: version "4.0.2" resolved "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" diff --git a/client/web/plugins/com.msgbyte.env.rn/src/index.tsx b/client/web/plugins/com.msgbyte.env.rn/src/index.tsx index 4672eafc..2395b6a6 100644 --- a/client/web/plugins/com.msgbyte.env.rn/src/index.tsx +++ b/client/web/plugins/com.msgbyte.env.rn/src/index.tsx @@ -4,6 +4,7 @@ import { getCachedUserInfo, getCachedBaseGroupInfo, getMessageTextDecorators, + getServiceUrl, } from '@capital/common'; import { Translate } from './translate'; @@ -46,6 +47,26 @@ function forwardSharedEvent( } forwardSharedEvent('loadColorScheme'); +forwardSharedEvent('loginSuccess', async (payload) => { + let token = window.localStorage.getItem('jsonwebtoken'); + try { + token = JSON.parse(token).rawData; + } catch (e) {} + + if (typeof token !== 'string') { + console.error('Cannot get token:', token); + return; + } + + return { + type: 'bindWebsocket', + payload: { + url: getServiceUrl(), + token, + userInfo: payload, + }, + }; +}); forwardSharedEvent('receiveUnmutedMessage', async (payload) => { const message = payload; const currentUserId = getGlobalState()?.user.info._id; diff --git a/packages/types/index.ts b/packages/types/index.ts new file mode 100644 index 00000000..6da6ce21 --- /dev/null +++ b/packages/types/index.ts @@ -0,0 +1,2 @@ +export * from './model/inbox'; +export * from './model/user'; diff --git a/packages/types/model/inbox.ts b/packages/types/model/inbox.ts new file mode 100644 index 00000000..82c64fef --- /dev/null +++ b/packages/types/model/inbox.ts @@ -0,0 +1,18 @@ +/** + * 收件箱记录项类型 + */ +export interface InboxItem { + _id: string; + userId: string; + readed: boolean; + type: 'message'; // will be more + message?: { + groupId?: string; + converseId: string; + messageId: string; + messageSnippet: string; + }; + payload?: Record; + createdAt: string; + updatedAt: string; +} diff --git a/packages/types/model/user.ts b/packages/types/model/user.ts new file mode 100644 index 00000000..0c755a84 --- /dev/null +++ b/packages/types/model/user.ts @@ -0,0 +1,10 @@ +export interface UserBaseInfo { + _id: string; + email: string; + nickname: string; + discriminator: string; + avatar: string | null; + temporary: boolean; + emailVerified: boolean; + extra?: Record; +} diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 00000000..03a2448b --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,12 @@ +{ + "name": "tailchat-types", + "version": "1.0.0", + "description": "", + "main": "index.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "MIT" +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index df1bc2b6..651a1ff2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,6 @@ packages: - 'apps/*' + - 'packages/*' - 'client/web' - 'client/shared' - 'client/web/plugins/**'