├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── api │ ├── conversation.ts │ ├── friend.ts │ ├── group.ts │ ├── index.ts │ ├── message.ts │ └── user.ts ├── constant │ ├── api.ts │ └── callback.ts ├── index.ts ├── types │ ├── entity.ts │ ├── enum.ts │ ├── params.ts │ └── upload.ts └── utils │ ├── emitter.ts │ ├── textCoder.ts │ ├── upload.ts │ ├── uuid.ts │ └── webSocketManager.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | lib 14 | demo 15 | *.local 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "arrowParens": "avoid", 5 | "printWidth": 80 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚠️ Project No Longer Maintained 2 | This project is no longer maintained and may contain unpatched vulnerabilities or compatibility issues with modern environments. Feel free to fork and modify the project if needed. 3 | If you need to use jssdk in the new version (v3.8.2+), you can introduce [@openim/client-sdk](https://www.npmjs.com/package/@openim/client-sdk) to directly communicate with IM Server without the need to deploy this project. 4 | 5 | # MiniProgram SDK for OpenIM 👨‍💻💬 6 | 7 | Use this SDK to add instant messaging capabilities to your application. By connecting to a self-hosted [OpenIM](https://www.openim.io/) server, you can quickly integrate instant messaging capabilities into your app with just a few lines of code. 8 | 9 | `open-im-sdk` is a pure javascript library. It doesn't store any information inside browser, instead it connects to [oimws](https://github.com/openim-sigs/oimws) the proxy layer. This proxy layer is [OpenIM SDK Core](https://github.com/openimsdk/openim-sdk-core.git)'s websocket proxy(listening on port `10003` by deafult). open-im-sdk and open-im-sdk-wasm's interfaces are completely the same. Without modifying any code, your website can run in mini-app. 10 | 11 | ## Documentation 📚 12 | 13 | Visit [https://docs.openim.io/](https://docs.openim.io/) for detailed documentation and guides. 14 | 15 | For the SDK reference, see [https://docs.openim.io/sdks/quickstart/browser](https://docs.openim.io/sdks/quickstart/browser). 16 | 17 | ## Installation 💻 18 | 19 | ### Adding Dependencies 20 | 21 | ```shell 22 | npm install open-im-sdk --save 23 | ``` 24 | 25 | ## Usage 🚀 26 | 27 | The following examples demonstrate how to use the SDK. TypeScript is used, providing complete type hints. 28 | 29 | ### Importing the SDK 30 | 31 | ```typescript 32 | import { OpenIMSDK } from 'open-im-sdk'; 33 | 34 | const OpenIM = new OpenIMSDK(); 35 | ``` 36 | 37 | ### Logging In and Listening for Connection Status 38 | 39 | > Note: You need to [deploy](https://github.com/openimsdk/open-im-server#rocket-quick-start) OpenIM Server first, the default port of OpenIM Server is 10001, 10002, 10003. 40 | 41 | ```typescript 42 | import { CbEvents } from 'open-im-sdk'; 43 | import type { WsResponse } from 'open-im-sdk'; 44 | 45 | OpenIM.on(CbEvents.OnConnecting, handleConnecting); 46 | OpenIM.on(CbEvents.OnConnectFailed, handleConnectFailed); 47 | OpenIM.on(CbEvents.OnConnectSuccess, handleConnectSuccess); 48 | 49 | OpenIM.login({ 50 | userID: 'IM user ID', 51 | token: 'IM user token', 52 | platformID: 5, 53 | wsAddr: 'ws://your-server-ip:10003', 54 | apiAddr: 'http://your-server-ip:10002', 55 | }); 56 | 57 | function handleConnecting() { 58 | // Connecting... 59 | } 60 | 61 | function handleConnectFailed({ errCode, errMsg }: WsResponse) { 62 | // Connection failed ❌ 63 | console.log(errCode, errMsg); 64 | } 65 | 66 | function handleConnectSuccess() { 67 | // Connection successful ✅ 68 | } 69 | ``` 70 | 71 | To log into the IM server, you need to create an account and obtain a user ID and token. Refer to the [access token documentation](https://docs.openim.io/restapi/userManagement/userRegister) for details. 72 | 73 | ### Receiving and Sending Messages 💬 74 | 75 | OpenIM makes it easy to send and receive messages. By default, there is no restriction on having a friend relationship to send messages (although you can configure other policies on the server). If you know the user ID of the recipient, you can conveniently send a message to them. 76 | 77 | ```typescript 78 | import { CbEvents } from 'open-im-sdk'; 79 | import type { WsResponse, MessageItem } from 'open-im-sdk'; 80 | 81 | // Listenfor new messages 📩 82 | OpenIM.on(CbEvents.OnRecvNewMessages, handleNewMessages); 83 | 84 | const message = (await OpenIM.createTextMessage('hello openim')).data; 85 | 86 | OpenIM.sendMessage({ 87 | recvID: 'recipient user ID', 88 | groupID: '', 89 | message, 90 | }) 91 | .then(() => { 92 | // Message sent successfully ✉️ 93 | }) 94 | .catch(err => { 95 | // Failed to send message ❌ 96 | console.log(err); 97 | }); 98 | 99 | function handleNewMessages({ data }: WsResponse) { 100 | // New message list 📨 101 | console.log(data); 102 | } 103 | ``` 104 | 105 | ## Examples 🌟 106 | 107 | You can find a demo web app that uses the SDK in the [openim-pc-web-demo](https://github.com/openimsdk/open-im-pc-web-demo) repository. 108 | 109 | ## Community :busts_in_silhouette: 110 | 111 | - 📚 [OpenIM Community](https://github.com/OpenIMSDK/community) 112 | - 💕 [OpenIM Interest Group](https://github.com/Openim-sigs) 113 | - 🚀 [Join our Slack community](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 114 | - :eyes: [Join our wechat (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) 115 | 116 | ## Community Meetings :calendar: 117 | 118 | We want anyone to get involved in our community and contributing code, we offer gifts and rewards, and we welcome you to join us every Thursday night. 119 | 120 | Our conference is in the [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, then you can search the Open-IM-Server pipeline to join 121 | 122 | We take notes of each [biweekly meeting](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) in [GitHub discussions](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Our historical meeting notes, as well as replays of the meetings are available at [Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing). 123 | 124 | ## Who are using OpenIM :eyes: 125 | 126 | Check out our [user case studies](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md) page for a list of the project users. Don't hesitate to leave a [📝comment](https://github.com/openimsdk/open-im-server/issues/379) and share your use case. 127 | 128 | ## License :page_facing_up: 129 | 130 | OpenIM is licensed under the Apache 2.0 license. See [LICENSE](https://github.com/openimsdk/open-im-server/tree/main/LICENSE) for the full license text. 131 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-im-sdk", 3 | "version": "v3.8.0-rc.2", 4 | "description": "OpenIM SDK for MiniProgram", 5 | "source": "src/index.ts", 6 | "main": "lib/index.js", 7 | "unpkg": "lib/index.umd.js", 8 | "module": "lib/index.es.js", 9 | "jsdelivr": "lib/index.umd.js", 10 | "types": "lib/index.d.ts", 11 | "exports": { 12 | ".": { 13 | "import": "./lib/index.es.js", 14 | "require": "./lib/index.js", 15 | "types": "./lib/index.d.ts" 16 | } 17 | }, 18 | "scripts": { 19 | "build": "rimraf lib && microbundle --no-sourcemap && tsc-alias", 20 | "test": "jest", 21 | "lint": "eslint . --ext .ts", 22 | "format": "prettier --write .", 23 | "release": "semantic-release" 24 | }, 25 | "files": [ 26 | "lib/**/*", 27 | "src/**/*" 28 | ], 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/openimsdk/open-im-sdk.git" 32 | }, 33 | "keywords": [ 34 | "OpenIM", 35 | "IM", 36 | "chat", 37 | "miniprogram" 38 | ], 39 | "author": "blooming ", 40 | "license": "Apache-2.0", 41 | "bugs": { 42 | "url": "https://github.com/openimsdk/open-im-sdk/issues" 43 | }, 44 | "homepage": "https://github.com/openimsdk/open-im-sdk#readme", 45 | "devDependencies": { 46 | "@types/spark-md5": "^3.0.3", 47 | "@typescript-eslint/parser": "^6.9.1", 48 | "microbundle": "^0.15.1", 49 | "prettier": "^3.0.3", 50 | "semantic-release": "^22.0.6", 51 | "spark-md5": "^3.0.2", 52 | "tsc-alias": "^1.8.8", 53 | "typescript": "^5.2.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/api/conversation.ts: -------------------------------------------------------------------------------- 1 | import { RequestApi } from '@/constant/api'; 2 | import OpenIMSDK from '.'; 3 | import type { 4 | GetOneConversationParams, 5 | PinConversationParams, 6 | SetBurnDurationParams, 7 | SetConversationDraftParams, 8 | SetConversationPrivateParams, 9 | SetConversationRecvOptParams, 10 | SplitConversationParams, 11 | } from '@/types/params'; 12 | import type { ConversationItem, WsResponse } from '@/types/entity'; 13 | 14 | export function setupConversation(openIMSDK: OpenIMSDK) { 15 | return { 16 | getAllConversationList: openIMSDK.createRequestFunctionWithoutParams< 17 | ConversationItem[] 18 | >(RequestApi.GetAllConversationList), 19 | getConversationListSplit: openIMSDK.createRequestFunction< 20 | SplitConversationParams, 21 | ConversationItem[] 22 | >(RequestApi.GetConversationListSplit, data => 23 | JSON.stringify([data.offset, data.count]) 24 | ), 25 | getOneConversation: openIMSDK.createRequestFunction< 26 | GetOneConversationParams, 27 | ConversationItem 28 | >(RequestApi.GetOneConversation, data => 29 | JSON.stringify([data.sessionType, data.sourceID]) 30 | ), 31 | getMultipleConversation: openIMSDK.createRequestFunction< 32 | string, 33 | ConversationItem[] 34 | >(RequestApi.GetMultipleConversation), 35 | getConversationIDBySessionType: openIMSDK.createRequestFunction< 36 | GetOneConversationParams, 37 | ConversationItem 38 | >(RequestApi.GetConversationIDBySessionType, data => 39 | JSON.stringify([data.sourceID, data.sessionType]) 40 | ), 41 | getTotalUnreadMsgCount: 42 | openIMSDK.createRequestFunctionWithoutParams( 43 | RequestApi.GetTotalUnreadMsgCount 44 | ), 45 | markConversationMessageAsRead: openIMSDK.createRequestFunction( 46 | RequestApi.MarkConversationMessageAsRead 47 | ), 48 | setConversationDraft: 49 | openIMSDK.createRequestFunction( 50 | RequestApi.SetConversationDraft, 51 | data => JSON.stringify([data.conversationID, data.draftText]) 52 | ), 53 | pinConversation: openIMSDK.createRequestFunction( 54 | RequestApi.PinConversation, 55 | data => JSON.stringify([data.conversationID, data.isPinned]) 56 | ), 57 | setConversationRecvMessageOpt: 58 | openIMSDK.createRequestFunction( 59 | RequestApi.SetConversationRecvMessageOpt, 60 | data => JSON.stringify([data.conversationID, data.opt]) 61 | ), 62 | setConversationPrivateChat: 63 | openIMSDK.createRequestFunction( 64 | RequestApi.SetConversationPrivateChat, 65 | data => JSON.stringify([data.conversationID, data.isPrivate]) 66 | ), 67 | setConversationBurnDuration: 68 | openIMSDK.createRequestFunction( 69 | RequestApi.SetConversationBurnDuration, 70 | data => JSON.stringify([data.conversationID, data.burnDuration]) 71 | ), 72 | resetConversationGroupAtType: openIMSDK.createRequestFunction( 73 | RequestApi.ResetConversationGroupAtType 74 | ), 75 | hideConversation: openIMSDK.createRequestFunction( 76 | RequestApi.HideConversation 77 | ), 78 | hideAllConversation: openIMSDK.createRequestFunctionWithoutParams( 79 | RequestApi.HideAllConversation 80 | ), 81 | clearConversationAndDeleteAllMsg: openIMSDK.createRequestFunction( 82 | RequestApi.ClearConversationAndDeleteAllMsg 83 | ), 84 | deleteConversationAndDeleteAllMsg: openIMSDK.createRequestFunction( 85 | RequestApi.DeleteConversationAndDeleteAllMsg 86 | ), 87 | }; 88 | } 89 | 90 | export interface ConversationApi { 91 | getAllConversationList: ( 92 | operationID?: string 93 | ) => Promise>; 94 | getConversationListSplit: ( 95 | params: SplitConversationParams, 96 | operationID?: string 97 | ) => Promise>; 98 | getOneConversation: ( 99 | params: GetOneConversationParams, 100 | operationID?: string 101 | ) => Promise>; 102 | getMultipleConversation: ( 103 | params: string, 104 | operationID?: string 105 | ) => Promise>; 106 | getConversationIDBySessionType: ( 107 | params: GetOneConversationParams, 108 | operationID?: string 109 | ) => Promise>; 110 | getTotalUnreadMsgCount: (operationID?: string) => Promise>; 111 | markConversationMessageAsRead: ( 112 | params: string, 113 | operationID?: string 114 | ) => Promise>; 115 | setConversationDraft: ( 116 | params: SetConversationDraftParams, 117 | operationID?: string 118 | ) => Promise>; 119 | pinConversation: ( 120 | params: PinConversationParams, 121 | operationID?: string 122 | ) => Promise>; 123 | setConversationRecvMessageOpt: ( 124 | params: SetConversationRecvOptParams, 125 | operationID?: string 126 | ) => Promise>; 127 | setConversationPrivateChat: ( 128 | params: SetConversationPrivateParams, 129 | operationID?: string 130 | ) => Promise>; 131 | setConversationBurnDuration: ( 132 | params: SetBurnDurationParams, 133 | operationID?: string 134 | ) => Promise>; 135 | resetConversationGroupAtType: ( 136 | params: string, 137 | operationID?: string 138 | ) => Promise>; 139 | hideConversation: ( 140 | params: string, 141 | operationID?: string 142 | ) => Promise>; 143 | hideAllConversation: (operationID?: string) => Promise>; 144 | clearConversationAndDeleteAllMsg: ( 145 | params: string, 146 | operationID?: string 147 | ) => Promise>; 148 | deleteConversationAndDeleteAllMsg: ( 149 | params: string, 150 | operationID?: string 151 | ) => Promise>; 152 | } 153 | -------------------------------------------------------------------------------- /src/api/friend.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | AccessFriendParams, 3 | AddBlackParams, 4 | RemarkFriendParams, 5 | SearchFriendParams, 6 | } from '@/types/params'; 7 | import OpenIMSDK from '.'; 8 | import { RequestApi } from '@/constant/api'; 9 | import type { 10 | BlackUserItem, 11 | FriendApplicationItem, 12 | FriendshipInfo, 13 | WsResponse, 14 | SearchedFriendsInfo, 15 | FullUserItem, 16 | Pagination, 17 | } from '@/types/entity'; 18 | 19 | export function setupFriend(openIMSDK: OpenIMSDK) { 20 | return { 21 | acceptFriendApplication: 22 | openIMSDK.createRequestFunction( 23 | RequestApi.AcceptFriendApplication 24 | ), 25 | addBlack: openIMSDK.createRequestFunction( 26 | RequestApi.AddBlack, 27 | data => JSON.stringify([data.toUserID, data.ex ?? '']) 28 | ), 29 | addFriend: openIMSDK.createRequestFunction(RequestApi.AddFriend), 30 | checkFriend: openIMSDK.createRequestFunction( 31 | RequestApi.CheckFriend 32 | ), 33 | deleteFriend: openIMSDK.createRequestFunction( 34 | RequestApi.DeleteFriend 35 | ), 36 | getBlackList: openIMSDK.createRequestFunctionWithoutParams( 37 | RequestApi.GetBlackList 38 | ), 39 | getFriendApplicationListAsApplicant: 40 | openIMSDK.createRequestFunctionWithoutParams( 41 | RequestApi.GetFriendApplicationListAsApplicant 42 | ), 43 | getFriendApplicationListAsRecipient: 44 | openIMSDK.createRequestFunctionWithoutParams( 45 | RequestApi.GetFriendApplicationListAsRecipient 46 | ), 47 | getFriendList: openIMSDK.createRequestFunctionWithoutParams( 48 | RequestApi.GetFriendList 49 | ), 50 | getFriendListPage: openIMSDK.createRequestFunctionWithoutParams< 51 | FullUserItem[] 52 | >(RequestApi.GetFriendListPage), 53 | getSpecifiedFriendsInfo: openIMSDK.createRequestFunction< 54 | string[], 55 | FullUserItem[] 56 | >(RequestApi.GetSpecifiedFriendsInfo), 57 | refuseFriendApplication: 58 | openIMSDK.createRequestFunction( 59 | RequestApi.RefuseFriendApplication 60 | ), 61 | removeBlack: openIMSDK.createRequestFunction( 62 | RequestApi.RemoveBlack 63 | ), 64 | searchFriends: openIMSDK.createRequestFunction< 65 | SearchFriendParams, 66 | SearchedFriendsInfo[] 67 | >(RequestApi.SearchFriends), 68 | setFriendRemark: openIMSDK.createRequestFunction( 69 | RequestApi.SetFriendRemark 70 | ), 71 | }; 72 | } 73 | 74 | export interface FriendApi { 75 | acceptFriendApplication: ( 76 | params: AccessFriendParams, 77 | operationID?: string 78 | ) => Promise>; 79 | addBlack: ( 80 | params: AddBlackParams, 81 | operationID?: string 82 | ) => Promise>; 83 | addFriend: ( 84 | params: string, 85 | operationID?: string 86 | ) => Promise>; 87 | checkFriend: ( 88 | params: string[], 89 | operationID?: string 90 | ) => Promise>; 91 | deleteFriend: ( 92 | params: string, 93 | operationID?: string 94 | ) => Promise>; 95 | getBlackList: (operationID?: string) => Promise>; 96 | getFriendApplicationListAsApplicant: ( 97 | operationID?: string 98 | ) => Promise>; 99 | getFriendApplicationListAsRecipient: ( 100 | operationID?: string 101 | ) => Promise>; 102 | getFriendList: (operationID?: string) => Promise>; 103 | getFriendListPage: ( 104 | params: Pagination, 105 | operationID?: string 106 | ) => Promise>; 107 | getSpecifiedFriendsInfo: ( 108 | params: string[], 109 | operationID?: string 110 | ) => Promise>; 111 | refuseFriendApplication: ( 112 | params: AccessFriendParams, 113 | operationID?: string 114 | ) => Promise>; 115 | removeBlack: ( 116 | params: string, 117 | operationID?: string 118 | ) => Promise>; 119 | searchFriends: ( 120 | params: SearchFriendParams, 121 | operationID?: string 122 | ) => Promise>; 123 | setFriendRemark: ( 124 | params: RemarkFriendParams, 125 | operationID?: string 126 | ) => Promise>; 127 | } 128 | -------------------------------------------------------------------------------- /src/api/group.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | AccessGroupParams, 3 | ChangeGroupMemberMuteParams, 4 | ChangeGroupMuteParams, 5 | CreateGroupParams, 6 | GetGroupMemberByTimeParams, 7 | GetGroupMemberParams, 8 | JoinGroupParams, 9 | OpreateGroupParams, 10 | SearchGroupMemberParams, 11 | SearchGroupParams, 12 | SetGroupinfoParams, 13 | TransferGroupParams, 14 | UpdateMemberInfoParams, 15 | getGroupMembersInfoParams, 16 | } from '@/types/params'; 17 | import OpenIMSDK from '.'; 18 | import { RequestApi } from '@/constant/api'; 19 | import type { 20 | GroupApplicationItem, 21 | GroupItem, 22 | GroupMemberItem, 23 | Pagination, 24 | WsResponse, 25 | } from '@/types/entity'; 26 | 27 | export function setupGroup(openIMSDK: OpenIMSDK) { 28 | return { 29 | createGroup: openIMSDK.createRequestFunction( 30 | RequestApi.CreateGroup 31 | ), 32 | joinGroup: openIMSDK.createRequestFunction( 33 | RequestApi.JoinGroup, 34 | data => 35 | JSON.stringify([ 36 | data.groupID, 37 | data.reqMsg, 38 | data.joinSource, 39 | data.ex ?? '', 40 | ]) 41 | ), 42 | inviteUserToGroup: openIMSDK.createRequestFunction( 43 | RequestApi.InviteUserToGroup, 44 | data => 45 | JSON.stringify([ 46 | data.groupID, 47 | data.reason, 48 | JSON.stringify(data.userIDList), 49 | ]) 50 | ), 51 | getJoinedGroupList: openIMSDK.createRequestFunctionWithoutParams< 52 | GroupItem[] 53 | >(RequestApi.GetJoinedGroupList), 54 | searchGroups: openIMSDK.createRequestFunction< 55 | SearchGroupParams, 56 | GroupItem[] 57 | >(RequestApi.SearchGroups), 58 | getSpecifiedGroupsInfo: openIMSDK.createRequestFunction< 59 | string[], 60 | GroupItem[] 61 | >(RequestApi.GetSpecifiedGroupsInfo), 62 | setGroupInfo: openIMSDK.createRequestFunction( 63 | RequestApi.SetGroupInfo 64 | ), 65 | getGroupApplicationListAsRecipient: 66 | openIMSDK.createRequestFunctionWithoutParams( 67 | RequestApi.GetGroupApplicationListAsRecipient 68 | ), 69 | getGroupApplicationListAsApplicant: 70 | openIMSDK.createRequestFunctionWithoutParams( 71 | RequestApi.GetGroupApplicationListAsApplicant 72 | ), 73 | acceptGroupApplication: openIMSDK.createRequestFunction( 74 | RequestApi.AcceptGroupApplication, 75 | data => JSON.stringify([data.groupID, data.fromUserID, data.handleMsg]) 76 | ), 77 | refuseGroupApplication: openIMSDK.createRequestFunction( 78 | RequestApi.RefuseGroupApplication, 79 | data => JSON.stringify([data.groupID, data.fromUserID, data.handleMsg]) 80 | ), 81 | getGroupMemberList: openIMSDK.createRequestFunction< 82 | GetGroupMemberParams, 83 | GroupMemberItem[] 84 | >(RequestApi.GetGroupMemberList, data => 85 | JSON.stringify([data.groupID, data.filter, data.offset, data.count]) 86 | ), 87 | getSpecifiedGroupMembersInfo: openIMSDK.createRequestFunction< 88 | getGroupMembersInfoParams, 89 | GroupMemberItem[] 90 | >(RequestApi.GetSpecifiedGroupMembersInfo, data => 91 | JSON.stringify([data.groupID, JSON.stringify(data.userIDList)]) 92 | ), 93 | searchGroupMembers: openIMSDK.createRequestFunction< 94 | SearchGroupMemberParams, 95 | GroupMemberItem[] 96 | >(RequestApi.SearchGroupMembers), 97 | setGroupMemberInfo: openIMSDK.createRequestFunction( 98 | RequestApi.SetGroupMemberInfo 99 | ), 100 | getGroupMemberOwnerAndAdmin: openIMSDK.createRequestFunction< 101 | string, 102 | GroupMemberItem[] 103 | >(RequestApi.GetGroupMemberOwnerAndAdmin), 104 | getGroupMemberListByJoinTimeFilter: openIMSDK.createRequestFunction< 105 | GetGroupMemberByTimeParams, 106 | GroupMemberItem[] 107 | >(RequestApi.GetGroupMemberListByJoinTimeFilter, data => 108 | JSON.stringify([ 109 | data.groupID, 110 | data.offset, 111 | data.count, 112 | data.joinTimeBegin, 113 | data.joinTimeEnd, 114 | JSON.stringify(data.filterUserIDList), 115 | ]) 116 | ), 117 | kickGroupMember: openIMSDK.createRequestFunction( 118 | RequestApi.KickGroupMember, 119 | data => 120 | JSON.stringify([ 121 | data.groupID, 122 | data.reason, 123 | JSON.stringify(data.userIDList), 124 | ]) 125 | ), 126 | changeGroupMemberMute: 127 | openIMSDK.createRequestFunction( 128 | RequestApi.ChangeGroupMemberMute, 129 | data => JSON.stringify([data.groupID, data.userID, data.mutedSeconds]) 130 | ), 131 | changeGroupMute: openIMSDK.createRequestFunction( 132 | RequestApi.ChangeGroupMute, 133 | data => JSON.stringify([data.groupID, data.isMute]) 134 | ), 135 | transferGroupOwner: openIMSDK.createRequestFunction( 136 | RequestApi.TransferGroupOwner, 137 | data => JSON.stringify([data.groupID, data.newOwnerUserID]) 138 | ), 139 | dismissGroup: openIMSDK.createRequestFunction( 140 | RequestApi.DismissGroup 141 | ), 142 | quitGroup: openIMSDK.createRequestFunction(RequestApi.QuitGroup), 143 | }; 144 | } 145 | 146 | export interface GroupApi { 147 | createGroup: ( 148 | params: CreateGroupParams, 149 | operationID?: string 150 | ) => Promise>; 151 | joinGroup: ( 152 | params: JoinGroupParams, 153 | operationID?: string 154 | ) => Promise>; 155 | inviteUserToGroup: ( 156 | params: OpreateGroupParams, 157 | operationID?: string 158 | ) => Promise>; 159 | getJoinedGroupList: ( 160 | operationID?: string 161 | ) => Promise>; 162 | getJoinedGroupListPage: ( 163 | params: Pagination, 164 | operationID?: string 165 | ) => Promise>; 166 | searchGroups: ( 167 | params: SearchGroupParams, 168 | operationID?: string 169 | ) => Promise>; 170 | getSpecifiedGroupsInfo: ( 171 | params: string[], 172 | operationID?: string 173 | ) => Promise>; 174 | setGroupInfo: ( 175 | params: SetGroupinfoParams, 176 | operationID?: string 177 | ) => Promise>; 178 | getGroupApplicationListAsRecipient: ( 179 | operationID?: string 180 | ) => Promise>; 181 | getGroupApplicationListAsApplicant: ( 182 | operationID?: string 183 | ) => Promise>; 184 | acceptGroupApplication: ( 185 | params: AccessGroupParams, 186 | operationID?: string 187 | ) => Promise>; 188 | refuseGroupApplication: ( 189 | params: AccessGroupParams, 190 | operationID?: string 191 | ) => Promise>; 192 | getGroupMemberList: ( 193 | operationID?: string 194 | ) => Promise>; 195 | getSpecifiedGroupMembersInfo: ( 196 | params: getGroupMembersInfoParams, 197 | operationID?: string 198 | ) => Promise>; 199 | searchGroupMembers: ( 200 | params: SearchGroupMemberParams, 201 | operationID?: string 202 | ) => Promise>; 203 | setGroupMemberInfo: ( 204 | params: UpdateMemberInfoParams, 205 | operationID?: string 206 | ) => Promise>; 207 | getGroupMemberOwnerAndAdmin: ( 208 | params: string, 209 | operationID?: string 210 | ) => Promise>; 211 | getGroupMemberListByJoinTimeFilter: ( 212 | params: GetGroupMemberByTimeParams, 213 | operationID?: string 214 | ) => Promise>; 215 | kickGroupMember: ( 216 | params: OpreateGroupParams, 217 | operationID?: string 218 | ) => Promise>; 219 | changeGroupMemberMute: ( 220 | params: ChangeGroupMemberMuteParams, 221 | operationID?: string 222 | ) => Promise>; 223 | changeGroupMute: ( 224 | params: ChangeGroupMuteParams, 225 | operationID?: string 226 | ) => Promise>; 227 | transferGroupOwner: ( 228 | params: TransferGroupParams, 229 | operationID?: string 230 | ) => Promise>; 231 | dismissGroup: ( 232 | params: string, 233 | operationID?: string 234 | ) => Promise>; 235 | quitGroup: ( 236 | params: string, 237 | operationID?: string 238 | ) => Promise>; 239 | } 240 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | MessageItem, 3 | PromiseMap, 4 | WsRequest, 5 | WsResponse, 6 | } from '@/types/entity'; 7 | import type { 8 | FileMsgParams, 9 | ImageMsgParams, 10 | LoginParams, 11 | SoundMsgParams, 12 | UploadFileParams, 13 | VideoMsgParams, 14 | } from '@/types/params'; 15 | import WebSocketManager from '@/utils/webSocketManager'; 16 | import { ErrorCode, RequestApi } from '@/constant/api'; 17 | import { UserApi, setupUser } from './user'; 18 | import { FriendApi, setupFriend } from './friend'; 19 | import { GroupApi, setupGroup } from './group'; 20 | import { MessageApi, setupMessage } from './message'; 21 | import { ConversationApi, setupConversation } from './conversation'; 22 | import Emitter from '@/utils/emitter'; 23 | import { CbEvents } from '@/constant/callback'; 24 | import SparkMD5 from 'spark-md5'; 25 | import { 26 | confirmUpload, 27 | getMimeType, 28 | getUploadPartsize, 29 | getUploadUrl, 30 | } from '@/utils/upload'; 31 | import { LoginStatus } from '@/types/enum'; 32 | import { uuid } from '@/utils/uuid'; 33 | 34 | const forceCloseEvents = [ 35 | RequestApi.Logout, 36 | CbEvents.OnKickedOffline, 37 | CbEvents.OnUserTokenInvalid, 38 | CbEvents.OnUserTokenExpired, 39 | ]; 40 | 41 | function isEventInCallbackEvents(event: string): event is CbEvents { 42 | return Object.values(CbEvents).includes(event as CbEvents); 43 | } 44 | 45 | class OpenIMSDK 46 | extends Emitter 47 | implements UserApi, FriendApi, GroupApi, MessageApi, ConversationApi 48 | { 49 | private userID?: string; 50 | private token?: string; 51 | private apiAddr?: string; 52 | private wsManager?: WebSocketManager; 53 | private requestMap = new Map(); 54 | 55 | constructor() { 56 | super(); 57 | Object.assign(this, setupUser(this)); 58 | Object.assign(this, setupFriend(this)); 59 | Object.assign(this, setupGroup(this)); 60 | Object.assign(this, setupMessage(this)); 61 | Object.assign(this, setupConversation(this)); 62 | } 63 | 64 | private sendRequest = (requestObj: WsRequest): Promise> => { 65 | return new Promise((resolve, reject) => { 66 | if (!this.wsManager) { 67 | reject({ 68 | data: '', 69 | operationID: requestObj.operationID, 70 | errMsg: 'please login first', 71 | errCode: ErrorCode.ResourceLoadNotCompleteError, 72 | event: requestObj.reqFuncName, 73 | }); 74 | return; 75 | } 76 | 77 | this.requestMap.set(requestObj.operationID, { 78 | resolve: resolve as unknown as (value: WsResponse) => void, 79 | reject, 80 | }); 81 | this.wsManager?.sendMessage(requestObj); 82 | }); 83 | }; 84 | 85 | private defaultDataFormatter = (params: T) => { 86 | if (typeof params === 'object') { 87 | params = JSON.stringify(params) as unknown as T; 88 | } 89 | return JSON.stringify([params]); 90 | }; 91 | 92 | createRequestFunction = ( 93 | reqFuncName: RequestApi, 94 | dataFormatter = this.defaultDataFormatter 95 | ) => { 96 | return (params: T, operationID = uuid()) => { 97 | const data = dataFormatter(params as T); 98 | return this.sendRequest({ 99 | data, 100 | operationID, 101 | userID: this.userID!, 102 | reqFuncName: reqFuncName, 103 | }); 104 | }; 105 | }; 106 | 107 | createRequestFunctionWithoutParams = ( 108 | reqFuncName: RequestApi 109 | ) => { 110 | return (operationID = uuid()) => 111 | this.sendRequest({ 112 | data: '[]', 113 | operationID, 114 | userID: this.userID!, 115 | reqFuncName: reqFuncName, 116 | }); 117 | }; 118 | 119 | private handleMessage = (data: WsResponse) => { 120 | if (data.event === RequestApi.InitSDK) { 121 | if (data.errCode !== 0) console.error(data); 122 | return; 123 | } 124 | 125 | try { 126 | data.data = JSON.parse(data.data as string); 127 | } catch (error) {} 128 | 129 | if (forceCloseEvents.includes(data.event)) { 130 | this.wsManager?.close(); 131 | this.wsManager = undefined; 132 | } 133 | 134 | if (isEventInCallbackEvents(data.event)) { 135 | this.emit(data.event, data); 136 | if (forceCloseEvents.includes(data.event)) { 137 | this.requestMap.clear(); 138 | } 139 | return; 140 | } 141 | const promiseHandlers = this.requestMap.get(data.operationID); 142 | if (promiseHandlers) { 143 | const promiseHandler = 144 | data.errCode === 0 ? promiseHandlers.resolve : promiseHandlers.reject; 145 | promiseHandler(data); 146 | this.requestMap.delete(data.operationID); 147 | } 148 | if (forceCloseEvents.includes(data.event)) { 149 | this.requestMap.clear(); 150 | } 151 | }; 152 | 153 | private handleReconnectSuccess = () => { 154 | if (!this.userID) return; 155 | 156 | this.sendRequest({ 157 | data: JSON.stringify([this.userID, this.token]), 158 | operationID: uuid(), 159 | userID: this.userID, 160 | reqFuncName: RequestApi.Login, 161 | }); 162 | }; 163 | 164 | login = async ( 165 | params: LoginParams, 166 | operationID = uuid() 167 | ): Promise => { 168 | if (this.wsManager) { 169 | return Promise.resolve({ 170 | data: '', 171 | operationID, 172 | errMsg: 'login repeat', 173 | errCode: ErrorCode.LoginRepeatError, 174 | event: RequestApi.Login, 175 | }); 176 | } 177 | const internalWsUrl = `${params.wsAddr}?sendID=${params.userID}&token=${params.token}&platformID=${params.platformID}&operationID=${operationID}`; 178 | this.userID = params.userID; 179 | this.token = params.token; 180 | this.apiAddr = params.apiAddr; 181 | this.wsManager = new WebSocketManager( 182 | internalWsUrl, 183 | this.handleMessage, 184 | this.handleReconnectSuccess 185 | ); 186 | try { 187 | await this.wsManager.connect(); 188 | } catch (error) { 189 | return Promise.reject({ 190 | data: '', 191 | operationID, 192 | errMsg: (error as Error).message, 193 | errCode: ErrorCode.ConnectionEstablishmentFailed, 194 | event: RequestApi.Login, 195 | }); 196 | } 197 | return this.sendRequest({ 198 | data: JSON.stringify([params.userID, params.token]), 199 | operationID, 200 | userID: this.userID, 201 | reqFuncName: RequestApi.Login, 202 | }); 203 | }; 204 | 205 | logout = this.createRequestFunctionWithoutParams(RequestApi.Logout); 206 | 207 | getLoginStatus = this.createRequestFunctionWithoutParams( 208 | RequestApi.GetLoginStatus 209 | ); 210 | 211 | getLoginUserID = this.createRequestFunctionWithoutParams( 212 | RequestApi.GetLoginUserID 213 | ); 214 | 215 | // third 216 | private internalUploadFile = async ( 217 | file: File, 218 | operationID: string 219 | ): Promise<{ url?: string; error?: Error }> => { 220 | try { 221 | const fileName = `${this.userID}/${file.name}`; 222 | const contentType = getMimeType(file.name); 223 | const commonOptions = { operationID, token: this.token! }; 224 | const { size: partSize } = await getUploadPartsize( 225 | this.apiAddr!, 226 | file.size, 227 | commonOptions 228 | ); 229 | const chunks = Math.ceil(file.size / partSize); 230 | const chunkGapList: { start: number; end: number }[] = []; 231 | const chunkHashList: string[] = []; 232 | const fileSpark = new SparkMD5.ArrayBuffer(); 233 | let currentChunk = 0; 234 | 235 | while (currentChunk < chunks) { 236 | const start = currentChunk * partSize; 237 | const end = Math.min(start + partSize, file.size); 238 | const chunk = file.slice(start, end); 239 | chunkGapList.push({ start, end }); 240 | 241 | // Use a self-invoking function to capture the currentChunk index 242 | const chunkHash = await new Promise((resolve, reject) => { 243 | const reader = new FileReader(); 244 | reader.readAsArrayBuffer(chunk); 245 | reader.onload = e => { 246 | if (e.target) { 247 | fileSpark.append(e.target.result as ArrayBuffer); 248 | resolve(fileSpark.end()); 249 | } 250 | }; 251 | reader.onerror = err => reject(err); 252 | }); 253 | chunkHashList.push(chunkHash); 254 | currentChunk++; 255 | } 256 | 257 | const totalFileHash = chunkHashList.join(','); 258 | fileSpark.destroy(); 259 | const textSpark = new SparkMD5(); 260 | textSpark.append(totalFileHash); 261 | const { url: finishUrl, upload } = await getUploadUrl( 262 | this.apiAddr!, 263 | { 264 | hash: textSpark.end(), 265 | size: file.size, 266 | partSize, 267 | maxParts: -1, 268 | cause: '', 269 | name: fileName, 270 | contentType, 271 | }, 272 | commonOptions 273 | ); 274 | textSpark.destroy(); 275 | if (finishUrl) { 276 | return { 277 | url: finishUrl, 278 | }; 279 | } 280 | 281 | let uploadParts = upload.sign.parts; 282 | const signQuery = upload.sign.query; 283 | const signHeader = upload.sign.header; 284 | 285 | // Use Promise.all to wait for all PUT operations to complete 286 | await Promise.all( 287 | uploadParts.map(async (part, idx) => { 288 | const url = part.url || upload.sign.url; 289 | const rawUrl = new URL(url); 290 | if (signQuery) { 291 | const params = new URLSearchParams(rawUrl.search); 292 | signQuery.forEach(item => { 293 | params.set(item.key, item.values[0]); 294 | }); 295 | rawUrl.search = params.toString(); 296 | } 297 | if (part.query) { 298 | const params = new URLSearchParams(rawUrl.search); 299 | part.query.forEach(item => { 300 | params.set(item.key, item.values[0]); 301 | }); 302 | rawUrl.search = params.toString(); 303 | } 304 | const putUrl = rawUrl.toString(); 305 | const headers = new Headers(); 306 | if (signHeader) { 307 | signHeader.forEach(item => { 308 | headers.set(item.key, item.values[0]); 309 | }); 310 | } 311 | if (part.header) { 312 | part.header.forEach(item => { 313 | headers.set(item.key, item.values[0]); 314 | }); 315 | } 316 | headers.set( 317 | 'Content-Length', 318 | (chunkGapList[idx].end - chunkGapList[idx].start).toString() 319 | ); 320 | 321 | // Ensure correct content type is set for the chunk 322 | headers.set('Content-Type', contentType); 323 | 324 | const response = await fetch(putUrl, { 325 | method: 'PUT', 326 | headers, 327 | body: file.slice(chunkGapList[idx].start, chunkGapList[idx].end), 328 | }); 329 | 330 | if (!response.ok) { 331 | throw new Error(`Failed to upload chunk ${idx + 1}`); 332 | } 333 | }) 334 | ); 335 | 336 | const { url } = await confirmUpload( 337 | this.apiAddr!, 338 | { 339 | uploadID: upload.uploadID, 340 | parts: chunkHashList, 341 | cause: '', 342 | name: fileName, 343 | contentType, 344 | }, 345 | commonOptions 346 | ); 347 | return { url }; 348 | } catch (error) { 349 | console.error('Upload failed:', error); 350 | return { error: error as Error }; 351 | } 352 | }; 353 | 354 | uploadFile = async ( 355 | { file }: UploadFileParams, 356 | operationID = uuid() 357 | ): Promise> => { 358 | const { url = '', error } = await this.internalUploadFile( 359 | file, 360 | operationID 361 | ); 362 | return { 363 | data: { 364 | url, 365 | }, 366 | operationID, 367 | errMsg: error?.message ?? '', 368 | errCode: error ? ErrorCode.UnknownError : 0, 369 | event: RequestApi.UploadFile, 370 | }; 371 | }; 372 | 373 | // extends message 374 | createImageMessageByFile = async ( 375 | params: ImageMsgParams & { file: File }, 376 | operationID = uuid() 377 | ) => { 378 | const { url, error } = await this.internalUploadFile( 379 | params.file, 380 | operationID 381 | ); 382 | if (error) { 383 | return Promise.reject({ 384 | data: '', 385 | operationID, 386 | errMsg: error.message, 387 | errCode: ErrorCode.UnknownError, 388 | event: RequestApi.CreateImageMessageByFile, 389 | }); 390 | } 391 | params.sourcePicture.url = url!; 392 | params.bigPicture.url = url!; 393 | params.snapshotPicture.url = `${url}?type=image&width=${params.snapshotPicture.width}&height=${params.snapshotPicture.height}`; 394 | const tmpParams = { ...params }; 395 | // @ts-ignore 396 | delete tmpParams.file; 397 | return this.createImageMessageByURL(tmpParams, operationID) as Promise< 398 | WsResponse 399 | >; 400 | }; 401 | 402 | createVideoMessageByFile = async ( 403 | params: VideoMsgParams & { videoFile: File; snapshotFile: File }, 404 | operationID = uuid() 405 | ) => { 406 | try { 407 | const [{ url: snapshotUrl }, { url: videoUrl }] = await Promise.all([ 408 | this.internalUploadFile(params.snapshotFile, operationID), 409 | this.internalUploadFile(params.videoFile, operationID), 410 | ]); 411 | params.videoUrl = videoUrl!; 412 | params.snapshotUrl = `${snapshotUrl}?type=image&width=${params.snapshotWidth}&height=${params.snapshotHeight}`; 413 | const tmpParams = { ...params }; 414 | // @ts-ignore 415 | delete tmpParams.videoFile; 416 | // @ts-ignore 417 | delete tmpParams.snapshotFile; 418 | return this.createVideoMessageByURL(tmpParams, operationID) as Promise< 419 | WsResponse 420 | >; 421 | } catch (error) { 422 | return Promise.reject({ 423 | data: '', 424 | operationID, 425 | errMsg: (error as Error).message, 426 | errCode: ErrorCode.UnknownError, 427 | event: RequestApi.CreateVideoMessageByFile, 428 | }); 429 | } 430 | }; 431 | 432 | createSoundMessageByFile = async ( 433 | params: SoundMsgParams & { file: File }, 434 | operationID = uuid() 435 | ) => { 436 | const { url, error } = await this.internalUploadFile( 437 | params.file, 438 | operationID 439 | ); 440 | if (error) { 441 | return Promise.reject({ 442 | data: '', 443 | operationID, 444 | errMsg: error.message, 445 | errCode: ErrorCode.UnknownError, 446 | event: RequestApi.CreateSoundMessageByFile, 447 | }); 448 | } 449 | params.sourceUrl = url!; 450 | const tmpParams = { ...params }; 451 | // @ts-ignore 452 | delete tmpParams.file; 453 | return this.createSoundMessageByURL(tmpParams, operationID) as Promise< 454 | WsResponse 455 | >; 456 | }; 457 | 458 | createFileMessageByFile = async ( 459 | params: FileMsgParams & { file: File }, 460 | operationID = uuid() 461 | ) => { 462 | const { url, error } = await this.internalUploadFile( 463 | params.file, 464 | operationID 465 | ); 466 | if (error) { 467 | return Promise.reject({ 468 | data: '', 469 | operationID, 470 | errMsg: error.message, 471 | errCode: ErrorCode.UnknownError, 472 | event: RequestApi.CreateFileMessageByFile, 473 | }); 474 | } 475 | params.sourceUrl = url!; 476 | const tmpParams = { ...params }; 477 | // @ts-ignore 478 | delete tmpParams.file; 479 | return this.createFileMessageByURL(tmpParams, operationID) as Promise< 480 | WsResponse 481 | >; 482 | }; 483 | 484 | // UserApi 485 | getSelfUserInfo!: UserApi['getSelfUserInfo']; 486 | setSelfInfo!: UserApi['setSelfInfo']; 487 | getUsersInfoWithCache!: UserApi['getUsersInfoWithCache']; 488 | subscribeUsersStatus!: UserApi['subscribeUsersStatus']; 489 | unsubscribeUsersStatus!: UserApi['unsubscribeUsersStatus']; 490 | getSubscribeUsersStatus!: UserApi['getSubscribeUsersStatus']; 491 | setAppBackgroundStatus!: UserApi['setAppBackgroundStatus']; 492 | networkStatusChanged!: UserApi['networkStatusChanged']; 493 | setGlobalRecvMessageOpt!: UserApi['setGlobalRecvMessageOpt']; 494 | 495 | // FriendApi 496 | acceptFriendApplication!: FriendApi['acceptFriendApplication']; 497 | addBlack!: FriendApi['addBlack']; 498 | addFriend!: FriendApi['addFriend']; 499 | checkFriend!: FriendApi['checkFriend']; 500 | deleteFriend!: FriendApi['deleteFriend']; 501 | getBlackList!: FriendApi['getBlackList']; 502 | getFriendApplicationListAsApplicant!: FriendApi['getFriendApplicationListAsApplicant']; 503 | getFriendApplicationListAsRecipient!: FriendApi['getFriendApplicationListAsRecipient']; 504 | getFriendList!: FriendApi['getFriendList']; 505 | getFriendListPage!: FriendApi['getFriendListPage']; 506 | getSpecifiedFriendsInfo!: FriendApi['getSpecifiedFriendsInfo']; 507 | refuseFriendApplication!: FriendApi['refuseFriendApplication']; 508 | removeBlack!: FriendApi['removeBlack']; 509 | searchFriends!: FriendApi['searchFriends']; 510 | setFriendRemark!: FriendApi['setFriendRemark']; 511 | 512 | // GroupApi 513 | createGroup!: GroupApi['createGroup']; 514 | joinGroup!: GroupApi['joinGroup']; 515 | inviteUserToGroup!: GroupApi['inviteUserToGroup']; 516 | getJoinedGroupList!: GroupApi['getJoinedGroupList']; 517 | getJoinedGroupListPage!: GroupApi['getJoinedGroupListPage']; 518 | searchGroups!: GroupApi['searchGroups']; 519 | getSpecifiedGroupsInfo!: GroupApi['getSpecifiedGroupsInfo']; 520 | setGroupInfo!: GroupApi['setGroupInfo']; 521 | getGroupApplicationListAsRecipient!: GroupApi['getGroupApplicationListAsRecipient']; 522 | getGroupApplicationListAsApplicant!: GroupApi['getGroupApplicationListAsApplicant']; 523 | acceptGroupApplication!: GroupApi['acceptGroupApplication']; 524 | refuseGroupApplication!: GroupApi['refuseGroupApplication']; 525 | getGroupMemberList!: GroupApi['getGroupMemberList']; 526 | getSpecifiedGroupMembersInfo!: GroupApi['getSpecifiedGroupMembersInfo']; 527 | searchGroupMembers!: GroupApi['searchGroupMembers']; 528 | setGroupMemberInfo!: GroupApi['setGroupMemberInfo']; 529 | getGroupMemberOwnerAndAdmin!: GroupApi['getGroupMemberOwnerAndAdmin']; 530 | getGroupMemberListByJoinTimeFilter!: GroupApi['getGroupMemberListByJoinTimeFilter']; 531 | kickGroupMember!: GroupApi['kickGroupMember']; 532 | changeGroupMemberMute!: GroupApi['changeGroupMemberMute']; 533 | changeGroupMute!: GroupApi['changeGroupMute']; 534 | transferGroupOwner!: GroupApi['transferGroupOwner']; 535 | dismissGroup!: GroupApi['dismissGroup']; 536 | quitGroup!: GroupApi['quitGroup']; 537 | 538 | // MessageApi 539 | createTextMessage!: MessageApi['createTextMessage']; 540 | createTextAtMessage!: MessageApi['createTextAtMessage']; 541 | createImageMessageByURL!: MessageApi['createImageMessageByURL']; 542 | createSoundMessageByURL!: MessageApi['createSoundMessageByURL']; 543 | createVideoMessageByURL!: MessageApi['createVideoMessageByURL']; 544 | createFileMessageByURL!: MessageApi['createFileMessageByURL']; 545 | createMergerMessage!: MessageApi['createMergerMessage']; 546 | createForwardMessage!: MessageApi['createForwardMessage']; 547 | createLocationMessage!: MessageApi['createLocationMessage']; 548 | createQuoteMessage!: MessageApi['createQuoteMessage']; 549 | createCardMessage!: MessageApi['createCardMessage']; 550 | createCustomMessage!: MessageApi['createCustomMessage']; 551 | createFaceMessage!: MessageApi['createFaceMessage']; 552 | sendMessage!: MessageApi['sendMessage']; 553 | sendMessageNotOss!: MessageApi['sendMessageNotOss']; 554 | typingStatusUpdate!: MessageApi['typingStatusUpdate']; 555 | revokeMessage!: MessageApi['revokeMessage']; 556 | deleteMessage!: MessageApi['deleteMessage']; 557 | deleteMessageFromLocalStorage!: MessageApi['deleteMessageFromLocalStorage']; 558 | deleteAllMsgFromLocal!: MessageApi['deleteAllMsgFromLocal']; 559 | deleteAllMsgFromLocalAndSvr!: MessageApi['deleteAllMsgFromLocalAndSvr']; 560 | searchLocalMessages!: MessageApi['searchLocalMessages']; 561 | getAdvancedHistoryMessageList!: MessageApi['getAdvancedHistoryMessageList']; 562 | getAdvancedHistoryMessageListReverse!: MessageApi['getAdvancedHistoryMessageListReverse']; 563 | findMessageList!: MessageApi['findMessageList']; 564 | insertGroupMessageToLocalStorage!: MessageApi['insertGroupMessageToLocalStorage']; 565 | insertSingleMessageToLocalStorage!: MessageApi['insertSingleMessageToLocalStorage']; 566 | setMessageLocalEx!: MessageApi['setMessageLocalEx']; 567 | 568 | // ConversationApi 569 | getAllConversationList!: ConversationApi['getAllConversationList']; 570 | getConversationListSplit!: ConversationApi['getConversationListSplit']; 571 | getOneConversation!: ConversationApi['getOneConversation']; 572 | getMultipleConversation!: ConversationApi['getMultipleConversation']; 573 | getConversationIDBySessionType!: ConversationApi['getConversationIDBySessionType']; 574 | getTotalUnreadMsgCount!: ConversationApi['getTotalUnreadMsgCount']; 575 | markConversationMessageAsRead!: ConversationApi['markConversationMessageAsRead']; 576 | setConversationDraft!: ConversationApi['setConversationDraft']; 577 | pinConversation!: ConversationApi['pinConversation']; 578 | setConversationRecvMessageOpt!: ConversationApi['setConversationRecvMessageOpt']; 579 | setConversationPrivateChat!: ConversationApi['setConversationPrivateChat']; 580 | setConversationBurnDuration!: ConversationApi['setConversationBurnDuration']; 581 | resetConversationGroupAtType!: ConversationApi['resetConversationGroupAtType']; 582 | hideConversation!: ConversationApi['hideConversation']; 583 | hideAllConversation!: ConversationApi['hideAllConversation']; 584 | clearConversationAndDeleteAllMsg!: ConversationApi['clearConversationAndDeleteAllMsg']; 585 | deleteConversationAndDeleteAllMsg!: ConversationApi['deleteConversationAndDeleteAllMsg']; 586 | } 587 | 588 | export default OpenIMSDK; 589 | 590 | export type MixinApiService = OpenIMSDK & 591 | UserApi & 592 | FriendApi & 593 | GroupApi & 594 | MessageApi & 595 | ConversationApi; 596 | -------------------------------------------------------------------------------- /src/api/message.ts: -------------------------------------------------------------------------------- 1 | import { RequestApi } from '@/constant/api'; 2 | import OpenIMSDK from '.'; 3 | import type { 4 | AtMsgParams, 5 | CustomMsgParams, 6 | FaceMessageParams, 7 | FileMsgParams, 8 | FindMessageParams, 9 | GetAdvancedHistoryMsgParams, 10 | ImageMsgParams, 11 | InsertGroupMsgParams, 12 | InsertSingleMsgParams, 13 | LocationMsgParams, 14 | MergerMsgParams, 15 | OpreateMessageParams, 16 | QuoteMsgParams, 17 | SearchLocalParams, 18 | SendMsgParams, 19 | SetMessageLocalExParams, 20 | SoundMsgParams, 21 | TypingUpdateParams, 22 | VideoMsgParams, 23 | } from '@/types/params'; 24 | import type { 25 | AdvancedGetMessageResult, 26 | CardElem, 27 | MessageItem, 28 | SearchMessageResult, 29 | WsResponse, 30 | } from '@/types/entity'; 31 | 32 | export function setupMessage(openIMSDK: OpenIMSDK) { 33 | return { 34 | createTextMessage: openIMSDK.createRequestFunction( 35 | RequestApi.CreateTextMessage 36 | ), 37 | createTextAtMessage: openIMSDK.createRequestFunction< 38 | AtMsgParams, 39 | MessageItem 40 | >(RequestApi.CreateTextAtMessage, data => 41 | JSON.stringify([ 42 | data.text, 43 | JSON.stringify(data.atUserIDList), 44 | JSON.stringify(data.atUsersInfo), 45 | JSON.stringify(data.message) ?? '', 46 | ]) 47 | ), 48 | createImageMessageByURL: openIMSDK.createRequestFunction< 49 | ImageMsgParams, 50 | MessageItem 51 | >(RequestApi.CreateImageMessageByURL, data => 52 | JSON.stringify([ 53 | data.sourcePath, 54 | JSON.stringify(data.sourcePicture), 55 | JSON.stringify(data.bigPicture), 56 | JSON.stringify(data.snapshotPicture), 57 | ]) 58 | ), 59 | createSoundMessageByURL: openIMSDK.createRequestFunction< 60 | SoundMsgParams, 61 | MessageItem 62 | >(RequestApi.CreateSoundMessageByURL), 63 | createVideoMessageByURL: openIMSDK.createRequestFunction< 64 | VideoMsgParams, 65 | MessageItem 66 | >(RequestApi.CreateVideoMessageByURL), 67 | createFileMessageByURL: openIMSDK.createRequestFunction< 68 | FileMsgParams, 69 | MessageItem 70 | >(RequestApi.CreateFileMessageByURL), 71 | createMergerMessage: openIMSDK.createRequestFunction< 72 | MergerMsgParams, 73 | MessageItem 74 | >(RequestApi.CreateMergerMessage, data => 75 | JSON.stringify([ 76 | JSON.stringify(data.messageList), 77 | data.title, 78 | JSON.stringify(data.summaryList), 79 | ]) 80 | ), 81 | createForwardMessage: openIMSDK.createRequestFunction< 82 | MessageItem, 83 | MessageItem 84 | >(RequestApi.CreateForwardMessage), 85 | createLocationMessage: openIMSDK.createRequestFunction< 86 | LocationMsgParams, 87 | MessageItem 88 | >(RequestApi.CreateLocationMessage, data => 89 | JSON.stringify([data.description, data.longitude, data.latitude]) 90 | ), 91 | createQuoteMessage: openIMSDK.createRequestFunction< 92 | QuoteMsgParams, 93 | MessageItem 94 | >(RequestApi.CreateQuoteMessage, data => 95 | JSON.stringify([data.text, data.message]) 96 | ), 97 | createCardMessage: openIMSDK.createRequestFunction( 98 | RequestApi.CreateCardMessage 99 | ), 100 | createCustomMessage: openIMSDK.createRequestFunction< 101 | CustomMsgParams, 102 | MessageItem 103 | >(RequestApi.CreateCustomMessage, data => 104 | JSON.stringify([data.data, data.extension, data.description]) 105 | ), 106 | createFaceMessage: openIMSDK.createRequestFunction< 107 | FaceMessageParams, 108 | MessageItem 109 | >(RequestApi.CreateFaceMessage, data => 110 | JSON.stringify([data.index, data.data]) 111 | ), 112 | sendMessage: openIMSDK.createRequestFunction( 113 | RequestApi.SendMessageNotOss, 114 | data => { 115 | const offlinePushInfo = data.offlinePushInfo ?? { 116 | title: 'You has a new message.', 117 | desc: 'message', 118 | ex: '', 119 | iOSPushSound: '+1', 120 | iOSBadgeCount: true, 121 | }; 122 | return JSON.stringify([ 123 | JSON.stringify(data.message), 124 | data.recvID, 125 | data.groupID, 126 | JSON.stringify(offlinePushInfo), 127 | data.isOnlineOnly ?? false, 128 | ]); 129 | } 130 | ), 131 | sendMessageNotOss: openIMSDK.createRequestFunction< 132 | SendMsgParams, 133 | MessageItem 134 | >(RequestApi.SendMessageNotOss, data => { 135 | const offlinePushInfo = data.offlinePushInfo ?? { 136 | title: 'You has a new message.', 137 | desc: 'message', 138 | ex: '', 139 | iOSPushSound: '+1', 140 | iOSBadgeCount: true, 141 | }; 142 | return JSON.stringify([ 143 | JSON.stringify(data.message), 144 | data.recvID, 145 | data.groupID, 146 | JSON.stringify(offlinePushInfo), 147 | data.isOnlineOnly ?? false, 148 | ]); 149 | }), 150 | typingStatusUpdate: openIMSDK.createRequestFunction( 151 | RequestApi.TypingStatusUpdate, 152 | data => JSON.stringify([data.recvID, data.msgTip]) 153 | ), 154 | revokeMessage: openIMSDK.createRequestFunction( 155 | RequestApi.RevokeMessage, 156 | data => JSON.stringify([data.conversationID, data.clientMsgID]) 157 | ), 158 | deleteMessage: openIMSDK.createRequestFunction( 159 | RequestApi.DeleteMessage, 160 | data => JSON.stringify([data.conversationID, data.clientMsgID]) 161 | ), 162 | deleteMessageFromLocalStorage: 163 | openIMSDK.createRequestFunction( 164 | RequestApi.DeleteMessageFromLocalStorage, 165 | data => JSON.stringify([data.conversationID, data.clientMsgID]) 166 | ), 167 | deleteAllMsgFromLocal: openIMSDK.createRequestFunctionWithoutParams( 168 | RequestApi.DeleteAllMsgFromLocal 169 | ), 170 | deleteAllMsgFromLocalAndSvr: openIMSDK.createRequestFunctionWithoutParams( 171 | RequestApi.DeleteAllMsgFromLocalAndSvr 172 | ), 173 | searchLocalMessages: openIMSDK.createRequestFunction< 174 | SearchLocalParams, 175 | SearchMessageResult 176 | >(RequestApi.SearchLocalMessages), 177 | getAdvancedHistoryMessageList: openIMSDK.createRequestFunction< 178 | GetAdvancedHistoryMsgParams, 179 | AdvancedGetMessageResult 180 | >(RequestApi.GetAdvancedHistoryMessageList), 181 | getAdvancedHistoryMessageListReverse: openIMSDK.createRequestFunction< 182 | GetAdvancedHistoryMsgParams, 183 | AdvancedGetMessageResult 184 | >(RequestApi.GetAdvancedHistoryMessageListReverse), 185 | findMessageList: openIMSDK.createRequestFunction< 186 | FindMessageParams[], 187 | MessageItem[] 188 | >(RequestApi.FindMessageList), 189 | insertGroupMessageToLocalStorage: 190 | openIMSDK.createRequestFunction( 191 | RequestApi.InsertGroupMessageToLocalStorage, 192 | data => 193 | JSON.stringify([ 194 | JSON.stringify(data.message), 195 | data.groupID, 196 | data.sendID, 197 | ]) 198 | ), 199 | insertSingleMessageToLocalStorage: 200 | openIMSDK.createRequestFunction( 201 | RequestApi.InsertSingleMessageToLocalStorage, 202 | data => 203 | JSON.stringify([ 204 | JSON.stringify(data.message), 205 | data.recvID, 206 | data.sendID, 207 | ]) 208 | ), 209 | setMessageLocalEx: openIMSDK.createRequestFunction( 210 | RequestApi.SetMessageLocalEx, 211 | data => 212 | JSON.stringify([data.conversationID, data.clientMsgID, data.localEx]) 213 | ), 214 | }; 215 | } 216 | 217 | export interface MessageApi { 218 | createTextMessage: ( 219 | params: string, 220 | operationID?: string 221 | ) => Promise>; 222 | createTextAtMessage: ( 223 | params: AtMsgParams, 224 | operationID?: string 225 | ) => Promise>; 226 | createImageMessageByURL: ( 227 | params: ImageMsgParams, 228 | operationID?: string 229 | ) => Promise>; 230 | createSoundMessageByURL: ( 231 | params: SoundMsgParams, 232 | operationID?: string 233 | ) => Promise>; 234 | createVideoMessageByURL: ( 235 | params: VideoMsgParams, 236 | operationID?: string 237 | ) => Promise>; 238 | createFileMessageByURL: ( 239 | params: FileMsgParams, 240 | operationID?: string 241 | ) => Promise>; 242 | createMergerMessage: ( 243 | params: MergerMsgParams, 244 | operationID?: string 245 | ) => Promise>; 246 | createForwardMessage: ( 247 | params: MessageItem, 248 | operationID?: string 249 | ) => Promise>; 250 | createLocationMessage: ( 251 | params: LocationMsgParams, 252 | operationID?: string 253 | ) => Promise>; 254 | createQuoteMessage: ( 255 | params: QuoteMsgParams, 256 | operationID?: string 257 | ) => Promise>; 258 | createCardMessage: ( 259 | params: CardElem, 260 | operationID?: string 261 | ) => Promise>; 262 | createCustomMessage: ( 263 | params: CustomMsgParams, 264 | operationID?: string 265 | ) => Promise>; 266 | createFaceMessage: ( 267 | params: FaceMessageParams, 268 | operationID?: string 269 | ) => Promise>; 270 | sendMessage: ( 271 | params: SendMsgParams, 272 | operationID?: string 273 | ) => Promise>; 274 | sendMessageNotOss: ( 275 | params: SendMsgParams, 276 | operationID?: string 277 | ) => Promise>; 278 | typingStatusUpdate: ( 279 | params: TypingUpdateParams, 280 | operationID?: string 281 | ) => Promise>; 282 | revokeMessage: ( 283 | params: OpreateMessageParams, 284 | operationID?: string 285 | ) => Promise>; 286 | deleteMessage: ( 287 | params: OpreateMessageParams, 288 | operationID?: string 289 | ) => Promise>; 290 | deleteMessageFromLocalStorage: ( 291 | params: OpreateMessageParams, 292 | operationID?: string 293 | ) => Promise>; 294 | deleteAllMsgFromLocal: (operationID?: string) => Promise>; 295 | deleteAllMsgFromLocalAndSvr: ( 296 | operationID?: string 297 | ) => Promise>; 298 | searchLocalMessages: ( 299 | params: SearchLocalParams, 300 | operationID?: string 301 | ) => Promise>; 302 | getAdvancedHistoryMessageList: ( 303 | params: GetAdvancedHistoryMsgParams, 304 | operationID?: string 305 | ) => Promise>; 306 | getAdvancedHistoryMessageListReverse: ( 307 | params: GetAdvancedHistoryMsgParams, 308 | operationID?: string 309 | ) => Promise>; 310 | findMessageList: ( 311 | params: FindMessageParams[], 312 | operationID?: string 313 | ) => Promise>; 314 | insertGroupMessageToLocalStorage: ( 315 | params: InsertGroupMsgParams, 316 | operationID?: string 317 | ) => Promise>; 318 | insertSingleMessageToLocalStorage: ( 319 | params: InsertSingleMsgParams, 320 | operationID?: string 321 | ) => Promise>; 322 | setMessageLocalEx: ( 323 | params: SetMessageLocalExParams, 324 | operationID?: string 325 | ) => Promise>; 326 | } 327 | -------------------------------------------------------------------------------- /src/api/user.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | GetUserInfoWithCacheParams, 3 | SetSelfInfoParams, 4 | } from '@/types/params'; 5 | import OpenIMSDK from '.'; 6 | import { RequestApi } from '@/constant/api'; 7 | import type { MessageReceiveOptType } from '@/types/enum'; 8 | import type { 9 | FullUserItemWithCache, 10 | SelfUserInfo, 11 | UserOnlineState, 12 | WsResponse, 13 | } from '@/types/entity'; 14 | 15 | export function setupUser(openIMSDK: OpenIMSDK) { 16 | return { 17 | getSelfUserInfo: openIMSDK.createRequestFunctionWithoutParams( 18 | RequestApi.GetSelfUserInfo 19 | ), 20 | setSelfInfo: openIMSDK.createRequestFunction( 21 | RequestApi.SetSelfInfo 22 | ), 23 | getUsersInfoWithCache: openIMSDK.createRequestFunction< 24 | GetUserInfoWithCacheParams, 25 | FullUserItemWithCache[] 26 | >(RequestApi.GetUsersInfoWithCache, data => 27 | JSON.stringify([JSON.stringify(data.userIDList), data.groupID ?? '']) 28 | ), 29 | subscribeUsersStatus: openIMSDK.createRequestFunction< 30 | string[], 31 | UserOnlineState 32 | >(RequestApi.SubscribeUsersStatus), 33 | unsubscribeUsersStatus: openIMSDK.createRequestFunction( 34 | RequestApi.UnsubscribeUsersStatus 35 | ), 36 | getSubscribeUsersStatus: openIMSDK.createRequestFunctionWithoutParams< 37 | UserOnlineState[] 38 | >(RequestApi.GetSubscribeUsersStatus), 39 | setAppBackgroundStatus: openIMSDK.createRequestFunction( 40 | RequestApi.SetAppBackgroundStatus 41 | ), 42 | networkStatusChanged: openIMSDK.createRequestFunctionWithoutParams( 43 | RequestApi.NetworkStatusChanged 44 | ), 45 | setGlobalRecvMessageOpt: 46 | openIMSDK.createRequestFunction( 47 | RequestApi.SetGlobalRecvMessageOpt 48 | ), 49 | }; 50 | } 51 | 52 | export interface UserApi { 53 | getSelfUserInfo: (operationID?: string) => Promise>; 54 | setSelfInfo: ( 55 | params: Partial, 56 | operationID?: string 57 | ) => Promise>; 58 | getUsersInfoWithCache: ( 59 | params: GetUserInfoWithCacheParams, 60 | operationID?: string 61 | ) => Promise>; 62 | subscribeUsersStatus: ( 63 | params: string[], 64 | operationID?: string 65 | ) => Promise>; 66 | unsubscribeUsersStatus: ( 67 | params: string[], 68 | operationID?: string 69 | ) => Promise>; 70 | getSubscribeUsersStatus: ( 71 | operationID?: string 72 | ) => Promise>; 73 | setAppBackgroundStatus: ( 74 | params: boolean, 75 | operationID?: string 76 | ) => Promise>; 77 | networkStatusChanged: (operationID?: string) => Promise>; 78 | setGlobalRecvMessageOpt: ( 79 | params: MessageReceiveOptType, 80 | operationID?: string 81 | ) => Promise>; 82 | } 83 | -------------------------------------------------------------------------------- /src/constant/api.ts: -------------------------------------------------------------------------------- 1 | export enum ErrorCode { 2 | ConnectionEstablishmentFailed = 10000, 3 | ResourceLoadNotCompleteError = 10004, 4 | UnknownError = 10005, 5 | LoginRepeatError = 10102, 6 | } 7 | 8 | export enum RequestApi { 9 | InitSDK = 'InitSDK', 10 | // auth 11 | Login = 'Login', 12 | Logout = 'Logout', 13 | GetLoginStatus = 'GetLoginStatus', 14 | GetLoginUserID = 'GetLoginUserID', 15 | 16 | // user 17 | GetSelfUserInfo = 'GetSelfUserInfo', 18 | SetSelfInfo = 'SetSelfInfo', 19 | GetUsersInfoWithCache = 'GetUsersInfoWithCache', 20 | SubscribeUsersStatus = 'SubscribeUsersStatus', 21 | UnsubscribeUsersStatus = 'UnsubscribeUsersStatus', 22 | GetSubscribeUsersStatus = 'GetSubscribeUsersStatus', 23 | SetAppBackgroundStatus = 'SetAppBackgroundStatus', 24 | NetworkStatusChanged = 'NetworkStatusChanged', 25 | SetGlobalRecvMessageOpt = 'SetGlobalRecvMessageOpt', 26 | 27 | // friend 28 | AcceptFriendApplication = 'AcceptFriendApplication', 29 | AddBlack = 'AddBlack', 30 | AddFriend = 'AddFriend', 31 | CheckFriend = 'CheckFriend', 32 | DeleteFriend = 'DeleteFriend', 33 | GetBlackList = 'GetBlackList', 34 | GetFriendApplicationListAsApplicant = 'GetFriendApplicationListAsApplicant', 35 | GetFriendApplicationListAsRecipient = 'GetFriendApplicationListAsRecipient', 36 | GetFriendList = 'GetFriendList', 37 | GetFriendListPage = 'GetFriendListPage', 38 | GetSpecifiedFriendsInfo = 'GetSpecifiedFriendsInfo', 39 | RefuseFriendApplication = 'RefuseFriendApplication', 40 | RemoveBlack = 'RemoveBlack', 41 | SearchFriends = 'SearchFriends', 42 | SetFriendRemark = 'SetFriendRemark', 43 | 44 | // group 45 | CreateGroup = 'CreateGroup', 46 | JoinGroup = 'JoinGroup', 47 | InviteUserToGroup = 'InviteUserToGroup', 48 | GetJoinedGroupList = 'GetJoinedGroupList', 49 | GetJoinedGroupListPage = 'GetJoinedGroupListPage', 50 | SearchGroups = 'SearchGroups', 51 | GetSpecifiedGroupsInfo = 'GetSpecifiedGroupsInfo', 52 | SetGroupInfo = 'SetGroupInfo', 53 | GetGroupApplicationListAsRecipient = 'GetGroupApplicationListAsRecipient', 54 | GetGroupApplicationListAsApplicant = 'GetGroupApplicationListAsApplicant', 55 | AcceptGroupApplication = 'AcceptGroupApplication', 56 | RefuseGroupApplication = 'RefuseGroupApplication', 57 | GetGroupMemberList = 'GetGroupMemberList', 58 | GetSpecifiedGroupMembersInfo = 'GetSpecifiedGroupMembersInfo', 59 | SearchGroupMembers = 'SearchGroupMembers', 60 | SetGroupMemberInfo = 'SetGroupMemberInfo', 61 | GetGroupMemberOwnerAndAdmin = 'GetGroupMemberOwnerAndAdmin', 62 | GetGroupMemberListByJoinTimeFilter = 'GetGroupMemberListByJoinTimeFilter', 63 | KickGroupMember = 'KickGroupMember', 64 | ChangeGroupMemberMute = 'ChangeGroupMemberMute', 65 | ChangeGroupMute = 'ChangeGroupMute', 66 | TransferGroupOwner = 'TransferGroupOwner', 67 | DismissGroup = 'DismissGroup', 68 | QuitGroup = 'QuitGroup', 69 | 70 | // conversation 71 | GetAllConversationList = 'GetAllConversationList', 72 | GetConversationListSplit = 'GetConversationListSplit', 73 | GetOneConversation = 'GetOneConversation', 74 | GetMultipleConversation = 'GetMultipleConversation', 75 | GetConversationIDBySessionType = 'GetConversationIDBySessionType', 76 | GetTotalUnreadMsgCount = 'GetTotalUnreadMsgCount', 77 | MarkConversationMessageAsRead = 'MarkConversationMessageAsRead', 78 | SetConversationDraft = 'SetConversationDraft', 79 | PinConversation = 'PinConversation', 80 | SetConversationRecvMessageOpt = 'SetConversationRecvMessageOpt', 81 | SetConversationPrivateChat = 'SetConversationPrivateChat', 82 | SetConversationBurnDuration = 'SetConversationBurnDuration', 83 | ResetConversationGroupAtType = 'ResetConversationGroupAtType', 84 | HideConversation = 'HideConversation', 85 | HideAllConversation = 'HideAllConversation', 86 | ClearConversationAndDeleteAllMsg = 'ClearConversationAndDeleteAllMsg', 87 | DeleteConversationAndDeleteAllMsg = 'DeleteConversationAndDeleteAllMsg', 88 | 89 | // message 90 | CreateTextMessage = 'CreateTextMessage', 91 | CreateTextAtMessage = 'CreateTextAtMessage', 92 | CreateImageMessageByFile = 'CreateImageMessageByFile', 93 | CreateImageMessageByURL = 'CreateImageMessageByURL', 94 | CreateSoundMessageByFile = 'CreateSoundMessageByFile', 95 | CreateSoundMessageByURL = 'CreateSoundMessageByURL', 96 | CreateVideoMessageByFile = 'CreateVideoMessageByFile', 97 | CreateVideoMessageByURL = 'CreateVideoMessageByURL', 98 | CreateFileMessageByFile = 'CreateFileMessageByFile', 99 | CreateFileMessageByURL = 'CreateFileMessageByURL', 100 | CreateMergerMessage = 'CreateMergerMessage', 101 | CreateForwardMessage = 'CreateForwardMessage', 102 | CreateLocationMessage = 'CreateLocationMessage', 103 | CreateQuoteMessage = 'CreateQuoteMessage', 104 | CreateCardMessage = 'CreateCardMessage', 105 | CreateCustomMessage = 'CreateCustomMessage', 106 | CreateFaceMessage = 'CreateFaceMessage', 107 | SendMessage = 'SendMessage', 108 | SendMessageNotOss = 'SendMessageNotOss', 109 | UploadFile = 'UploadFile', 110 | TypingStatusUpdate = 'TypingStatusUpdate', 111 | RevokeMessage = 'RevokeMessage', 112 | DeleteMessage = 'DeleteMessage', 113 | DeleteMessageFromLocalStorage = 'DeleteMessageFromLocalStorage', 114 | DeleteAllMsgFromLocal = 'DeleteAllMsgFromLocal', 115 | DeleteAllMsgFromLocalAndSvr = 'DeleteAllMsgFromLocalAndSvr', 116 | SearchLocalMessages = 'SearchLocalMessages', 117 | GetAdvancedHistoryMessageList = 'GetAdvancedHistoryMessageList', 118 | GetAdvancedHistoryMessageListReverse = 'GetAdvancedHistoryMessageListReverse', 119 | FindMessageList = 'FindMessageList', 120 | InsertGroupMessageToLocalStorage = 'InsertGroupMessageToLocalStorage', 121 | InsertSingleMessageToLocalStorage = 'InsertSingleMessageToLocalStorage', 122 | SetMessageLocalEx = 'SetMessageLocalEx', 123 | } 124 | -------------------------------------------------------------------------------- /src/constant/callback.ts: -------------------------------------------------------------------------------- 1 | export enum CbEvents { 2 | OnConnectFailed = 'OnConnectFailed', 3 | OnConnectSuccess = 'OnConnectSuccess', 4 | OnConnecting = 'OnConnecting', 5 | OnKickedOffline = 'OnKickedOffline', 6 | OnUserTokenInvalid = 'OnUserTokenInvalid', 7 | OnSelfInfoUpdated = 'OnSelfInfoUpdated', 8 | OnUserTokenExpired = 'OnUserTokenExpired', 9 | OnProgress = 'OnProgress', 10 | OnRecvNewMessage = 'OnRecvNewMessage', 11 | OnRecvNewMessages = 'OnRecvNewMessages', 12 | OnRecvMessageRevoked = 'OnRecvMessageRevoked', 13 | OnNewRecvMessageRevoked = 'OnNewRecvMessageRevoked', 14 | OnRecvC2CReadReceipt = 'OnRecvC2CReadReceipt', 15 | OnRecvGroupReadReceipt = 'OnRecvGroupReadReceipt', 16 | OnConversationChanged = 'OnConversationChanged', 17 | OnNewConversation = 'OnNewConversation', 18 | OnSyncServerFailed = 'OnSyncServerFailed', 19 | OnSyncServerFinish = 'OnSyncServerFinish', 20 | OnSyncServerStart = 'OnSyncServerStart', 21 | OnSyncServerProgress = 'OnSyncServerProgress', 22 | OnTotalUnreadMessageCountChanged = 'OnTotalUnreadMessageCountChanged', 23 | OnBlackAdded = 'OnBlackAdded', 24 | OnBlackDeleted = 'OnBlackDeleted', 25 | OnFriendApplicationAccepted = 'OnFriendApplicationAccepted', 26 | OnFriendApplicationAdded = 'OnFriendApplicationAdded', 27 | OnFriendApplicationDeleted = 'OnFriendApplicationDeleted', 28 | OnFriendApplicationRejected = 'OnFriendApplicationRejected', 29 | OnFriendInfoChanged = 'OnFriendInfoChanged', 30 | OnFriendAdded = 'OnFriendAdded', 31 | OnFriendDeleted = 'OnFriendDeleted', 32 | OnJoinedGroupAdded = 'OnJoinedGroupAdded', 33 | OnJoinedGroupDeleted = 'OnJoinedGroupDeleted', 34 | OnGroupDismissed = 'OnGroupDismissed', 35 | OnGroupMemberAdded = 'OnGroupMemberAdded', 36 | OnGroupMemberDeleted = 'OnGroupMemberDeleted', 37 | OnGroupApplicationAdded = 'OnGroupApplicationAdded', 38 | OnGroupApplicationDeleted = 'OnGroupApplicationDeleted', 39 | OnGroupInfoChanged = 'OnGroupInfoChanged', 40 | OnGroupMemberInfoChanged = 'OnGroupMemberInfoChanged', 41 | OnGroupApplicationAccepted = 'OnGroupApplicationAccepted', 42 | OnGroupApplicationRejected = 'OnGroupApplicationRejected', 43 | 44 | UploadComplete = 'UploadComplete', 45 | OnRecvCustomBusinessMessage = 'OnRecvCustomBusinessMessage', 46 | OnUserStatusChanged = 'OnUserStatusChanged', 47 | 48 | // rtc 49 | OnReceiveNewInvitation = 'OnReceiveNewInvitation', 50 | OnInviteeAccepted = 'OnInviteeAccepted', 51 | OnInviteeRejected = 'OnInviteeRejected', 52 | OnInvitationCancelled = 'OnInvitationCancelled', 53 | OnHangUp = 'OnHangUp', 54 | OnInvitationTimeout = 'OnInvitationTimeout', 55 | OnInviteeAcceptedByOtherDevice = 'OnInviteeAcceptedByOtherDevice', 56 | OnInviteeRejectedByOtherDevice = 'OnInviteeRejectedByOtherDevice', 57 | 58 | // meeting 59 | OnStreamChange = 'OnStreamChange', 60 | OnRoomParticipantConnected = 'OnRoomParticipantConnected', 61 | OnRoomParticipantDisconnected = 'OnRoomParticipantDisconnected', 62 | OnReceiveCustomSignal = 'OnReceiveCustomSignal', 63 | } 64 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import OpenIMSDK from './api'; 2 | import { CbEvents } from '@/constant/callback'; 3 | import { ErrorCode, RequestApi } from '@/constant/api'; 4 | export * from '@/types/enum'; 5 | export * from '@/types/entity'; 6 | export * from '@/types/params'; 7 | 8 | export { OpenIMSDK, CbEvents, ErrorCode, RequestApi }; 9 | -------------------------------------------------------------------------------- /src/types/entity.ts: -------------------------------------------------------------------------------- 1 | import { RequestApi } from '@/constant/api'; 2 | import type { 3 | AllowType, 4 | ApplicationHandleResult, 5 | GroupAtType, 6 | GroupJoinSource, 7 | GroupMemberRole, 8 | GroupStatus, 9 | GroupType, 10 | GroupVerificationType, 11 | MessageReceiveOptType, 12 | MessageStatus, 13 | MessageType, 14 | OnlineState, 15 | Platform, 16 | Relationship, 17 | SessionType, 18 | } from './enum'; 19 | import { CbEvents } from '..'; 20 | 21 | export interface WsRequest { 22 | reqFuncName: RequestApi; 23 | userID: string; 24 | data: string; 25 | operationID: string; 26 | } 27 | 28 | export interface WsResponse { 29 | event: RequestApi | CbEvents; 30 | errCode: number; 31 | errMsg: string; 32 | data: T; 33 | operationID: string; 34 | } 35 | 36 | export interface PromiseMap { 37 | resolve: (response: WsResponse) => void; 38 | reject: (response: WsResponse) => void; 39 | } 40 | 41 | export type Pagination = { 42 | offset: number; 43 | count: number; 44 | } 45 | 46 | export type MessageEntity = { 47 | type: string; 48 | offset: number; 49 | length: number; 50 | url?: string; 51 | info?: string; 52 | }; 53 | export type PicBaseInfo = { 54 | uuid: string; 55 | type: string; 56 | size: number; 57 | width: number; 58 | height: number; 59 | url: string; 60 | }; 61 | export type AtUsersInfoItem = { 62 | atUserID: string; 63 | groupNickname: string; 64 | }; 65 | export type GroupApplicationItem = { 66 | createTime: number; 67 | creatorUserID: string; 68 | ex: string; 69 | groupFaceURL: string; 70 | groupID: string; 71 | groupName: string; 72 | groupType: GroupType; 73 | handleResult: ApplicationHandleResult; 74 | handleUserID: string; 75 | handledMsg: string; 76 | handledTime: number; 77 | introduction: string; 78 | memberCount: number; 79 | nickname: string; 80 | notification: string; 81 | ownerUserID: string; 82 | reqMsg: string; 83 | reqTime: number; 84 | joinSource: GroupJoinSource; 85 | status: GroupStatus; 86 | userFaceURL: string; 87 | userID: string; 88 | }; 89 | export type FriendApplicationItem = { 90 | createTime: number; 91 | ex: string; 92 | fromFaceURL: string; 93 | fromNickname: string; 94 | fromUserID: string; 95 | handleMsg: string; 96 | handleResult: ApplicationHandleResult; 97 | handleTime: number; 98 | handlerUserID: string; 99 | reqMsg: string; 100 | toFaceURL: string; 101 | toNickname: string; 102 | toUserID: string; 103 | }; 104 | export type FullUserItem = { 105 | blackInfo: BlackUserItem | null; 106 | friendInfo: FriendUserItem | null; 107 | publicInfo: PublicUserItem | null; 108 | }; 109 | export type FullUserItemWithCache = { 110 | blackInfo: BlackUserItem | null; 111 | friendInfo: FriendUserItem | null; 112 | publicInfo: PublicUserItem | null; 113 | groupMemberInfo: GroupMemberItem | null; 114 | }; 115 | export type PublicUserItem = { 116 | nickname: string; 117 | userID: string; 118 | faceURL: string; 119 | ex: string; 120 | }; 121 | export type SelfUserInfo = { 122 | createTime: number; 123 | ex: string; 124 | faceURL: string; 125 | nickname: string; 126 | userID: string; 127 | globalRecvMsgOpt: MessageReceiveOptType; 128 | }; 129 | export type PartialUserInfo = { 130 | userID: string; 131 | } & Partial>; 132 | export type FriendUserItem = { 133 | addSource: number; 134 | createTime: number; 135 | ex: string; 136 | faceURL: string; 137 | userID: string; 138 | nickname: string; 139 | operatorUserID: string; 140 | ownerUserID: string; 141 | remark: string; 142 | attachedInfo: string; 143 | }; 144 | export type SearchedFriendsInfo = FriendUserItem & { 145 | relationship: Relationship; 146 | }; 147 | export type FriendshipInfo = { 148 | result: number; 149 | userID: string; 150 | }; 151 | export type BlackUserItem = { 152 | addSource: number; 153 | userID: string; 154 | createTime: number; 155 | ex: string; 156 | faceURL: string; 157 | nickname: string; 158 | operatorUserID: string; 159 | ownerUserID: string; 160 | }; 161 | export type GroupItem = { 162 | groupID: string; 163 | groupName: string; 164 | notification: string; 165 | notificationUserID: string; 166 | notificationUpdateTime: number; 167 | introduction: string; 168 | faceURL: string; 169 | ownerUserID: string; 170 | createTime: number; 171 | memberCount: number; 172 | status: GroupStatus; 173 | creatorUserID: string; 174 | groupType: GroupType; 175 | needVerification: GroupVerificationType; 176 | ex: string; 177 | applyMemberFriend: AllowType; 178 | lookMemberInfo: AllowType; 179 | }; 180 | export type GroupMemberItem = { 181 | groupID: string; 182 | userID: string; 183 | nickname: string; 184 | faceURL: string; 185 | roleLevel: GroupMemberRole; 186 | muteEndTime: number; 187 | joinTime: number; 188 | joinSource: GroupJoinSource; 189 | inviterUserID: string; 190 | operatorUserID: string; 191 | ex: string; 192 | }; 193 | export type ConversationItem = { 194 | conversationID: string; 195 | conversationType: SessionType; 196 | userID: string; 197 | groupID: string; 198 | showName: string; 199 | faceURL: string; 200 | recvMsgOpt: MessageReceiveOptType; 201 | unreadCount: number; 202 | groupAtType: GroupAtType; 203 | latestMsg: string; 204 | latestMsgSendTime: number; 205 | draftText: string; 206 | draftTextTime: number; 207 | burnDuration: number; 208 | msgDestructTime: number; 209 | isPinned: boolean; 210 | isNotInGroup: boolean; 211 | isPrivateChat: boolean; 212 | isMsgDestruct: boolean; 213 | attachedInfo: string; 214 | ex: string; 215 | }; 216 | export type MessageItem = { 217 | clientMsgID: string; 218 | serverMsgID: string; 219 | createTime: number; 220 | sendTime: number; 221 | sessionType: SessionType; 222 | sendID: string; 223 | recvID: string; 224 | msgFrom: number; 225 | contentType: MessageType; 226 | senderPlatformID: Platform; 227 | senderNickname: string; 228 | senderFaceUrl: string; 229 | groupID: string; 230 | content: string; 231 | seq: number; 232 | isRead: boolean; 233 | status: MessageStatus; 234 | isReact: boolean; 235 | isExternalExtensions: boolean; 236 | offlinePush: OfflinePush; 237 | attachedInfo: string; 238 | ex: string; 239 | localEx: string; 240 | textElem: TextElem; 241 | cardElem: CardElem; 242 | pictureElem: PictureElem; 243 | soundElem: SoundElem; 244 | videoElem: VideoElem; 245 | fileElem: FileElem; 246 | mergeElem: MergeElem; 247 | atTextElem: AtTextElem; 248 | faceElem: FaceElem; 249 | locationElem: LocationElem; 250 | customElem: CustomElem; 251 | quoteElem: QuoteElem; 252 | notificationElem: NotificationElem; 253 | advancedTextElem: AdvancedTextElem; 254 | typingElem: TypingElem; 255 | attachedInfoElem: AttachedInfoElem; 256 | }; 257 | export type TextElem = { 258 | content: string; 259 | }; 260 | export type CardElem = { 261 | userID: string; 262 | nickname: string; 263 | faceURL: string; 264 | ex: string; 265 | }; 266 | export type AtTextElem = { 267 | text: string; 268 | atUserList: string[]; 269 | atUsersInfo?: AtUsersInfoItem[]; 270 | quoteMessage?: MessageItem; 271 | isAtSelf?: boolean; 272 | }; 273 | export type NotificationElem = { 274 | detail: string; 275 | }; 276 | export type AdvancedTextElem = { 277 | text: string; 278 | messageEntityList: MessageEntity[]; 279 | }; 280 | export type TypingElem = { 281 | msgTips: string; 282 | }; 283 | export type CustomElem = { 284 | data: string; 285 | description: string; 286 | extension: string; 287 | }; 288 | export type FileElem = { 289 | filePath: string; 290 | uuid: string; 291 | sourceUrl: string; 292 | fileName: string; 293 | fileSize: number; 294 | }; 295 | export type FaceElem = { 296 | index: number; 297 | data: string; 298 | }; 299 | export type LocationElem = { 300 | description: string; 301 | longitude: number; 302 | latitude: number; 303 | }; 304 | export type MergeElem = { 305 | title: string; 306 | abstractList: string[]; 307 | multiMessage: MessageItem[]; 308 | messageEntityList: MessageEntity[]; 309 | }; 310 | export type OfflinePush = { 311 | title: string; 312 | desc: string; 313 | ex: string; 314 | iOSPushSound: string; 315 | iOSBadgeCount: boolean; 316 | }; 317 | export type PictureElem = { 318 | sourcePath: string; 319 | sourcePicture: Picture; 320 | bigPicture: Picture; 321 | snapshotPicture: Picture; 322 | }; 323 | export type AttachedInfoElem = { 324 | groupHasReadInfo: GroupHasReadInfo; 325 | isPrivateChat: boolean; 326 | isEncryption: boolean; 327 | inEncryptStatus: boolean; 328 | burnDuration: number; 329 | hasReadTime: number; 330 | notSenderNotificationPush: boolean; 331 | messageEntityList: MessageEntity[]; 332 | uploadProgress: UploadProgress; 333 | }; 334 | export type UploadProgress = { 335 | total: number; 336 | save: number; 337 | current: number; 338 | }; 339 | export type GroupHasReadInfo = { 340 | hasReadCount: number; 341 | unreadCount: number; 342 | hasReadUserIDList: string[]; 343 | groupMemberCount: number; 344 | }; 345 | export type Picture = { 346 | uuid: string; 347 | type: string; 348 | size: number; 349 | width: number; 350 | height: number; 351 | url: string; 352 | }; 353 | export type QuoteElem = { 354 | text: string; 355 | quoteMessage: MessageItem; 356 | }; 357 | export type SoundElem = { 358 | uuid: string; 359 | soundPath: string; 360 | sourceUrl: string; 361 | dataSize: number; 362 | duration: number; 363 | }; 364 | export type VideoElem = { 365 | videoPath: string; 366 | videoUUID: string; 367 | videoUrl: string; 368 | videoType: string; 369 | videoSize: number; 370 | duration: number; 371 | snapshotPath: string; 372 | snapshotUUID: string; 373 | snapshotSize: number; 374 | snapshotUrl: string; 375 | snapshotWidth: number; 376 | snapshotHeight: number; 377 | }; 378 | export type AdvancedRevokeContent = { 379 | clientMsgID: string; 380 | revokeTime: number; 381 | revokerID: string; 382 | revokerNickname: string; 383 | revokerRole: number; 384 | seq: number; 385 | sessionType: SessionType; 386 | sourceMessageSendID: string; 387 | sourceMessageSendTime: number; 388 | sourceMessageSenderNickname: string; 389 | }; 390 | 391 | export type RevokedInfo = { 392 | revokerID: string; 393 | revokerRole: number; 394 | clientMsgID: string; 395 | revokerNickname: string; 396 | revokeTime: number; 397 | sourceMessageSendTime: number; 398 | sourceMessageSendID: string; 399 | sourceMessageSenderNickname: string; 400 | sessionType: number; 401 | seq: number; 402 | ex: string; 403 | }; 404 | 405 | export type ReceiptInfo = { 406 | userID: string; 407 | groupID: string; 408 | msgIDList: string[]; 409 | readTime: number; 410 | msgFrom: number; 411 | contentType: MessageType; 412 | sessionType: SessionType; 413 | }; 414 | 415 | export type SearchMessageResult = { 416 | totalCount: number; 417 | searchResultItems?: SearchMessageResultItem[]; 418 | findResultItems?: SearchMessageResultItem[]; 419 | }; 420 | 421 | export type SearchMessageResultItem = { 422 | conversationID: string; 423 | messageCount: number; 424 | conversationType: SessionType; 425 | showName: string; 426 | faceURL: string; 427 | messageList: MessageItem[]; 428 | }; 429 | 430 | export type AdvancedGetMessageResult = { 431 | isEnd: boolean; 432 | lastMinSeq: number; 433 | errCode: number; 434 | errMsg: string; 435 | messageList: MessageItem[]; 436 | }; 437 | 438 | export type RtcInvite = { 439 | inviterUserID: string; 440 | inviteeUserIDList: string[]; 441 | customData?: string; 442 | groupID: string; 443 | roomID: string; 444 | timeout: number; 445 | mediaType: string; 446 | sessionType: number; 447 | platformID: number; 448 | initiateTime?: number; 449 | busyLineUserIDList?: string[]; 450 | }; 451 | 452 | export type UserOnlineState = { 453 | platformIDs?: Platform[]; 454 | status: OnlineState; 455 | userID: string; 456 | }; 457 | 458 | export type GroupMessageReceiptInfo = { 459 | conversationID: string; 460 | groupMessageReadInfo: GroupMessageReadInfo[]; 461 | }; 462 | export type GroupMessageReadInfo = { 463 | clientMsgID: string; 464 | hasReadCount: number; 465 | unreadCount: number; 466 | readMembers: GroupMemberItem[]; 467 | }; 468 | -------------------------------------------------------------------------------- /src/types/enum.ts: -------------------------------------------------------------------------------- 1 | export enum MessageReceiveOptType { 2 | Nomal = 0, 3 | NotReceive = 1, 4 | NotNotify = 2, 5 | } 6 | export enum AllowType { 7 | Allowed = 0, 8 | NotAllowed = 1, 9 | } 10 | export enum GroupType { 11 | Group = 2, 12 | WorkingGroup = 2, 13 | } 14 | export enum GroupJoinSource { 15 | Invitation = 2, 16 | Search = 3, 17 | QrCode = 4, 18 | } 19 | export enum GroupMemberRole { 20 | Nomal = 20, 21 | Admin = 60, 22 | Owner = 100, 23 | } 24 | export enum GroupVerificationType { 25 | ApplyNeedInviteNot = 0, 26 | AllNeed = 1, 27 | AllNot = 2, 28 | } 29 | export enum MessageStatus { 30 | Sending = 1, 31 | Succeed = 2, 32 | Failed = 3, 33 | } 34 | export enum Platform { 35 | iOS = 1, 36 | Android = 2, 37 | Windows = 3, 38 | MacOSX = 4, 39 | Web = 5, 40 | Linux = 7, 41 | AndroidPad = 8, 42 | iPad = 9, 43 | } 44 | export enum LogLevel { 45 | Debug = 5, 46 | Info = 4, 47 | Warn = 3, 48 | Error = 2, 49 | Fatal = 1, 50 | Panic = 0, 51 | } 52 | export enum ApplicationHandleResult { 53 | Unprocessed = 0, 54 | Agree = 1, 55 | Reject = -1, 56 | } 57 | export enum MessageType { 58 | TextMessage = 101, 59 | PictureMessage = 102, 60 | VoiceMessage = 103, 61 | VideoMessage = 104, 62 | FileMessage = 105, 63 | AtTextMessage = 106, 64 | MergeMessage = 107, 65 | CardMessage = 108, 66 | LocationMessage = 109, 67 | CustomMessage = 110, 68 | TypingMessage = 113, 69 | QuoteMessage = 114, 70 | FaceMessage = 115, 71 | FriendAdded = 1201, 72 | OANotification = 1400, 73 | 74 | GroupCreated = 1501, 75 | MemberQuit = 1504, 76 | GroupOwnerTransferred = 1507, 77 | MemberKicked = 1508, 78 | MemberInvited = 1509, 79 | MemberEnter = 1510, 80 | GroupDismissed = 1511, 81 | GroupMemberMuted = 1512, 82 | GroupMemberCancelMuted = 1513, 83 | GroupMuted = 1514, 84 | GroupCancelMuted = 1515, 85 | GroupAnnouncementUpdated = 1519, 86 | GroupNameUpdated = 1520, 87 | BurnMessageChange = 1701, 88 | 89 | // notification 90 | RevokeMessage = 2101, 91 | } 92 | export enum SessionType { 93 | Single = 1, 94 | Group = 3, 95 | WorkingGroup = 3, 96 | Notification = 4, 97 | } 98 | export enum GroupStatus { 99 | Nomal = 0, 100 | Baned = 1, 101 | Dismissed = 2, 102 | Muted = 3, 103 | } 104 | export enum GroupAtType { 105 | AtNormal = 0, 106 | AtMe = 1, 107 | AtAll = 2, 108 | AtAllAtMe = 3, 109 | AtGroupNotice = 4, 110 | } 111 | export enum GroupMemberFilter { 112 | All = 0, 113 | Owner = 1, 114 | Admin = 2, 115 | Nomal = 3, 116 | AdminAndNomal = 4, 117 | AdminAndOwner = 5, 118 | } 119 | export enum Relationship { 120 | isBlack = 0, 121 | isFriend = 1, 122 | } 123 | export enum LoginStatus { 124 | Logout = 1, 125 | Logging = 2, 126 | Logged = 3, 127 | } 128 | export enum OnlineState { 129 | Online = 1, 130 | Offline = 0, 131 | } 132 | export enum GroupMessageReaderFilter { 133 | Readed = 0, 134 | UnRead = 1, 135 | } 136 | -------------------------------------------------------------------------------- /src/types/params.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | AtUsersInfoItem, 3 | GroupItem, 4 | MessageItem, 5 | OfflinePush, 6 | PicBaseInfo, 7 | SelfUserInfo, 8 | } from './entity'; 9 | import type { 10 | GroupJoinSource, 11 | GroupMemberFilter, 12 | GroupMemberRole, 13 | MessageReceiveOptType, 14 | MessageType, 15 | } from './enum'; 16 | 17 | export type LoginParams = { 18 | userID: string; 19 | token: string; 20 | wsAddr: string; 21 | apiAddr: string; 22 | platformID: number; 23 | }; 24 | 25 | export type SetSelfInfoParams = Partial; 26 | 27 | export type GetUserInfoWithCacheParams = { 28 | userIDList: string[]; 29 | groupID?: string; 30 | }; 31 | 32 | export type SplitConversationParams = { 33 | offset: number; 34 | count: number; 35 | }; 36 | 37 | export type GetOneConversationParams = { 38 | sourceID: string; 39 | sessionType: number; 40 | }; 41 | 42 | export type SetConversationDraftParams = { 43 | conversationID: string; 44 | draftText: string; 45 | }; 46 | 47 | export type PinConversationParams = { 48 | conversationID: string; 49 | isPinned: boolean; 50 | }; 51 | 52 | export type SetConversationRecvOptParams = { 53 | conversationID: string; 54 | opt: MessageReceiveOptType; 55 | }; 56 | 57 | export type SetConversationPrivateParams = { 58 | conversationID: string; 59 | isPrivate: boolean; 60 | }; 61 | 62 | export type SetBurnDurationParams = { 63 | conversationID: string; 64 | burnDuration: number; 65 | }; 66 | 67 | export type AccessFriendParams = { 68 | toUserID: string; 69 | handleMsg: string; 70 | }; 71 | 72 | export type AddBlackParams = { 73 | toUserID: string; 74 | ex?: string; 75 | }; 76 | 77 | export type SearchFriendParams = { 78 | keywordList: string[]; 79 | isSearchUserID: boolean; 80 | isSearchNickname: boolean; 81 | isSearchRemark: boolean; 82 | }; 83 | 84 | export type RemarkFriendParams = { 85 | toUserID: string; 86 | remark: string; 87 | }; 88 | 89 | export type CreateGroupParams = { 90 | memberUserIDs: string[]; 91 | groupInfo: Partial; 92 | adminUserIDs?: string[]; 93 | ownerUserID?: string; 94 | }; 95 | 96 | export type JoinGroupParams = { 97 | groupID: string; 98 | reqMsg: string; 99 | joinSource: GroupJoinSource; 100 | ex?: string; 101 | }; 102 | 103 | export type OpreateGroupParams = { 104 | groupID: string; 105 | reason: string; 106 | userIDList: string[]; 107 | }; 108 | 109 | export type SearchGroupParams = { 110 | keywordList: string[]; 111 | isSearchGroupID: boolean; 112 | isSearchGroupName: boolean; 113 | }; 114 | 115 | export type SetGroupinfoParams = Partial & { groupID: string }; 116 | 117 | export type AccessGroupParams = { 118 | groupID: string; 119 | fromUserID: string; 120 | handleMsg: string; 121 | }; 122 | 123 | export declare type GetGroupMemberParams = { 124 | groupID: string; 125 | filter: GroupMemberFilter; 126 | offset: number; 127 | count: number; 128 | }; 129 | 130 | export type getGroupMembersInfoParams = { 131 | groupID: string; 132 | userIDList: string[]; 133 | }; 134 | 135 | export type SearchGroupMemberParams = { 136 | groupID: string; 137 | keywordList: string[]; 138 | isSearchUserID: boolean; 139 | isSearchMemberNickname: boolean; 140 | offset: number; 141 | count: number; 142 | }; 143 | 144 | export type UpdateMemberInfoParams = { 145 | groupID: string; 146 | userID: string; 147 | nickname?: string; 148 | faceURL?: string; 149 | roleLevel?: GroupMemberRole; 150 | ex?: string; 151 | }; 152 | 153 | export type GetGroupMemberByTimeParams = { 154 | groupID: string; 155 | filterUserIDList: string[]; 156 | offset: number; 157 | count: number; 158 | joinTimeBegin: number; 159 | joinTimeEnd: number; 160 | }; 161 | 162 | export type ChangeGroupMemberMuteParams = { 163 | groupID: string; 164 | userID: string; 165 | mutedSeconds: number; 166 | }; 167 | 168 | export type ChangeGroupMuteParams = { 169 | groupID: string; 170 | isMute: boolean; 171 | }; 172 | 173 | export type TransferGroupParams = { 174 | groupID: string; 175 | newOwnerUserID: string; 176 | }; 177 | 178 | export type AtMsgParams = { 179 | text: string; 180 | atUserIDList: string[]; 181 | atUsersInfo?: AtUsersInfoItem[]; 182 | message?: MessageItem; 183 | }; 184 | 185 | export type ImageMsgParams = { 186 | sourcePicture: PicBaseInfo; 187 | bigPicture: PicBaseInfo; 188 | snapshotPicture: PicBaseInfo; 189 | sourcePath: string; 190 | }; 191 | 192 | export type SoundMsgParams = { 193 | uuid: string; 194 | soundPath: string; 195 | sourceUrl: string; 196 | dataSize: number; 197 | duration: number; 198 | soundType?: string; 199 | }; 200 | 201 | export type VideoMsgParams = { 202 | videoPath: string; 203 | duration: number; 204 | videoType: string; 205 | snapshotPath: string; 206 | videoUUID: string; 207 | videoUrl: string; 208 | videoSize: number; 209 | snapshotUUID: string; 210 | snapshotSize: number; 211 | snapshotUrl: string; 212 | snapshotWidth: number; 213 | snapshotHeight: number; 214 | snapShotType?: string; 215 | }; 216 | 217 | export type FileMsgParams = { 218 | filePath: string; 219 | fileName: string; 220 | uuid: string; 221 | sourceUrl: string; 222 | fileSize: number; 223 | fileType?: string; 224 | }; 225 | 226 | export type MergerMsgParams = { 227 | messageList: MessageItem[]; 228 | title: string; 229 | summaryList: string[]; 230 | }; 231 | 232 | export type LocationMsgParams = { 233 | description: string; 234 | longitude: number; 235 | latitude: number; 236 | }; 237 | 238 | export type QuoteMsgParams = { 239 | text: string; 240 | message: string; 241 | }; 242 | 243 | export type CustomMsgParams = { 244 | data: string; 245 | extension: string; 246 | description: string; 247 | }; 248 | 249 | export type FaceMessageParams = { 250 | index: number; 251 | data: string; 252 | }; 253 | 254 | export type SendMsgParams = { 255 | recvID: string; 256 | groupID: string; 257 | offlinePushInfo?: OfflinePush; 258 | message: MessageItem; 259 | isOnlineOnly?: boolean; 260 | }; 261 | 262 | export type TypingUpdateParams = { 263 | recvID: string; 264 | msgTip: string; 265 | }; 266 | 267 | export type OpreateMessageParams = { 268 | conversationID: string; 269 | clientMsgID: string; 270 | }; 271 | 272 | export type SearchLocalParams = { 273 | conversationID: string; 274 | keywordList: string[]; 275 | keywordListMatchType?: number; 276 | senderUserIDList?: string[]; 277 | messageTypeList?: MessageType[]; 278 | searchTimePosition?: number; 279 | searchTimePeriod?: number; 280 | pageIndex?: number; 281 | count?: number; 282 | }; 283 | 284 | export type GetAdvancedHistoryMsgParams = { 285 | userID?: string; 286 | groupID?: string; 287 | lastMinSeq: number; 288 | count: number; 289 | startClientMsgID: string; 290 | conversationID: string; 291 | }; 292 | 293 | export type FindMessageParams = { 294 | conversationID: string; 295 | clientMsgIDList: string[]; 296 | }; 297 | 298 | export type InsertGroupMsgParams = { 299 | message: MessageItem; 300 | groupID: string; 301 | sendID: string; 302 | }; 303 | 304 | export type InsertSingleMsgParams = { 305 | message: MessageItem; 306 | recvID: string; 307 | sendID: string; 308 | }; 309 | 310 | export type SetMessageLocalExParams = { 311 | conversationID: string; 312 | clientMsgID: string; 313 | localEx: string; 314 | }; 315 | 316 | export declare type UploadFileParams = { 317 | name?: string; 318 | contentType?: string; 319 | uuid?: string; 320 | file: File; 321 | }; 322 | -------------------------------------------------------------------------------- /src/types/upload.ts: -------------------------------------------------------------------------------- 1 | export interface CommonOptions { 2 | operationID: string; 3 | token: string; 4 | } 5 | 6 | export interface UploadParams { 7 | hash: string; 8 | size: number; 9 | partSize: number; 10 | maxParts: number; 11 | cause: string; 12 | name: string; 13 | contentType: string; 14 | } 15 | 16 | export interface ConfirmData { 17 | uploadID: string; 18 | parts: string[]; 19 | cause: string; 20 | name: string; 21 | contentType: string; 22 | } 23 | 24 | export interface UploadData { 25 | url: string; 26 | upload: Upload; 27 | } 28 | 29 | export interface Upload { 30 | uploadID: string; 31 | partSize: number; 32 | sign: Sign; 33 | } 34 | 35 | export interface Sign { 36 | url: string; 37 | query?: KeyForValueList[]; 38 | header?: KeyForValueList[]; 39 | parts: Part[]; 40 | } 41 | 42 | export interface Part { 43 | partNumber: number; 44 | url: string; 45 | query?: KeyForValueList[]; 46 | header?: KeyForValueList[]; 47 | } 48 | 49 | export interface KeyForValueList { 50 | key: string; 51 | values: string[]; 52 | } 53 | -------------------------------------------------------------------------------- /src/utils/emitter.ts: -------------------------------------------------------------------------------- 1 | import { CbEvents } from '@/constant/callback'; 2 | import type { WsResponse } from '@/types/entity'; 3 | 4 | interface Events { 5 | [key: string]: Cbfn[]; 6 | } 7 | 8 | type Cbfn = (data: WsResponse) => void; 9 | 10 | class Emitter { 11 | private events: Events; 12 | 13 | constructor() { 14 | this.events = {}; 15 | } 16 | 17 | emit(event: CbEvents, data: WsResponse) { 18 | if (this.events[event]) { 19 | this.events[event].forEach(fn => { 20 | return fn(data); 21 | }); 22 | } 23 | 24 | return this; 25 | } 26 | 27 | on(event: CbEvents, fn: Cbfn) { 28 | if (this.events[event]) { 29 | this.events[event].push(fn); 30 | } else { 31 | this.events[event] = [fn]; 32 | } 33 | 34 | return this; 35 | } 36 | 37 | off(event: CbEvents, fn: Cbfn) { 38 | if (event && typeof fn === 'function' && this.events[event]) { 39 | const listeners = this.events[event]; 40 | if (!listeners || listeners.length === 0) { 41 | return; 42 | } 43 | const index = listeners.findIndex(_fn => { 44 | return _fn === fn; 45 | }); 46 | if (index !== -1) { 47 | listeners.splice(index, 1); 48 | } 49 | } 50 | 51 | return this; 52 | } 53 | } 54 | 55 | export default Emitter; 56 | -------------------------------------------------------------------------------- /src/utils/textCoder.ts: -------------------------------------------------------------------------------- 1 | export function utf8Encode(str: string): ArrayBuffer { 2 | if (typeof TextEncoder !== 'undefined') { 3 | return new TextEncoder().encode(str); 4 | } 5 | 6 | const bytes: number[] = []; 7 | let c: number; 8 | 9 | for (let i = 0; i < str.length; i++) { 10 | c = str.charCodeAt(i); 11 | if (c >= 0x010000 && c <= 0x10ffff) { 12 | bytes.push(((c >> 18) & 0x07) | 0xf0); 13 | bytes.push(((c >> 12) & 0x3f) | 0x80); 14 | bytes.push(((c >> 6) & 0x3f) | 0x80); 15 | bytes.push((c & 0x3f) | 0x80); 16 | } else if (c >= 0x000800 && c <= 0x00ffff) { 17 | bytes.push(((c >> 12) & 0x0f) | 0xe0); 18 | bytes.push(((c >> 6) & 0x3f) | 0x80); 19 | bytes.push((c & 0x3f) | 0x80); 20 | } else if (c >= 0x000080 && c <= 0x0007ff) { 21 | bytes.push(((c >> 6) & 0x1f) | 0xc0); 22 | bytes.push((c & 0x3f) | 0x80); 23 | } else { 24 | bytes.push(c & 0xff); 25 | } 26 | } 27 | 28 | return new Uint8Array(bytes).buffer; 29 | } 30 | 31 | export function utf8Decode(buffer: ArrayBuffer): string { 32 | if (typeof TextDecoder !== 'undefined') { 33 | return new TextDecoder().decode(buffer); 34 | } 35 | 36 | const dataView = new DataView(buffer); 37 | const bytes = new Uint8Array(buffer.byteLength); 38 | for (let i = 0; i < bytes.length; i++) { 39 | bytes[i] = dataView.getUint8(i); 40 | } 41 | 42 | let str = ''; 43 | for (let i = 0; i < bytes.length; i++) { 44 | const one = bytes[i].toString(2); 45 | const v = one.match(/^1+?(?=0)/); 46 | if (v && one.length === 8) { 47 | const bytesLength = v[0].length; 48 | let store = bytes[i].toString(2).slice(7 - bytesLength); 49 | for (let st = 1; st < bytesLength; st++) { 50 | store += bytes[st + i].toString(2).slice(2); 51 | } 52 | str += String.fromCharCode(parseInt(store, 2)); 53 | i += bytesLength - 1; 54 | } else { 55 | str += String.fromCharCode(bytes[i]); 56 | } 57 | } 58 | 59 | return str; 60 | } 61 | -------------------------------------------------------------------------------- /src/utils/upload.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CommonOptions, 3 | ConfirmData, 4 | UploadData, 5 | UploadParams, 6 | } from '@/types/upload'; 7 | 8 | // api 9 | const handleResponse = async (res: Response) => { 10 | if (!res.ok) { 11 | throw new Error(res.statusText); 12 | } 13 | const data = await res.json(); 14 | if (data.errCode !== 0) { 15 | throw new Error(data.errMsg); 16 | } 17 | return data.data; 18 | }; 19 | 20 | export const getUploadPartsize = ( 21 | baseUrl: string, 22 | size: number, 23 | commonOptions: CommonOptions 24 | ): Promise<{ size: number }> => 25 | fetch(`${baseUrl}/object/part_size`, { 26 | method: 'POST', 27 | headers: { 28 | ...commonOptions, 29 | }, 30 | body: JSON.stringify({ 31 | size, 32 | }), 33 | }).then(handleResponse); 34 | 35 | export const getUploadUrl = ( 36 | baseUrl: string, 37 | params: UploadParams, 38 | commonOptions: CommonOptions 39 | ): Promise => 40 | fetch(`${baseUrl}/object/initiate_multipart_upload`, { 41 | method: 'POST', 42 | headers: { 43 | ...commonOptions, 44 | }, 45 | body: JSON.stringify(params), 46 | }).then(handleResponse); 47 | 48 | export const confirmUpload = ( 49 | baseUrl: string, 50 | params: ConfirmData, 51 | commonOptions: CommonOptions 52 | ): Promise<{ url: string }> => 53 | fetch(`${baseUrl}/object/complete_multipart_upload`, { 54 | method: 'POST', 55 | headers: { 56 | ...commonOptions, 57 | }, 58 | body: JSON.stringify(params), 59 | }).then(handleResponse); 60 | 61 | // common 62 | const mimeTypesMap: Record = { 63 | txt: 'text/plain', 64 | html: 'text/html', 65 | css: 'text/css', 66 | js: 'text/javascript', 67 | json: 'application/json', 68 | csv: 'text/csv', 69 | jpg: 'image/jpeg', 70 | jpeg: 'image/jpeg', 71 | png: 'image/png', 72 | gif: 'image/gif', 73 | bmp: 'image/bmp', 74 | svg: 'image/svg+xml', 75 | mp3: 'audio/mpeg', 76 | mp4: 'video/mp4', 77 | wav: 'audio/wav', 78 | pdf: 'application/pdf', 79 | doc: 'application/msword', 80 | docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 81 | xls: 'application/vnd.ms-excel', 82 | xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 83 | ppt: 'application/vnd.ms-powerpoint', 84 | pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 85 | xml: 'application/xml', 86 | zip: 'application/zip', 87 | tar: 'application/x-tar', 88 | '7z': 'application/x-7z-compressed', 89 | rar: 'application/vnd.rar', 90 | ogg: 'audio/ogg', 91 | midi: 'audio/midi', 92 | webm: 'audio/webm', 93 | avi: 'video/x-msvideo', 94 | mpeg: 'video/mpeg', 95 | ts: 'video/mp2t', 96 | mov: 'video/quicktime', 97 | wmv: 'video/x-ms-wmv', 98 | flv: 'video/x-flv', 99 | mkv: 'video/x-matroska', 100 | webp: 'image/webp', 101 | heic: 'image/heic', 102 | psd: 'image/vnd.adobe.photoshop', 103 | ai: 'application/postscript', 104 | eps: 'application/postscript', 105 | ttf: 'font/ttf', 106 | otf: 'font/otf', 107 | woff: 'font/woff', 108 | woff2: 'font/woff2', 109 | jsonld: 'application/ld+json', 110 | ics: 'text/calendar', 111 | sh: 'application/x-sh', 112 | php: 'application/x-httpd-php', 113 | jar: 'application/java-archive', 114 | }; 115 | 116 | export const getMimeType = (fileName: string) => { 117 | const extension = fileName.split('.').pop()?.toLowerCase() ?? ''; 118 | return mimeTypesMap[extension] || 'application/octet-stream'; 119 | }; 120 | -------------------------------------------------------------------------------- /src/utils/uuid.ts: -------------------------------------------------------------------------------- 1 | export const uuid = (): string => 2 | (Math.random() * 36).toString(36).slice(2) + new Date().getTime().toString(); 3 | -------------------------------------------------------------------------------- /src/utils/webSocketManager.ts: -------------------------------------------------------------------------------- 1 | import type { WsRequest, WsResponse } from '@/types/entity'; 2 | import { utf8Decode, utf8Encode } from './textCoder'; 3 | import { RequestApi } from '@/constant/api'; 4 | 5 | type AppPlatform = 'unknow' | 'web' | 'uni' | 'wx' |'my'; 6 | 7 | enum WsOpenState { 8 | CONNECTING = 0, 9 | OPEN = 1, 10 | CLOSING = 2, 11 | CLOSED = 3, 12 | } 13 | 14 | class WebSocketManager { 15 | private ws?: WebSocket; 16 | private url: string; 17 | private reconnectInterval: number; // ms 18 | private maxReconnectAttempts: number; 19 | private reconnectAttempts: number; 20 | private shouldReconnect: boolean; 21 | private isProcessingMessage: boolean = false; 22 | private platformNamespace: AppPlatform; 23 | 24 | constructor( 25 | url: string, 26 | private onMessage: (data: WsResponse) => void, 27 | private onReconnectSuccess: () => void, 28 | reconnectInterval = 5000, 29 | maxReconnectAttempts = 10 30 | ) { 31 | this.url = url; 32 | this.reconnectInterval = reconnectInterval; 33 | this.maxReconnectAttempts = maxReconnectAttempts; 34 | this.reconnectAttempts = 0; 35 | this.shouldReconnect = false; 36 | this.platformNamespace = this.checkPlatform(); 37 | } 38 | 39 | private checkPlatform = () => { 40 | if (typeof WebSocket !== 'undefined') { 41 | return 'web'; 42 | } 43 | // my优先于uni 其onBinaryMessage中返回值相对于其他较为特殊 44 | // @ts-ignore 45 | if (typeof my !== 'undefined') { 46 | return 'my'; 47 | } 48 | // @ts-ignore 49 | if (typeof uni !== 'undefined') { 50 | return 'uni'; 51 | } 52 | // @ts-ignore 53 | if (typeof wx !== 'undefined') { 54 | return 'wx'; 55 | } 56 | 57 | return 'unknow'; 58 | }; 59 | 60 | public connect = (): Promise => { 61 | if (this.platformNamespace === 'unknow') { 62 | return Promise.reject(new Error('WebSocket is not supported')); 63 | } 64 | return new Promise((resolve, reject) => { 65 | if (!this.ws || this.ws.readyState === WsOpenState.CLOSED) { 66 | const onWsOpen = () => { 67 | if (this.reconnectAttempts) { 68 | this.onReconnectSuccess(); 69 | } 70 | this.reconnectAttempts = 0; 71 | resolve(); 72 | }; 73 | const onWsError = (event: Event) => reject(event); 74 | if (this.platformNamespace === 'web') { 75 | this.ws = new WebSocket(this.url); 76 | this.ws.onopen = onWsOpen; 77 | this.ws.onerror = onWsError; 78 | } else { 79 | const connectOptions = { 80 | url: this.url, 81 | complete: () => {}, 82 | }; 83 | // @ts-ignore 84 | my && (connectOptions.multiple = true) 85 | if (this.platformNamespace === 'uni') { 86 | // @ts-ignore 87 | this.ws = uni.connectSocket(connectOptions); 88 | } 89 | if (this.platformNamespace === 'wx') { 90 | // @ts-ignore 91 | this.ws = wx.connectSocket(connectOptions); 92 | } 93 | if (this.platformNamespace === 'my') { 94 | // @ts-ignore 95 | this.ws = my.connectSocket(connectOptions); 96 | } 97 | // @ts-ignore 98 | this.ws.onOpen(onWsOpen); 99 | // @ts-ignore 100 | this.ws.onError(onWsError); 101 | } 102 | 103 | this.setupEventListeners(); 104 | } else if (this.ws.readyState === this.ws.OPEN) { 105 | resolve(); 106 | } else { 107 | reject(new Error('WebSocket is in an unknown state')); 108 | } 109 | }); 110 | }; 111 | 112 | private setupEventListeners = () => { 113 | if (!this.ws) return; 114 | 115 | const onWsMessage = (event: MessageEvent) => 116 | this.onBinaryMessage(event.data); 117 | const onWsClose = (event?: CloseEvent) => { 118 | if ( 119 | this.shouldReconnect && 120 | this.reconnectAttempts < this.maxReconnectAttempts 121 | ) { 122 | if (this.isProcessingMessage) { 123 | setTimeout(() => onWsClose(), 100); 124 | return; 125 | } 126 | setTimeout(() => this.connect(), this.reconnectInterval); 127 | this.reconnectAttempts++; 128 | } 129 | }; 130 | 131 | if (this.platformNamespace === 'web') { 132 | this.ws.onmessage = onWsMessage; 133 | this.ws.onclose = onWsClose; 134 | } else { 135 | // @ts-ignore 136 | this.ws.onMessage(onWsMessage); 137 | // @ts-ignore 138 | this.ws.onClose(onWsClose); 139 | } 140 | }; 141 | 142 | private onBinaryMessage = async (message: string|{data:string}) => { 143 | this.isProcessingMessage = true; 144 | // if (this.platformNamespace === 'web' && data instanceof Blob) { 145 | // data = await data.arrayBuffer(); 146 | // } 147 | // const message = utf8Decode(data); 148 | if(typeof message !== 'string' && this.platformNamespace === 'my'){ 149 | message = message.data 150 | } 151 | const json: WsResponse = JSON.parse(message as string) 152 | // const json: WsResponse = JSON.parse(message); 153 | this.onMessage(json); 154 | if (json.event === RequestApi.Login && json.errCode === 0) { 155 | this.shouldReconnect = true; 156 | } 157 | this.isProcessingMessage = false; 158 | }; 159 | 160 | private encodeMessage = (messageObject: WsRequest): ArrayBuffer => { 161 | const messageString = JSON.stringify(messageObject); 162 | return utf8Encode(messageString); 163 | }; 164 | 165 | public sendMessage = (message: WsRequest) => { 166 | if (this.ws?.readyState === WsOpenState.OPEN) { 167 | if (this.platformNamespace === 'web') { 168 | this.ws.send(JSON.stringify(message)); 169 | } else { 170 | this.ws.send({ 171 | //@ts-ignore 172 | data: JSON.stringify(message), 173 | }); 174 | } 175 | } else { 176 | console.error('WebSocket is not open. Message not sent.'); 177 | } 178 | }; 179 | 180 | public close = () => { 181 | this.shouldReconnect = false; 182 | if (this.ws?.readyState === WsOpenState.OPEN) { 183 | this.ws.close(); 184 | } 185 | }; 186 | } 187 | 188 | export default WebSocketManager; 189 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "declaration": true, 6 | "outDir": "lib", 7 | "strict": true, 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "baseUrl": "./", 13 | "paths": { 14 | "@/*": ["src/*"] 15 | } 16 | }, 17 | "include": ["src/**/*.ts"] 18 | } 19 | --------------------------------------------------------------------------------