From 61950198e3bf06555f48e8f51c882a4c3cce5128 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Wed, 7 Sep 2022 11:14:42 +0200 Subject: REFACTOR! pt.1 --- COPYING | 678 +++++++++++ LICENCE | 678 ----------- README.md | 23 - client-web/.gitignore | 1 + client-web/makefile | 4 + client-web/public/app.html | 20 + client-web/public/assets/rnnoise/LICENSE | 21 + .../public/assets/rnnoise/rnnoise-processor.js | 41 + .../public/assets/rnnoise/rnnoise-processor.wasm | Bin 0 -> 124830 bytes .../public/assets/rnnoise/rnnoise-runtime.js | 76 ++ client-web/public/assets/style/logger.css | 42 + client-web/public/assets/style/master.css | 115 ++ client-web/public/start.html | 52 + client-web/source/helper.ts | 43 + client-web/source/index.ts | 55 + client-web/source/local_user.ts | 125 ++ client-web/source/logger.ts | 50 + client-web/source/menu.ts | 26 + client-web/source/remote_user.ts | 74 ++ client-web/source/rnnoise.ts | 38 + client-web/source/room.ts | 72 ++ client-web/source/track_handle.ts | 26 + client-web/source/user.ts | 80 ++ common/packets.d.ts | 21 + nodemon.json | 5 - public/index.html | 20 - public/rnnoise/LICENSE | 21 - public/rnnoise/rnnoise-processor.js | 41 - public/rnnoise/rnnoise-processor.wasm | Bin 124830 -> 0 bytes public/rnnoise/rnnoise-runtime.js | 76 -- public/style/logger.css | 42 - public/style/master.css | 115 -- readme.md | 23 + server-legacy/server/index.ts | 77 ++ server-legacy/server/room.ts | 58 + server/.gitignore | 1 + server/Cargo.lock | 1193 ++++++++++++++++++++ server/Cargo.toml | 13 + server/src/main.rs | 118 ++ source/client/helper.ts | 43 - source/client/index.ts | 70 -- source/client/local_user.ts | 125 -- source/client/logger.ts | 50 - source/client/menu.ts | 26 - source/client/remote_user.ts | 74 -- source/client/rnnoise.ts | 38 - source/client/room.ts | 72 -- source/client/track_handle.ts | 26 - source/client/user.ts | 80 -- source/packets.ts | 21 - source/server/index.ts | 77 -- source/server/room.ts | 58 - 52 files changed, 3143 insertions(+), 1781 deletions(-) create mode 100644 COPYING delete mode 100644 LICENCE delete mode 100644 README.md create mode 100644 client-web/.gitignore create mode 100644 client-web/makefile create mode 100644 client-web/public/app.html create mode 100644 client-web/public/assets/rnnoise/LICENSE create mode 100644 client-web/public/assets/rnnoise/rnnoise-processor.js create mode 100644 client-web/public/assets/rnnoise/rnnoise-processor.wasm create mode 100644 client-web/public/assets/rnnoise/rnnoise-runtime.js create mode 100644 client-web/public/assets/style/logger.css create mode 100644 client-web/public/assets/style/master.css create mode 100644 client-web/public/start.html create mode 100644 client-web/source/helper.ts create mode 100644 client-web/source/index.ts create mode 100644 client-web/source/local_user.ts create mode 100644 client-web/source/logger.ts create mode 100644 client-web/source/menu.ts create mode 100644 client-web/source/remote_user.ts create mode 100644 client-web/source/rnnoise.ts create mode 100644 client-web/source/room.ts create mode 100644 client-web/source/track_handle.ts create mode 100644 client-web/source/user.ts create mode 100644 common/packets.d.ts delete mode 100644 nodemon.json delete mode 100644 public/index.html delete mode 100644 public/rnnoise/LICENSE delete mode 100644 public/rnnoise/rnnoise-processor.js delete mode 100644 public/rnnoise/rnnoise-processor.wasm delete mode 100644 public/rnnoise/rnnoise-runtime.js delete mode 100644 public/style/logger.css delete mode 100644 public/style/master.css create mode 100644 readme.md create mode 100644 server-legacy/server/index.ts create mode 100644 server-legacy/server/room.ts create mode 100644 server/.gitignore create mode 100644 server/Cargo.lock create mode 100644 server/Cargo.toml create mode 100644 server/src/main.rs delete mode 100644 source/client/helper.ts delete mode 100644 source/client/index.ts delete mode 100644 source/client/local_user.ts delete mode 100644 source/client/logger.ts delete mode 100644 source/client/menu.ts delete mode 100644 source/client/remote_user.ts delete mode 100644 source/client/rnnoise.ts delete mode 100644 source/client/room.ts delete mode 100644 source/client/track_handle.ts delete mode 100644 source/client/user.ts delete mode 100644 source/packets.ts delete mode 100644 source/server/index.ts delete mode 100644 source/server/room.ts diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..33724bb --- /dev/null +++ b/COPYING @@ -0,0 +1,678 @@ +keks-meet - a online conference application +Copyright (C) 2022 metamuffin + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, version 3 of the +License only. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + + + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file diff --git a/LICENCE b/LICENCE deleted file mode 100644 index 33724bb..0000000 --- a/LICENCE +++ /dev/null @@ -1,678 +0,0 @@ -keks-meet - a online conference application -Copyright (C) 2022 metamuffin - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, version 3 of the -License only. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - - - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 1fd3900..0000000 --- a/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# keks-meet - -a web conference application - -## Parameters - -For configuration add options in section of URL in a style that is common for query parameters (e.g. `/room/asdfg#username=bob`). -Note that the page wont automatically reload if the section changes. - -Booleans can be either `1`, `true`, `yes` or their opposites. - -| Option name | Type | Default | Description | -| ---------------- | ------- | ------- | ---------------------------------------- | -| `username` | string | "guest" | Sets the username | -| `rnnoise` | boolean | true | Enables noise suppression with rnnoise | -| `mic_enabled` | boolean | false | Adds audio track on startup | -| `camera_enabled` | boolean | false | Adds camera track on startup | -| `screen_enabled` | boolean | false | Adds screen track on startup (wont work) | -| `mic_gain` | number | 1 | Sets the microphone volume | - -## Licence - -See `LICENCE` file. diff --git a/client-web/.gitignore b/client-web/.gitignore new file mode 100644 index 0000000..90fbc35 --- /dev/null +++ b/client-web/.gitignore @@ -0,0 +1 @@ +/public/assets/bundle.js diff --git a/client-web/makefile b/client-web/makefile new file mode 100644 index 0000000..cc36a8e --- /dev/null +++ b/client-web/makefile @@ -0,0 +1,4 @@ + +public/bundle.js: source/* + deno bundle --no-check --unstable source/index.ts > $@ + diff --git a/client-web/public/app.html b/client-web/public/app.html new file mode 100644 index 0000000..d88abbd --- /dev/null +++ b/client-web/public/app.html @@ -0,0 +1,20 @@ + + + + + + + + + + keks-meet + + + +

+ keks-meet needs evil javascript to be enabled. Don't be afraid though, all + the code is free (AGPL-3.0-only)! Look at it on + codeberg +

+ + diff --git a/client-web/public/assets/rnnoise/LICENSE b/client-web/public/assets/rnnoise/LICENSE new file mode 100644 index 0000000..4824556 --- /dev/null +++ b/client-web/public/assets/rnnoise/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 WONG Tin Chi Timothy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/client-web/public/assets/rnnoise/rnnoise-processor.js b/client-web/public/assets/rnnoise/rnnoise-processor.js new file mode 100644 index 0000000..5b594a4 --- /dev/null +++ b/client-web/public/assets/rnnoise/rnnoise-processor.js @@ -0,0 +1,41 @@ +"use strict"; +{ + let b, d; + registerProcessor( + "rnnoise", + class extends AudioWorkletProcessor { + constructor(a) { + super({ + ...a, + numberOfInputs: 1, + numberOfOutputs: 1, + outputChannelCount: [1], + }); + b || + (d = new Float32Array( + (b = new WebAssembly.Instance(a.processorOptions.module) + .exports).memory.buffer + )); + this.state = b.newState(); + this.alive = !0; + this.port.onmessage = ({ data: a }) => { + this.alive && + (a + ? this.port.postMessage({ vadProb: b.getVadProb(this.state) }) + : ((this.alive = !1), b.deleteState(this.state))); + }; + } + process(a, c, e) { + if (!a[0][0]) return 1 + if (this.alive) + return ( + d.set(a[0][0], b.getInput(this.state) / 4), + (a = c[0][0]), + (c = b.pipe(this.state, a.length) / 4) && + a.set(d.subarray(c, c + a.length)), + !0 + ); + } + } + ); +} diff --git a/client-web/public/assets/rnnoise/rnnoise-processor.wasm b/client-web/public/assets/rnnoise/rnnoise-processor.wasm new file mode 100644 index 0000000..86fea35 Binary files /dev/null and b/client-web/public/assets/rnnoise/rnnoise-processor.wasm differ diff --git a/client-web/public/assets/rnnoise/rnnoise-runtime.js b/client-web/public/assets/rnnoise/rnnoise-runtime.js new file mode 100644 index 0000000..f69c568 --- /dev/null +++ b/client-web/public/assets/rnnoise/rnnoise-runtime.js @@ -0,0 +1,76 @@ +"use strict"; +{ + const g = document.currentScript.src.match(/(.*\/)?/)[0], + h = ( + WebAssembly.compileStreaming || + (async (a) => await WebAssembly.compile(await (await a).arrayBuffer())) + )(fetch(g + "rnnoise-processor.wasm")); + let k, c, e; + window.RNNoiseNode = + ((window.AudioWorkletNode || + (window.AudioWorkletNode = window.webkitAudioWorkletNode)) && + class extends AudioWorkletNode { + static async register(a) { + k = await h; + await a.audioWorklet.addModule(g + "rnnoise-processor.js"); + } + constructor(a) { + super(a, "rnnoise", { + channelCountMode: "explicit", + channelCount: 1, + channelInterpretation: "speakers", + numberOfInputs: 1, + numberOfOutputs: 1, + outputChannelCount: [1], + processorOptions: { module: k }, + }); + this.port.onmessage = ({ data: b }) => { + b = Object.assign(new Event("status"), b); + this.dispatchEvent(b); + if (this.onstatus) this.onstatus(b); + }; + } + update(a) { + this.port.postMessage(a); + } + }) || + ((window.ScriptProcessorNode || + (window.ScriptProcessorNode = window.webkitScriptProcessorNode)) && + Object.assign( + function (a) { + const b = a.createScriptProcessor(512, 1, 1), + d = c.newState(); + let f = !0; + b.onaudioprocess = ({ inputBuffer: b, outputBuffer: a }) => { + f && + (e.set(b.getChannelData(0), c.getInput(d) / 4), + (b = a.getChannelData(0)), + (a = c.pipe(d, b.length) / 4) && + b.set(e.subarray(a, a + b.length))); + }; + b.update = (a) => { + if (f) + if (a) { + if ( + ((a = Object.assign(new Event("status"), { + vadProb: c.getVadProb(d), + })), + b.dispatchEvent(a), + b.onstatus) + ) + b.onstatus(a); + } else (f = !1), c.deleteState(d); + }; + return b; + }, + { + register: async () => { + c || + (e = new Float32Array( + (c = (await WebAssembly.instantiate(await h)) + .exports).memory.buffer + )); + }, + } + )); +} diff --git a/client-web/public/assets/style/logger.css b/client-web/public/assets/style/logger.css new file mode 100644 index 0000000..cb76586 --- /dev/null +++ b/client-web/public/assets/style/logger.css @@ -0,0 +1,42 @@ +.logger-container { + position: absolute; + top: 0px; + right: 0px; + transition: width 1s; + + background-color: rgba(0, 0, 0, 0.376); + border-radius: 0.2em; + border: 0px solid transparent; + padding: 0.2em; +} + +.logger-line { + font-size: 1em; + height: 1.2em; + + animation-name: appear, disappear; + animation-timing-function: linear, linear; + animation-delay: 0s, 3s; + animation-duration: 0.3s, 1s; + animation-fill-mode: forwards, forwards; +} + +@keyframes appear { + from { + margin-top: -1.2em; + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes disappear { + from { + opacity: 1; + } + to { + margin-top: -1.2em; + opacity: 0; + } +} diff --git a/client-web/public/assets/style/master.css b/client-web/public/assets/style/master.css new file mode 100644 index 0000000..0b9e205 --- /dev/null +++ b/client-web/public/assets/style/master.css @@ -0,0 +1,115 @@ +@import url("https://s.metamuffin.org/static/font-ubuntu/include.css"); +@import url("./logger.css"); + +* { + font-family: "Ubuntu", sans-serif; + font-weight: 300; + color: white; + margin: 0px; + padding: 0px; +} + +:root { + --bg: #263238; + --bg-dark: #000a12; + --bg-light: #354b58; + --bg-lighter: #4f5b62; + --bg-disabled: #720000; + --bg-enabled: #097200; + --ac: #4a148c; + --ac-light: #7c43bd; + --ac-dark: #12005e; +} + +body { + background-color: var(--bg-dark); +} + +h2 { + font-weight: 700; + margin: 1em; +} + +input[type="button"], +button { + padding: 0.5em; + margin: 0.25em; + background-color: var(--bg-light); + border: 0px solid transparent; + border-radius: 3px; +} +input[type="button"]:hover, +button:hover { + background-color: var(--bg-lighter); +} +input[type="button"].enabled, +button.enabled { + background-color: var(--bg-enabled); +} +input[type="text"] { + background-color: var(--bg-dark); + border: 1px solid var(--ac-light); +} + +.local-controls { + background-color: var(--bg); + padding: 0.5em; + position: absolute; + bottom: 0.5em; + border: 0px solid transparent; + border-radius: 5px; + left: 50%; + transform: translateX(-50%); + z-index: 100; +} + +.room { + width: 100%; + height: 100%; +} + +.user { + background-color: var(--bg); + border: 0px soly transparent; + border-radius: 5px; + padding: 1em; + vertical-align: baseline; + min-width: 10em; + margin: 0.5em; +} + +.user .info .name { + font-weight: 400; +} +.user.local .info .name { + text-decoration: underline; +} + +.media { + max-height: 30vh; + border: 0px solid transparent; + border-radius: 5px; +} + +.start-box { + position: absolute; + top: 50vh; + left: 50vw; + transform: translate(-50%, -50%); +} +.start-box p { + margin-bottom: 0.5em; +} +.start-box input[type="text"] { + margin: 0.5em; + font-size: 32px; +} + +.menu-overlay { + position: absolute; + bottom: 0px; + right: 0px; + display: block; + text-align: right; +} + diff --git a/client-web/public/start.html b/client-web/public/start.html new file mode 100644 index 0000000..0852f8b --- /dev/null +++ b/client-web/public/start.html @@ -0,0 +1,52 @@ + + + + + + + + + + keks-meet + + + +

keks-meet

+

A web conferencing application using webrtc

+

+ keks-meet is free software! It is licenced under the terms of the + third version of the GNU Affero General Public Licence only. +

+

+ To get started, just enter a unique idenfier, click 'Join', then + share the URL with your partner. +

+ + + + diff --git a/client-web/source/helper.ts b/client-web/source/helper.ts new file mode 100644 index 0000000..31e500a --- /dev/null +++ b/client-web/source/helper.ts @@ -0,0 +1,43 @@ +/// + +import { parameters } from "./index.ts" + +export function get_query_params(): { [key: string]: string } { + const q: { [key: string]: string } = {} + for (const kv of window.location.hash.substring(1).split("&")) { + const [key, value] = kv.split("=") + q[decodeURIComponent(key)] = decodeURIComponent(value) + } + return q +} + +export function hex_id(len = 8): string { + if (len > 8) return hex_id() + hex_id(len - 8) + return Math.floor(Math.random() * 16 ** len).toString(16).padStart(len, "0") +} + +export function parameter_bool(name: string, def: boolean): boolean { + const v = parameters[name] + if (!v) return def + if (v == "0" || v == "false" || v == "no") return false + if (v == "1" || v == "true" || v == "yes") return true + alert(`parameter ${name} is invalid`) + return def +} + +export function parameter_number(name: string, def: number): number { + const v = parameters[name] + if (!v) return def + const n = parseFloat(v) + if (Number.isNaN(n)) { + alert(`parameter ${name} is invalid`) + return def + } + return n +} + +export function parameter_string(name: string, def: string): string { + const v = parameters[name] + if (!v) return def + return v +} diff --git a/client-web/source/index.ts b/client-web/source/index.ts new file mode 100644 index 0000000..fbb77d4 --- /dev/null +++ b/client-web/source/index.ts @@ -0,0 +1,55 @@ +/// + +import { get_query_params } from "./helper.ts" +import { log } from "./logger.ts" +import { create_menu } from "./menu.ts"; +import { Room } from "./room.ts" + +export const servers: RTCConfiguration = { + iceServers: [{ urls: ["stun:stun1.l.google.com:19302", "stun:stun2.l.google.com:19302"] }], + iceCandidatePoolSize: 10, +} + +export interface User { + peer: RTCPeerConnection + stream: MediaStream, +} + +export const parameters = get_query_params() + +window.onload = () => main() + +export function main() { + document.body.querySelector("p")?.remove() + log("*", "starting up") + if (window.location.pathname.startsWith("/room/")) { + const room_name = window.location.pathname.substring("/room/".length) + const room = new Room(room_name) + create_menu(room) + document.body.append(room.el) + } else { + create_menu() + document.body.append(create_start_screen()) + } +} + +function create_start_screen() { + const with_text_content = (a: string) => (b: string) => { + const e = document.createElement(a) + e.textContent = b + return e + } + const p = with_text_content("p") + const h2 = with_text_content("h2") + + const el = document.createElement("div") + el.append( + h2("keks-meet"), + p("A web conferencing application using webrtc"), + p("keks-meet is free software! It is licenced under the terms of the third version of the GNU Affero General Public Licence only."), + p("To get started, just enter a unique idenfier, click 'Join', then share the URL with your partner.") + ) + + + return el +} diff --git a/client-web/source/local_user.ts b/client-web/source/local_user.ts new file mode 100644 index 0000000..ca91a19 --- /dev/null +++ b/client-web/source/local_user.ts @@ -0,0 +1,125 @@ +/// + +import { parameter_bool, parameter_number } from "./helper.ts"; +import { log } from "./logger.ts"; +import { RemoteUser } from "./remote_user.ts"; +import { get_rnnoise_node } from "./rnnoise.ts"; +import { Room } from "./room.ts"; +import { TrackHandle } from "./track_handle.ts"; +import { User } from "./user.ts"; + + +export class LocalUser extends User { + mic_gain?: GainNode + default_gain: number = parameter_number("mic_gain", 1) + + constructor(room: Room, name: string) { + super(room, name) + this.el.classList.add("local") + this.local = true + this.create_controls() + this.add_initial_tracks() + log("usermodel", `added local user: ${this.name}`) + } + + async add_initial_tracks() { + if (parameter_bool("mic_enabled", false)) this.publish_track(await this.create_mic_track()) + if (parameter_bool("camera_enabled", false)) this.publish_track(await this.create_camera_track()) + if (parameter_bool("screen_enabled", false)) this.publish_track(await this.create_screen_track()) + } + + publish_track(t: TrackHandle) { + this.room.remote_users.forEach(u => u.peer.addTrack(t.track)) + this.add_track(t) + t.addEventListener("ended", () => { + this.room.remote_users.forEach(u => { + u.peer.getSenders().forEach(s => { + if (s.track == t.track) u.peer.removeTrack(s) + }) + }) + }) + } + + add_initial_to_remote(u: RemoteUser) { + this.tracks.forEach(t => u.peer.addTrack(t.track)) + } + + create_controls() { + const mic_toggle = document.createElement("input") + const camera_toggle = document.createElement("input") + const screen_toggle = document.createElement("input") + mic_toggle.type = camera_toggle.type = screen_toggle.type = "button" + mic_toggle.value = "Microphone" + camera_toggle.value = "Camera" + screen_toggle.value = "Screen" + + const create = async (_e: HTMLElement, tp: Promise) => { + log("media", "awaiting track") + const t = await tp + log("media", "got track") + this.publish_track(t) + } + + mic_toggle.addEventListener("click", () => create(mic_toggle, this.create_mic_track())) + camera_toggle.addEventListener("click", () => create(camera_toggle, this.create_camera_track())) + screen_toggle.addEventListener("click", () => create(screen_toggle, this.create_screen_track())) + + const el = document.createElement("div") + el.classList.add("local-controls") + el.append(mic_toggle, camera_toggle, screen_toggle) + document.body.append(el) + } + + + async create_camera_track() { + log("media", "requesting user media (camera)") + const user_media = await window.navigator.mediaDevices.getUserMedia({ video: true }) + return new TrackHandle(user_media.getVideoTracks()[0], true) + } + async create_screen_track() { + log("media", "requesting user media (screen)") + const user_media = await window.navigator.mediaDevices.getDisplayMedia({ video: true }) + return new TrackHandle(user_media.getVideoTracks()[0], true) + } + async create_mic_track() { + log("media", "requesting user media (audio)") + const use_rnnoise = parameter_bool("rnnoise", true) + const audio_contraints = use_rnnoise ? { + channelCount: { ideal: 1 }, + noiseSuppression: { ideal: false }, + echoCancellation: { ideal: true }, + autoGainControl: { ideal: false }, + } : true; + + const user_media = await window.navigator.mediaDevices.getUserMedia({ audio: audio_contraints }) + const context = new AudioContext() + const source = context.createMediaStreamSource(user_media) + const destination = context.createMediaStreamDestination() + const gain = context.createGain() + gain.gain.value = this.default_gain + this.mic_gain = gain + + let rnnoise: RNNoiseNode; + if (use_rnnoise) { + rnnoise = await get_rnnoise_node(context) + source.connect(rnnoise) + rnnoise.connect(gain) + } else { + source.connect(gain) + } + gain.connect(destination) + + const t = new TrackHandle(destination.stream.getAudioTracks()[0], true) + + t.addEventListener("ended", () => { + user_media.getTracks().forEach(t => t.stop()) + source.disconnect() + if (rnnoise) rnnoise.disconnect() + gain.disconnect() + destination.disconnect() + this.mic_gain = undefined + }) + + return t + } +} diff --git a/client-web/source/logger.ts b/client-web/source/logger.ts new file mode 100644 index 0000000..e00b1d0 --- /dev/null +++ b/client-web/source/logger.ts @@ -0,0 +1,50 @@ +/// + +const log_tag_color = { + "*": "#FF4444", + webrtc: "#FF44FF", + media: "#FFFF44", + ws: "#44FFFF", + rnnoise: "#2222FF", + usermodel: "#44FF44", + error: "#FF0000", +} +export type LogTag = keyof typeof log_tag_color + +let logger_container: HTMLDivElement + +// TODO maybe log time aswell +// deno-lint-ignore no-explicit-any +export function log(tag: LogTag, message: string, ...data: any[]) { + for (let i = 0; i < data.length; i++) { + const e = data[i]; + if (e instanceof MediaStreamTrack) data[i] = `(${e.kind}) ${e.id}` + } + console.log(`%c[${tag}] ${message}`, "color:" + log_tag_color[tag], ...data); + + if (logger_container) { + const e = document.createElement("p") + e.classList.add("logger-line") + e.textContent = `[${tag}] ${message}` + e.style.color = log_tag_color[tag] + logger_container.append(e) + setTimeout(() => { + e.remove() + }, tag == "error" ? 60000 : 6000) + } +} + +globalThis.addEventListener("load", () => { + const d = document.createElement("div") + d.classList.add("logger-container") + document.body.append(d) + logger_container = d + + // clear the console every hour so logs dont accumulate + setInterval(() => console.clear(), 1000 * 60 * 60) +}) + +globalThis.onerror = (_ev, source, line, col, err) => { + log("error", `${err?.name} ${err?.message}`, err) + log("error", `on ${source}:${line}:${col}`, err) +} \ No newline at end of file diff --git a/client-web/source/menu.ts b/client-web/source/menu.ts new file mode 100644 index 0000000..1401b42 --- /dev/null +++ b/client-web/source/menu.ts @@ -0,0 +1,26 @@ +import { Room } from "./room.ts"; + +export function create_menu(room?: Room) { + const menu = document.createElement("div") + menu.classList.add("menu-overlay") + document.body.append(menu) + + const item = (name: string, cb: (() => void) | string) => { + const p = document.createElement("p") + const a = document.createElement("a") + a.classList.add("menu-item") + a.textContent = name + if (typeof cb == "string") a.href = cb + else a.addEventListener("click", cb), a.href = "#" + p.append(a) + return p + } + + if (room) menu.append( + item("Settings", () => alert("todo, refer to the url parameters in the docs for now")) + ) + menu.append( + item("Licence", "/licence"), + item("Sources / Documentation", "https://codeberg.org/metamuffin/keks-meet"), + ) +} \ No newline at end of file diff --git a/client-web/source/remote_user.ts b/client-web/source/remote_user.ts new file mode 100644 index 0000000..a0fdeaf --- /dev/null +++ b/client-web/source/remote_user.ts @@ -0,0 +1,74 @@ +/// + +import { servers } from "./index.ts" +import { log } from "./logger.ts" +import { Room } from "./room.ts" +import { TrackHandle } from "./track_handle.ts"; +import { User } from "./user.ts" + +export class RemoteUser extends User { + peer: RTCPeerConnection + negotiation_busy = false + + constructor(room: Room, name: string) { + super(room, name) + log("usermodel", `added remote user: ${name}`) + this.peer = new RTCPeerConnection(servers) + this.peer.onicecandidate = ev => { + if (!ev.candidate) return + room.websocket_send({ ice_candiate: ev.candidate.toJSON(), receiver: this.name }) + } + this.peer.ontrack = ev => { + const t = ev.track + log("media", `remote track: ${this.name}`, t) + this.add_track(new TrackHandle(t)) + } + this.peer.onnegotiationneeded = async () => { + log("webrtc", `negotiation needed: ${this.name}`) + while (this.negotiation_busy) { + await new Promise(r => setTimeout(() => r(), 100)) + } + this.offer() + } + } + + async offer() { + this.negotiation_busy = true + const offer_description = await this.peer.createOffer() + await this.peer.setLocalDescription(offer_description) + const offer = { type: offer_description.type, sdp: offer_description.sdp } + log("webrtc", `sent offer: ${this.name}`, { a: offer }) + this.room.websocket_send({ receiver: this.name, offer }) + } + async on_offer(offer: RTCSessionDescriptionInit) { + this.negotiation_busy = true + log("webrtc", `got offer: ${this.name}`, { a: offer }) + const offer_description = new RTCSessionDescription(offer) + await this.peer.setRemoteDescription(offer_description) + this.answer() + } + async answer() { + const answer_description = await this.peer.createAnswer() + await this.peer.setLocalDescription(answer_description) + const answer = { type: answer_description.type, sdp: answer_description.sdp } + log("webrtc", `sent answer: ${this.name}`, { a: answer }) + this.room.websocket_send({ receiver: this.name, answer }) + this.negotiation_busy = false + } + async on_answer(answer: RTCSessionDescriptionInit) { + log("webrtc", `got answer: ${this.name}`, { a: answer }) + const answer_description = new RTCSessionDescription(answer) + await this.peer.setRemoteDescription(answer_description) + this.negotiation_busy = false + } + + add_ice_candidate(candidate: RTCIceCandidateInit) { + this.peer.addIceCandidate(new RTCIceCandidate(candidate)) + } + + leave() { + log("usermodel", `remove remote user: ${this.name}`) + this.peer.close() + this.room.el.removeChild(this.el) + } +} \ No newline at end of file diff --git a/client-web/source/rnnoise.ts b/client-web/source/rnnoise.ts new file mode 100644 index 0000000..7867682 --- /dev/null +++ b/client-web/source/rnnoise.ts @@ -0,0 +1,38 @@ +/// + +import { log } from "./logger.ts" + +declare global { + class RNNoiseNode extends AudioWorkletNode { + static register(context: AudioContext): Promise + constructor(context: AudioContext) + // deno-lint-ignore no-explicit-any + onstatus: (data: any) => void + update(something: boolean): void + } +} + + +// TODO fix leak +export async function get_rnnoise_node(context: AudioContext): Promise { + log("rnnoise", "enabled") + //@ts-ignore asfdasfd + let RNNoiseNode: typeof RNNoiseNode = window.RNNoiseNode; + + let script: HTMLScriptElement; + if (!RNNoiseNode) { + log("rnnoise", "loading wasm...") + script = document.createElement("script") + script.src = "/_rnnoise/rnnoise-runtime.js" + script.defer = true + document.head.appendChild(script) + //@ts-ignore asdfsfad + while (!window.RNNoiseNode) await new Promise(r => setTimeout(() => r(), 100)) + //@ts-ignore asfdsadfsafd + RNNoiseNode = window.RNNoiseNode; + log("rnnoise", "loaded") + } + + await RNNoiseNode.register(context) + return new RNNoiseNode(context) +} \ No newline at end of file diff --git a/client-web/source/room.ts b/client-web/source/room.ts new file mode 100644 index 0000000..f22cff1 --- /dev/null +++ b/client-web/source/room.ts @@ -0,0 +1,72 @@ +/// + +import { log } from "./logger.ts"; +import { RemoteUser } from "./remote_user.ts"; +import { User } from "./user.ts"; +import { LocalUser } from "./local_user.ts"; +import { hex_id, parameter_string } from "./helper.ts"; +import { PacketS, PacketC } from "../../common/packets.d.ts"; + + +export class Room { + el: HTMLElement + name: string + users: Map = new Map() + remote_users: Map = new Map() + local_user: LocalUser + websocket: WebSocket + + constructor(name: string) { + this.name = name + this.el = document.createElement("div") + this.el.classList.add("room") + this.websocket = new WebSocket(`${window.location.protocol.endsWith("s:") ? "wss" : "ws"}://${window.location.host}/signaling/${encodeURIComponent(name)}`) + this.websocket.onclose = () => this.websocket_close() + this.websocket.onopen = () => this.websocket_open() + this.websocket.onmessage = (ev) => { + this.websocket_message(JSON.parse(ev.data)) + } + this.local_user = new LocalUser(this, parameter_string("username", `guest-${hex_id()}`)) + } + + websocket_send(data: PacketS) { + log("ws", `-> ${data.receiver ?? "*"}`, data) + this.websocket.send(JSON.stringify(data)) + } + websocket_message(packet: PacketC) { + if (packet.join) { + log("*", `${this.name} ${packet.sender} joined`); + const ru = new RemoteUser(this, packet.sender) + this.local_user.add_initial_to_remote(ru) + if (!packet.stable) ru.offer() + this.users.set(packet.sender, ru) + this.remote_users.set(packet.sender, ru) + return + } + const sender = this.remote_users.get(packet.sender) + if (!sender) return console.warn(`unknown sender ${packet.sender}`) + if (packet.leave) { + log("*", `${this.name} ${packet.sender} left`); + sender.leave() + this.users.delete(packet.sender) + this.remote_users.delete(packet.sender) + return + } + if (!packet.data) return console.warn("dataless packet") + log("ws", `<- ${packet.sender}: `, packet.data); + if (packet.data.ice_candiate) sender.add_ice_candidate(packet.data.ice_candiate) + if (packet.data.offer) sender.on_offer(packet.data.offer) + if (packet.data.answer) sender.on_answer(packet.data.answer) + } + websocket_close() { + log("ws", "websocket closed"); + setTimeout(() => { + window.location.reload() + }, 1000) + } + websocket_open() { + log("ws", "websocket opened"); + this.websocket.send(this.local_user.name) + setInterval(() => this.websocket_send({}), 30000) // stupid workaround for nginx disconnection inactive connections + } +} \ No newline at end of file diff --git a/client-web/source/track_handle.ts b/client-web/source/track_handle.ts new file mode 100644 index 0000000..98b2b2f --- /dev/null +++ b/client-web/source/track_handle.ts @@ -0,0 +1,26 @@ +/// + +export class TrackHandle extends EventTarget { + constructor( + public track: MediaStreamTrack, + public local = false + ) { + super() + track.onended = () => this.dispatchEvent(new CustomEvent("ended")) + // TODO research how onmute and onunmute behave + track.onmute = () => this.dispatchEvent(new CustomEvent("ended")) // onmute seems to be called when the remote ends the track + track.onunmute = () => this.dispatchEvent(new CustomEvent("started")) + + this.addEventListener("ended", () => { + // drop all references to help gc + track.onunmute = track.onmute = track.onended = null + }) + } + + get kind() { return this.track.kind } + get label() { return this.track.label } + get muted() { return this.track.muted } + get id() { return this.track.id } + + end() { this.track.stop(); this.dispatchEvent(new CustomEvent("ended")) } +} diff --git a/client-web/source/user.ts b/client-web/source/user.ts new file mode 100644 index 0000000..bda875f --- /dev/null +++ b/client-web/source/user.ts @@ -0,0 +1,80 @@ +/// + +import { log } from "./logger.ts" +import { Room } from "./room.ts" +import { TrackHandle } from "./track_handle.ts"; + + +export abstract class User { + name: string + room: Room + + el: HTMLElement + + local = false + + protected tracks: Set = new Set() + + constructor(room: Room, name: string) { + this.name = name + this.room = room + this.el = document.createElement("div") + this.el.classList.add("user") + this.room.el.append(this.el) + this.setup_view() + } + + add_track(t: TrackHandle) { + this.tracks.add(t) + this.create_track_element(t) + t.addEventListener("ended", () => { + log("media", "track ended", t) + this.tracks.delete(t) + }) + t.addEventListener("mute", () => { + log("media", "track muted", t) + }) + t.addEventListener("unmute", () => { + log("media", "track unmuted", t) + }) + } + + setup_view() { + const info_el = document.createElement("div") + info_el.classList.add("info") + const name_el = document.createElement("span") + name_el.textContent = this.name + name_el.classList.add("name") + info_el.append(name_el) + this.el.append(info_el) + } + + create_track_element(t: TrackHandle) { + const is_video = t.kind == "video" + const media_el = is_video ? document.createElement("video") : document.createElement("audio") + const stream = new MediaStream([t.track]) + media_el.srcObject = stream + media_el.classList.add("media") + media_el.autoplay = true + media_el.controls = true + + if (this.local) media_el.muted = true + + + const el = document.createElement("div") + if (t.local) { + const end_button = document.createElement("button") + end_button.textContent = "End" + end_button.addEventListener("click", () => { + t.end() + }) + el.append(end_button) + } + el.append(media_el) + this.el.append(el) + t.addEventListener("ended", () => { + media_el.srcObject = null + el.remove() + }) + } +} \ No newline at end of file diff --git a/common/packets.d.ts b/common/packets.d.ts new file mode 100644 index 0000000..f42d99b --- /dev/null +++ b/common/packets.d.ts @@ -0,0 +1,21 @@ + +// copy pasted from dom.lib.d.ts because it can not be referenced in the server. +type F_RTCSdpType = "answer" | "offer" | "pranswer" | "rollback"; +interface F_RTCSessionDescriptionInit { sdp?: string; type: F_RTCSdpType; } +interface F_RTCIceCandidateInit { candidate?: string; sdpMLineIndex?: number | null; sdpMid?: string | null; usernameFragment?: string | null; } + +export interface PacketC { + sender: string, + data?: PacketS, + join?: boolean, // user just joined + leave?: boolean, // user left + stable?: boolean // user "joined" because you joined aka. user was already there +} +export interface PacketS { + receiver?: string + ice_candiate?: F_RTCIceCandidateInit + offer?: F_RTCSessionDescriptionInit + answer?: F_RTCSessionDescriptionInit +} + + diff --git a/nodemon.json b/nodemon.json deleted file mode 100644 index 6c020fe..0000000 --- a/nodemon.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "watch": "./source/server", - "exec": "deno run --allow-read --allow-env --allow-net --unstable source/server/index.ts", - "ext": "ts" -} \ No newline at end of file diff --git a/public/index.html b/public/index.html deleted file mode 100644 index d88abbd..0000000 --- a/public/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - keks-meet - - - -

- keks-meet needs evil javascript to be enabled. Don't be afraid though, all - the code is free (AGPL-3.0-only)! Look at it on - codeberg -

- - diff --git a/public/rnnoise/LICENSE b/public/rnnoise/LICENSE deleted file mode 100644 index 4824556..0000000 --- a/public/rnnoise/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 WONG Tin Chi Timothy - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/public/rnnoise/rnnoise-processor.js b/public/rnnoise/rnnoise-processor.js deleted file mode 100644 index 5b594a4..0000000 --- a/public/rnnoise/rnnoise-processor.js +++ /dev/null @@ -1,41 +0,0 @@ -"use strict"; -{ - let b, d; - registerProcessor( - "rnnoise", - class extends AudioWorkletProcessor { - constructor(a) { - super({ - ...a, - numberOfInputs: 1, - numberOfOutputs: 1, - outputChannelCount: [1], - }); - b || - (d = new Float32Array( - (b = new WebAssembly.Instance(a.processorOptions.module) - .exports).memory.buffer - )); - this.state = b.newState(); - this.alive = !0; - this.port.onmessage = ({ data: a }) => { - this.alive && - (a - ? this.port.postMessage({ vadProb: b.getVadProb(this.state) }) - : ((this.alive = !1), b.deleteState(this.state))); - }; - } - process(a, c, e) { - if (!a[0][0]) return 1 - if (this.alive) - return ( - d.set(a[0][0], b.getInput(this.state) / 4), - (a = c[0][0]), - (c = b.pipe(this.state, a.length) / 4) && - a.set(d.subarray(c, c + a.length)), - !0 - ); - } - } - ); -} diff --git a/public/rnnoise/rnnoise-processor.wasm b/public/rnnoise/rnnoise-processor.wasm deleted file mode 100644 index 86fea35..0000000 Binary files a/public/rnnoise/rnnoise-processor.wasm and /dev/null differ diff --git a/public/rnnoise/rnnoise-runtime.js b/public/rnnoise/rnnoise-runtime.js deleted file mode 100644 index f69c568..0000000 --- a/public/rnnoise/rnnoise-runtime.js +++ /dev/null @@ -1,76 +0,0 @@ -"use strict"; -{ - const g = document.currentScript.src.match(/(.*\/)?/)[0], - h = ( - WebAssembly.compileStreaming || - (async (a) => await WebAssembly.compile(await (await a).arrayBuffer())) - )(fetch(g + "rnnoise-processor.wasm")); - let k, c, e; - window.RNNoiseNode = - ((window.AudioWorkletNode || - (window.AudioWorkletNode = window.webkitAudioWorkletNode)) && - class extends AudioWorkletNode { - static async register(a) { - k = await h; - await a.audioWorklet.addModule(g + "rnnoise-processor.js"); - } - constructor(a) { - super(a, "rnnoise", { - channelCountMode: "explicit", - channelCount: 1, - channelInterpretation: "speakers", - numberOfInputs: 1, - numberOfOutputs: 1, - outputChannelCount: [1], - processorOptions: { module: k }, - }); - this.port.onmessage = ({ data: b }) => { - b = Object.assign(new Event("status"), b); - this.dispatchEvent(b); - if (this.onstatus) this.onstatus(b); - }; - } - update(a) { - this.port.postMessage(a); - } - }) || - ((window.ScriptProcessorNode || - (window.ScriptProcessorNode = window.webkitScriptProcessorNode)) && - Object.assign( - function (a) { - const b = a.createScriptProcessor(512, 1, 1), - d = c.newState(); - let f = !0; - b.onaudioprocess = ({ inputBuffer: b, outputBuffer: a }) => { - f && - (e.set(b.getChannelData(0), c.getInput(d) / 4), - (b = a.getChannelData(0)), - (a = c.pipe(d, b.length) / 4) && - b.set(e.subarray(a, a + b.length))); - }; - b.update = (a) => { - if (f) - if (a) { - if ( - ((a = Object.assign(new Event("status"), { - vadProb: c.getVadProb(d), - })), - b.dispatchEvent(a), - b.onstatus) - ) - b.onstatus(a); - } else (f = !1), c.deleteState(d); - }; - return b; - }, - { - register: async () => { - c || - (e = new Float32Array( - (c = (await WebAssembly.instantiate(await h)) - .exports).memory.buffer - )); - }, - } - )); -} diff --git a/public/style/logger.css b/public/style/logger.css deleted file mode 100644 index cb76586..0000000 --- a/public/style/logger.css +++ /dev/null @@ -1,42 +0,0 @@ -.logger-container { - position: absolute; - top: 0px; - right: 0px; - transition: width 1s; - - background-color: rgba(0, 0, 0, 0.376); - border-radius: 0.2em; - border: 0px solid transparent; - padding: 0.2em; -} - -.logger-line { - font-size: 1em; - height: 1.2em; - - animation-name: appear, disappear; - animation-timing-function: linear, linear; - animation-delay: 0s, 3s; - animation-duration: 0.3s, 1s; - animation-fill-mode: forwards, forwards; -} - -@keyframes appear { - from { - margin-top: -1.2em; - opacity: 0; - } - to { - opacity: 1; - } -} - -@keyframes disappear { - from { - opacity: 1; - } - to { - margin-top: -1.2em; - opacity: 0; - } -} diff --git a/public/style/master.css b/public/style/master.css deleted file mode 100644 index 0b9e205..0000000 --- a/public/style/master.css +++ /dev/null @@ -1,115 +0,0 @@ -@import url("https://s.metamuffin.org/static/font-ubuntu/include.css"); -@import url("./logger.css"); - -* { - font-family: "Ubuntu", sans-serif; - font-weight: 300; - color: white; - margin: 0px; - padding: 0px; -} - -:root { - --bg: #263238; - --bg-dark: #000a12; - --bg-light: #354b58; - --bg-lighter: #4f5b62; - --bg-disabled: #720000; - --bg-enabled: #097200; - --ac: #4a148c; - --ac-light: #7c43bd; - --ac-dark: #12005e; -} - -body { - background-color: var(--bg-dark); -} - -h2 { - font-weight: 700; - margin: 1em; -} - -input[type="button"], -button { - padding: 0.5em; - margin: 0.25em; - background-color: var(--bg-light); - border: 0px solid transparent; - border-radius: 3px; -} -input[type="button"]:hover, -button:hover { - background-color: var(--bg-lighter); -} -input[type="button"].enabled, -button.enabled { - background-color: var(--bg-enabled); -} -input[type="text"] { - background-color: var(--bg-dark); - border: 1px solid var(--ac-light); -} - -.local-controls { - background-color: var(--bg); - padding: 0.5em; - position: absolute; - bottom: 0.5em; - border: 0px solid transparent; - border-radius: 5px; - left: 50%; - transform: translateX(-50%); - z-index: 100; -} - -.room { - width: 100%; - height: 100%; -} - -.user { - background-color: var(--bg); - border: 0px soly transparent; - border-radius: 5px; - padding: 1em; - vertical-align: baseline; - min-width: 10em; - margin: 0.5em; -} - -.user .info .name { - font-weight: 400; -} -.user.local .info .name { - text-decoration: underline; -} - -.media { - max-height: 30vh; - border: 0px solid transparent; - border-radius: 5px; -} - -.start-box { - position: absolute; - top: 50vh; - left: 50vw; - transform: translate(-50%, -50%); -} -.start-box p { - margin-bottom: 0.5em; -} -.start-box input[type="text"] { - margin: 0.5em; - font-size: 32px; -} - -.menu-overlay { - position: absolute; - bottom: 0px; - right: 0px; - display: block; - text-align: right; -} - diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..1fd3900 --- /dev/null +++ b/readme.md @@ -0,0 +1,23 @@ +# keks-meet + +a web conference application + +## Parameters + +For configuration add options in section of URL in a style that is common for query parameters (e.g. `/room/asdfg#username=bob`). +Note that the page wont automatically reload if the section changes. + +Booleans can be either `1`, `true`, `yes` or their opposites. + +| Option name | Type | Default | Description | +| ---------------- | ------- | ------- | ---------------------------------------- | +| `username` | string | "guest" | Sets the username | +| `rnnoise` | boolean | true | Enables noise suppression with rnnoise | +| `mic_enabled` | boolean | false | Adds audio track on startup | +| `camera_enabled` | boolean | false | Adds camera track on startup | +| `screen_enabled` | boolean | false | Adds screen track on startup (wont work) | +| `mic_gain` | number | 1 | Sets the microphone volume | + +## Licence + +See `LICENCE` file. diff --git a/server-legacy/server/index.ts b/server-legacy/server/index.ts new file mode 100644 index 0000000..de2ba4e --- /dev/null +++ b/server-legacy/server/index.ts @@ -0,0 +1,77 @@ +import { Application, Router, RouterContext, send } from "https://deno.land/x/oak@v10.4.0/mod.ts"; +import { api } from "./room.ts"; +import { bundle } from "https://deno.land/x/emit@0.1.1/mod.ts"; + +const app = new Application() +const root = new Router() + + +root.get("/", async c => { await c.send({ path: "index.html", root: `${Deno.cwd()}/public` }) }) +root.get("/room/:id", async c => { await c.send({ path: "index.html", root: `${Deno.cwd()}/public` }) }) + +root.get("/licen(c|s)e", async c => { + c.response.body = await Deno.readTextFile("LICENCE") + c.response.headers.set("Content-Type", "text/plain") +}) + +root.get("/favicon.ico", c => { c.response.status = 204 }) + +// deno-lint-ignore no-explicit-any +function respondWithType(mimeType: string, f: () => string): (c: RouterContext) => void { + return c => { + c.response.headers.set("Content-Type", mimeType) + c.response.body = f() + } +} + +let bundle_code = "" +root.get("/bundle.js", respondWithType("application/javascript", () => bundle_code)) + +root.use(api.routes()) + +function mountFilesystem(r: Router, route: string, path: string) { + r.get(route + "/(.*)", async (context) => { + console.log(context.request.url.pathname.substring(route.length)); + await send(context, context.request.url.pathname.substring(route.length), { root: Deno.cwd() + path }); + }) +} + +mountFilesystem(root, "/_style", "/public/style") +mountFilesystem(root, "/_rnnoise", "/public/rnnoise") + +app.use(root.routes()) +app.use(root.allowedMethods()) + +app.addEventListener("listen", ({ hostname, port, secure }) => { + console.log(`listening on: ${secure ? "https://" : "http://"}${hostname}:${port}`); +}); + +app.listen({ + hostname: Deno.env.get("HOSTNAME") ?? "127.0.0.1", + port: parseInt(Deno.env.get("PORT") ?? "8080") +}); + + +let refresh_needed = false +let refresh_pending = false +async function refresh() { + refresh_needed = true + if (refresh_pending) return + refresh_needed = false + refresh_pending = true + + try { + const { code } = await bundle("source/client/index.ts", { compilerOptions: { checkJs: false } }) + bundle_code = code + } catch (e) { console.error(e) } + + refresh_pending = false + if (refresh_needed) refresh() +} + +refresh() +for await (const event of Deno.watchFs("source/client")) { + if (event.kind == "modify" || event.kind == "create") { + refresh() + } +} diff --git a/server-legacy/server/room.ts b/server-legacy/server/room.ts new file mode 100644 index 0000000..d9c1a25 --- /dev/null +++ b/server-legacy/server/room.ts @@ -0,0 +1,58 @@ +import { Router } from "https://deno.land/x/oak@v10.4.0/router.ts"; +import { PacketC, PacketS } from "../packets.ts"; + +export const api = new Router() + +type Room = Map +const rooms: Map = new Map() + +function send_packet(ws: WebSocket, packet: PacketC) { + ws.send(JSON.stringify(packet)) +} + +api.get("/signaling/:id", c => { + const ws = c.upgrade() + + const room_name = c.params.id + const room: Room = rooms.get(c.params.id) ?? new Map() + let initialized = false + let user_name = "" + + const init = (n: string) => { + if (room.get(n)) return ws.close() + initialized = true + user_name = n + rooms.set(c.params.id, room) + room.forEach(uws => send_packet(uws, { sender: user_name, join: true })) + room.forEach((_, uname) => send_packet(ws, { sender: uname, join: true, stable: true })) + room.set(user_name, ws) + console.log(`[${room_name}] ${user_name} joined`) + } + ws.onclose = () => { + room.delete(user_name) + room.forEach(uws => send_packet(uws, { sender: user_name, leave: true })) + if (room.size == 0) rooms.delete(room_name) + console.log(`[${room_name}] ${user_name} left`) + } + ws.onmessage = ev => { + const message = ev.data.toString() + if (!initialized) return init(message) + let in_packet: PacketS; + try { in_packet = JSON.parse(message) } + catch (_e) { return } + + if (JSON.stringify(in_packet) == "{}") return // drop ping + + console.log(`[${room_name}] ${user_name} -> ${in_packet.receiver ?? "*"}: ${message.substr(0, 100)}`) + const out_packet: PacketC = { sender: user_name, data: in_packet } + + if (in_packet.receiver) { + const rws = room.get(in_packet.receiver) + if (rws) send_packet(rws, out_packet) + } else { + room.forEach((uws, uname) => { + if (uname != user_name) send_packet(uws, out_packet) + }) + } + } +}) diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/server/.gitignore @@ -0,0 +1 @@ +/target diff --git a/server/Cargo.lock b/server/Cargo.lock new file mode 100644 index 0000000..5798db1 --- /dev/null +++ b/server/Cargo.lock @@ -0,0 +1,1193 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.3", + "crypto-common", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" + +[[package]] +name = "futures-macro" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" + +[[package]] +name = "futures-task" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" + +[[package]] +name = "futures-util" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util 0.7.3", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "libc" +version = "0.2.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "listenfd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e4fcc00ff6731d94b70e16e71f43bda62883461f31230742e3bc6dddf12988" +dependencies = [ + "libc", + "uuid", + "winapi", +] + +[[package]] +name = "lock_api" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log", + "mime", + "mime_guess", + "quick-error", + "rand", + "safemem", + "tempfile", + "twoway", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" + +[[package]] +name = "serde_json" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "server" +version = "0.1.0" +dependencies = [ + "env_logger", + "futures-util", + "hyper", + "listenfd", + "log", + "tokio", + "warp", +] + +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha1" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +dependencies = [ + "futures-util", + "log", + "pin-project", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "sha-1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "multipart", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util 0.6.10", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/server/Cargo.toml b/server/Cargo.toml new file mode 100644 index 0000000..f9b4cd2 --- /dev/null +++ b/server/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "server" +version = "0.1.0" +edition = "2021" + +[dependencies] +warp = "0.3" +tokio = { version = "1.21", features = ["full"] } +log = "0.4" +env_logger = "0.8" +futures-util = "0.3.24" +listenfd = "1.0.0" +hyper = "0.14.20" diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 0000000..1128fb1 --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,118 @@ +use futures_util::{SinkExt, StreamExt, TryFutureExt}; +use listenfd::ListenFd; +use log::error; +use std::collections::HashMap; +use std::convert::Infallible; +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; +use tokio::sync::{mpsc, RwLock}; +use warp::hyper::Server; +use warp::ws::{Message, WebSocket}; +use warp::Filter; + +static NEXT_USER_ID: AtomicUsize = AtomicUsize::new(1); + +type Users = Arc>>>; + +#[tokio::main] +async fn main() { + env_logger::init_from_env("LOG"); + + let users = Users::default(); + let users = warp::any().map(move || users.clone()); + + let signaling = warp::path("signaling") + .and(warp::ws()) + .and(users) + .map(|ws: warp::ws::Ws, users| ws.on_upgrade(move |socket| user_connected(socket, users))); + + let static_routes = { + let index = warp::get() + .and(warp::path!()) + .and(warp::fs::file("../client-web/public/index.html")); + let assets = warp::path("_assets").and(warp::fs::dir("../client-web/public/assets")); + + index + }; + + let routes = static_routes.or(signaling).or(four_oh_four_tm); + + // if listender fd is passed from the outside world, use it. + let mut listenfd = ListenFd::from_env(); + let server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() { + Server::from_tcp(l).unwrap() + } else { + Server::bind(&([127, 0, 0, 1], 3030).into()) + }; + let service = warp::service(routes); + server + .serve(hyper::service::make_service_fn(|_| { + let service = service.clone(); + async move { Ok::<_, Infallible>(service) } + })) + .await + .unwrap(); +} + +async fn user_connected(ws: WebSocket, users: Users) { + let my_id = NEXT_USER_ID.fetch_add(1, Ordering::Relaxed); + + eprintln!("new chat user: {}", my_id); + + let (mut user_ws_tx, mut user_ws_rx) = ws.split(); + + let (tx, mut rx) = mpsc::unbounded_channel(); + + tokio::task::spawn(async move { + while let Some(message) = rx.recv().await { + user_ws_tx + .send(message) + .unwrap_or_else(|e| { + eprintln!("websocket send error: {}", e); + }) + .await; + } + }); + + users.write().await.insert(my_id, tx); + + while let Some(result) = user_ws_rx.next().await { + let msg = match result { + Ok(msg) => msg, + Err(e) => { + error!("websocket error(uid={my_id}): {e}"); + break; + } + }; + user_message(my_id, msg, &users).await; + } + + users.write().await.remove(&my_id); +} + +async fn user_message(my_id: usize, msg: Message, users: &Users) { + // Skip any non-Text messages... + let msg = if let Ok(s) = msg.to_str() { + s + } else { + return; + }; + + let new_msg = format!(": {}", my_id, msg); + + for (&uid, tx) in users.read().await.iter() { + if my_id != uid { + if let Err(_disconnected) = tx.send(Message::text(new_msg.clone())) { + // The tx is disconnected, our `user_disconnected` code + // should be happening in another task, nothing more to + // do here. + } + } + } +} + +async fn user_disconnected(my_id: usize, users: &Users) { + eprintln!("good bye user: {}", my_id); +} diff --git a/source/client/helper.ts b/source/client/helper.ts deleted file mode 100644 index 31e500a..0000000 --- a/source/client/helper.ts +++ /dev/null @@ -1,43 +0,0 @@ -/// - -import { parameters } from "./index.ts" - -export function get_query_params(): { [key: string]: string } { - const q: { [key: string]: string } = {} - for (const kv of window.location.hash.substring(1).split("&")) { - const [key, value] = kv.split("=") - q[decodeURIComponent(key)] = decodeURIComponent(value) - } - return q -} - -export function hex_id(len = 8): string { - if (len > 8) return hex_id() + hex_id(len - 8) - return Math.floor(Math.random() * 16 ** len).toString(16).padStart(len, "0") -} - -export function parameter_bool(name: string, def: boolean): boolean { - const v = parameters[name] - if (!v) return def - if (v == "0" || v == "false" || v == "no") return false - if (v == "1" || v == "true" || v == "yes") return true - alert(`parameter ${name} is invalid`) - return def -} - -export function parameter_number(name: string, def: number): number { - const v = parameters[name] - if (!v) return def - const n = parseFloat(v) - if (Number.isNaN(n)) { - alert(`parameter ${name} is invalid`) - return def - } - return n -} - -export function parameter_string(name: string, def: string): string { - const v = parameters[name] - if (!v) return def - return v -} diff --git a/source/client/index.ts b/source/client/index.ts deleted file mode 100644 index 745dd67..0000000 --- a/source/client/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -/// - -import { get_query_params } from "./helper.ts" -import { log } from "./logger.ts" -import { create_menu } from "./menu.ts"; -import { Room } from "./room.ts" - -export const servers: RTCConfiguration = { - iceServers: [{ urls: ["stun:stun1.l.google.com:19302", "stun:stun2.l.google.com:19302"] }], - iceCandidatePoolSize: 10, -} - -export interface User { - peer: RTCPeerConnection - stream: MediaStream, -} - -export const parameters = get_query_params() - -window.onload = () => main() - -export function main() { - document.body.querySelector("p")?.remove() - log("*", "starting up") - if (window.location.pathname.startsWith("/room/")) { - const room_name = window.location.pathname.substring("/room/".length) - const room = new Room(room_name) - create_menu(room) - document.body.append(room.el) - } else { - create_menu() - document.body.append(create_start_screen()) - } -} - -function create_start_screen() { - const with_text_content = (a: string) => (b: string) => { - const e = document.createElement(a) - e.textContent = b - return e - } - const p = with_text_content("p") - const h2 = with_text_content("h2") - - const el = document.createElement("div") - el.append( - h2("keks-meet"), - p("A web conferencing application using webrtc"), - p("keks-meet is free software! It is licenced under the terms of the third version of the GNU Affero General Public Licence only."), - p("To get started, just enter a unique idenfier, click 'Join', then share the URL with your partner.") - ) - - const room_input = document.createElement("input") - room_input.type = "text" - room_input.id = "room-id-input" - room_input.placeholder = "room id" - - const submit = document.createElement("input") - submit.type = "button" - submit.addEventListener("click", () => { - if (room_input.value.length == 0) room_input.value = Math.floor(Math.random() * 10000).toString(16).padStart(5, "0") - window.location.pathname = `/room/${encodeURIComponent(room_input.value)}` - }) - submit.value = "Join room" - - el.classList.add("start-box") - el.append(room_input, document.createElement("br"), submit) - - return el -} diff --git a/source/client/local_user.ts b/source/client/local_user.ts deleted file mode 100644 index ca91a19..0000000 --- a/source/client/local_user.ts +++ /dev/null @@ -1,125 +0,0 @@ -/// - -import { parameter_bool, parameter_number } from "./helper.ts"; -import { log } from "./logger.ts"; -import { RemoteUser } from "./remote_user.ts"; -import { get_rnnoise_node } from "./rnnoise.ts"; -import { Room } from "./room.ts"; -import { TrackHandle } from "./track_handle.ts"; -import { User } from "./user.ts"; - - -export class LocalUser extends User { - mic_gain?: GainNode - default_gain: number = parameter_number("mic_gain", 1) - - constructor(room: Room, name: string) { - super(room, name) - this.el.classList.add("local") - this.local = true - this.create_controls() - this.add_initial_tracks() - log("usermodel", `added local user: ${this.name}`) - } - - async add_initial_tracks() { - if (parameter_bool("mic_enabled", false)) this.publish_track(await this.create_mic_track()) - if (parameter_bool("camera_enabled", false)) this.publish_track(await this.create_camera_track()) - if (parameter_bool("screen_enabled", false)) this.publish_track(await this.create_screen_track()) - } - - publish_track(t: TrackHandle) { - this.room.remote_users.forEach(u => u.peer.addTrack(t.track)) - this.add_track(t) - t.addEventListener("ended", () => { - this.room.remote_users.forEach(u => { - u.peer.getSenders().forEach(s => { - if (s.track == t.track) u.peer.removeTrack(s) - }) - }) - }) - } - - add_initial_to_remote(u: RemoteUser) { - this.tracks.forEach(t => u.peer.addTrack(t.track)) - } - - create_controls() { - const mic_toggle = document.createElement("input") - const camera_toggle = document.createElement("input") - const screen_toggle = document.createElement("input") - mic_toggle.type = camera_toggle.type = screen_toggle.type = "button" - mic_toggle.value = "Microphone" - camera_toggle.value = "Camera" - screen_toggle.value = "Screen" - - const create = async (_e: HTMLElement, tp: Promise) => { - log("media", "awaiting track") - const t = await tp - log("media", "got track") - this.publish_track(t) - } - - mic_toggle.addEventListener("click", () => create(mic_toggle, this.create_mic_track())) - camera_toggle.addEventListener("click", () => create(camera_toggle, this.create_camera_track())) - screen_toggle.addEventListener("click", () => create(screen_toggle, this.create_screen_track())) - - const el = document.createElement("div") - el.classList.add("local-controls") - el.append(mic_toggle, camera_toggle, screen_toggle) - document.body.append(el) - } - - - async create_camera_track() { - log("media", "requesting user media (camera)") - const user_media = await window.navigator.mediaDevices.getUserMedia({ video: true }) - return new TrackHandle(user_media.getVideoTracks()[0], true) - } - async create_screen_track() { - log("media", "requesting user media (screen)") - const user_media = await window.navigator.mediaDevices.getDisplayMedia({ video: true }) - return new TrackHandle(user_media.getVideoTracks()[0], true) - } - async create_mic_track() { - log("media", "requesting user media (audio)") - const use_rnnoise = parameter_bool("rnnoise", true) - const audio_contraints = use_rnnoise ? { - channelCount: { ideal: 1 }, - noiseSuppression: { ideal: false }, - echoCancellation: { ideal: true }, - autoGainControl: { ideal: false }, - } : true; - - const user_media = await window.navigator.mediaDevices.getUserMedia({ audio: audio_contraints }) - const context = new AudioContext() - const source = context.createMediaStreamSource(user_media) - const destination = context.createMediaStreamDestination() - const gain = context.createGain() - gain.gain.value = this.default_gain - this.mic_gain = gain - - let rnnoise: RNNoiseNode; - if (use_rnnoise) { - rnnoise = await get_rnnoise_node(context) - source.connect(rnnoise) - rnnoise.connect(gain) - } else { - source.connect(gain) - } - gain.connect(destination) - - const t = new TrackHandle(destination.stream.getAudioTracks()[0], true) - - t.addEventListener("ended", () => { - user_media.getTracks().forEach(t => t.stop()) - source.disconnect() - if (rnnoise) rnnoise.disconnect() - gain.disconnect() - destination.disconnect() - this.mic_gain = undefined - }) - - return t - } -} diff --git a/source/client/logger.ts b/source/client/logger.ts deleted file mode 100644 index e00b1d0..0000000 --- a/source/client/logger.ts +++ /dev/null @@ -1,50 +0,0 @@ -/// - -const log_tag_color = { - "*": "#FF4444", - webrtc: "#FF44FF", - media: "#FFFF44", - ws: "#44FFFF", - rnnoise: "#2222FF", - usermodel: "#44FF44", - error: "#FF0000", -} -export type LogTag = keyof typeof log_tag_color - -let logger_container: HTMLDivElement - -// TODO maybe log time aswell -// deno-lint-ignore no-explicit-any -export function log(tag: LogTag, message: string, ...data: any[]) { - for (let i = 0; i < data.length; i++) { - const e = data[i]; - if (e instanceof MediaStreamTrack) data[i] = `(${e.kind}) ${e.id}` - } - console.log(`%c[${tag}] ${message}`, "color:" + log_tag_color[tag], ...data); - - if (logger_container) { - const e = document.createElement("p") - e.classList.add("logger-line") - e.textContent = `[${tag}] ${message}` - e.style.color = log_tag_color[tag] - logger_container.append(e) - setTimeout(() => { - e.remove() - }, tag == "error" ? 60000 : 6000) - } -} - -globalThis.addEventListener("load", () => { - const d = document.createElement("div") - d.classList.add("logger-container") - document.body.append(d) - logger_container = d - - // clear the console every hour so logs dont accumulate - setInterval(() => console.clear(), 1000 * 60 * 60) -}) - -globalThis.onerror = (_ev, source, line, col, err) => { - log("error", `${err?.name} ${err?.message}`, err) - log("error", `on ${source}:${line}:${col}`, err) -} \ No newline at end of file diff --git a/source/client/menu.ts b/source/client/menu.ts deleted file mode 100644 index 1401b42..0000000 --- a/source/client/menu.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Room } from "./room.ts"; - -export function create_menu(room?: Room) { - const menu = document.createElement("div") - menu.classList.add("menu-overlay") - document.body.append(menu) - - const item = (name: string, cb: (() => void) | string) => { - const p = document.createElement("p") - const a = document.createElement("a") - a.classList.add("menu-item") - a.textContent = name - if (typeof cb == "string") a.href = cb - else a.addEventListener("click", cb), a.href = "#" - p.append(a) - return p - } - - if (room) menu.append( - item("Settings", () => alert("todo, refer to the url parameters in the docs for now")) - ) - menu.append( - item("Licence", "/licence"), - item("Sources / Documentation", "https://codeberg.org/metamuffin/keks-meet"), - ) -} \ No newline at end of file diff --git a/source/client/remote_user.ts b/source/client/remote_user.ts deleted file mode 100644 index a0fdeaf..0000000 --- a/source/client/remote_user.ts +++ /dev/null @@ -1,74 +0,0 @@ -/// - -import { servers } from "./index.ts" -import { log } from "./logger.ts" -import { Room } from "./room.ts" -import { TrackHandle } from "./track_handle.ts"; -import { User } from "./user.ts" - -export class RemoteUser extends User { - peer: RTCPeerConnection - negotiation_busy = false - - constructor(room: Room, name: string) { - super(room, name) - log("usermodel", `added remote user: ${name}`) - this.peer = new RTCPeerConnection(servers) - this.peer.onicecandidate = ev => { - if (!ev.candidate) return - room.websocket_send({ ice_candiate: ev.candidate.toJSON(), receiver: this.name }) - } - this.peer.ontrack = ev => { - const t = ev.track - log("media", `remote track: ${this.name}`, t) - this.add_track(new TrackHandle(t)) - } - this.peer.onnegotiationneeded = async () => { - log("webrtc", `negotiation needed: ${this.name}`) - while (this.negotiation_busy) { - await new Promise(r => setTimeout(() => r(), 100)) - } - this.offer() - } - } - - async offer() { - this.negotiation_busy = true - const offer_description = await this.peer.createOffer() - await this.peer.setLocalDescription(offer_description) - const offer = { type: offer_description.type, sdp: offer_description.sdp } - log("webrtc", `sent offer: ${this.name}`, { a: offer }) - this.room.websocket_send({ receiver: this.name, offer }) - } - async on_offer(offer: RTCSessionDescriptionInit) { - this.negotiation_busy = true - log("webrtc", `got offer: ${this.name}`, { a: offer }) - const offer_description = new RTCSessionDescription(offer) - await this.peer.setRemoteDescription(offer_description) - this.answer() - } - async answer() { - const answer_description = await this.peer.createAnswer() - await this.peer.setLocalDescription(answer_description) - const answer = { type: answer_description.type, sdp: answer_description.sdp } - log("webrtc", `sent answer: ${this.name}`, { a: answer }) - this.room.websocket_send({ receiver: this.name, answer }) - this.negotiation_busy = false - } - async on_answer(answer: RTCSessionDescriptionInit) { - log("webrtc", `got answer: ${this.name}`, { a: answer }) - const answer_description = new RTCSessionDescription(answer) - await this.peer.setRemoteDescription(answer_description) - this.negotiation_busy = false - } - - add_ice_candidate(candidate: RTCIceCandidateInit) { - this.peer.addIceCandidate(new RTCIceCandidate(candidate)) - } - - leave() { - log("usermodel", `remove remote user: ${this.name}`) - this.peer.close() - this.room.el.removeChild(this.el) - } -} \ No newline at end of file diff --git a/source/client/rnnoise.ts b/source/client/rnnoise.ts deleted file mode 100644 index 7867682..0000000 --- a/source/client/rnnoise.ts +++ /dev/null @@ -1,38 +0,0 @@ -/// - -import { log } from "./logger.ts" - -declare global { - class RNNoiseNode extends AudioWorkletNode { - static register(context: AudioContext): Promise - constructor(context: AudioContext) - // deno-lint-ignore no-explicit-any - onstatus: (data: any) => void - update(something: boolean): void - } -} - - -// TODO fix leak -export async function get_rnnoise_node(context: AudioContext): Promise { - log("rnnoise", "enabled") - //@ts-ignore asfdasfd - let RNNoiseNode: typeof RNNoiseNode = window.RNNoiseNode; - - let script: HTMLScriptElement; - if (!RNNoiseNode) { - log("rnnoise", "loading wasm...") - script = document.createElement("script") - script.src = "/_rnnoise/rnnoise-runtime.js" - script.defer = true - document.head.appendChild(script) - //@ts-ignore asdfsfad - while (!window.RNNoiseNode) await new Promise(r => setTimeout(() => r(), 100)) - //@ts-ignore asfdsadfsafd - RNNoiseNode = window.RNNoiseNode; - log("rnnoise", "loaded") - } - - await RNNoiseNode.register(context) - return new RNNoiseNode(context) -} \ No newline at end of file diff --git a/source/client/room.ts b/source/client/room.ts deleted file mode 100644 index 145fb1b..0000000 --- a/source/client/room.ts +++ /dev/null @@ -1,72 +0,0 @@ -/// - -import { log } from "./logger.ts"; -import { RemoteUser } from "./remote_user.ts"; -import { User } from "./user.ts"; -import { LocalUser } from "./local_user.ts"; -import { hex_id, parameter_string } from "./helper.ts"; -import { PacketS, PacketC } from "../packets.ts"; - - -export class Room { - el: HTMLElement - name: string - users: Map = new Map() - remote_users: Map = new Map() - local_user: LocalUser - websocket: WebSocket - - constructor(name: string) { - this.name = name - this.el = document.createElement("div") - this.el.classList.add("room") - this.websocket = new WebSocket(`${window.location.protocol.endsWith("s:") ? "wss" : "ws"}://${window.location.host}/signaling/${encodeURIComponent(name)}`) - this.websocket.onclose = () => this.websocket_close() - this.websocket.onopen = () => this.websocket_open() - this.websocket.onmessage = (ev) => { - this.websocket_message(JSON.parse(ev.data)) - } - this.local_user = new LocalUser(this, parameter_string("username", `guest-${hex_id()}`)) - } - - websocket_send(data: PacketS) { - log("ws", `-> ${data.receiver ?? "*"}`, data) - this.websocket.send(JSON.stringify(data)) - } - websocket_message(packet: PacketC) { - if (packet.join) { - log("*", `${this.name} ${packet.sender} joined`); - const ru = new RemoteUser(this, packet.sender) - this.local_user.add_initial_to_remote(ru) - if (!packet.stable) ru.offer() - this.users.set(packet.sender, ru) - this.remote_users.set(packet.sender, ru) - return - } - const sender = this.remote_users.get(packet.sender) - if (!sender) return console.warn(`unknown sender ${packet.sender}`) - if (packet.leave) { - log("*", `${this.name} ${packet.sender} left`); - sender.leave() - this.users.delete(packet.sender) - this.remote_users.delete(packet.sender) - return - } - if (!packet.data) return console.warn("dataless packet") - log("ws", `<- ${packet.sender}: `, packet.data); - if (packet.data.ice_candiate) sender.add_ice_candidate(packet.data.ice_candiate) - if (packet.data.offer) sender.on_offer(packet.data.offer) - if (packet.data.answer) sender.on_answer(packet.data.answer) - } - websocket_close() { - log("ws", "websocket closed"); - setTimeout(() => { - window.location.reload() - }, 1000) - } - websocket_open() { - log("ws", "websocket opened"); - this.websocket.send(this.local_user.name) - setInterval(() => this.websocket_send({}), 30000) // stupid workaround for nginx disconnection inactive connections - } -} \ No newline at end of file diff --git a/source/client/track_handle.ts b/source/client/track_handle.ts deleted file mode 100644 index 98b2b2f..0000000 --- a/source/client/track_handle.ts +++ /dev/null @@ -1,26 +0,0 @@ -/// - -export class TrackHandle extends EventTarget { - constructor( - public track: MediaStreamTrack, - public local = false - ) { - super() - track.onended = () => this.dispatchEvent(new CustomEvent("ended")) - // TODO research how onmute and onunmute behave - track.onmute = () => this.dispatchEvent(new CustomEvent("ended")) // onmute seems to be called when the remote ends the track - track.onunmute = () => this.dispatchEvent(new CustomEvent("started")) - - this.addEventListener("ended", () => { - // drop all references to help gc - track.onunmute = track.onmute = track.onended = null - }) - } - - get kind() { return this.track.kind } - get label() { return this.track.label } - get muted() { return this.track.muted } - get id() { return this.track.id } - - end() { this.track.stop(); this.dispatchEvent(new CustomEvent("ended")) } -} diff --git a/source/client/user.ts b/source/client/user.ts deleted file mode 100644 index bda875f..0000000 --- a/source/client/user.ts +++ /dev/null @@ -1,80 +0,0 @@ -/// - -import { log } from "./logger.ts" -import { Room } from "./room.ts" -import { TrackHandle } from "./track_handle.ts"; - - -export abstract class User { - name: string - room: Room - - el: HTMLElement - - local = false - - protected tracks: Set = new Set() - - constructor(room: Room, name: string) { - this.name = name - this.room = room - this.el = document.createElement("div") - this.el.classList.add("user") - this.room.el.append(this.el) - this.setup_view() - } - - add_track(t: TrackHandle) { - this.tracks.add(t) - this.create_track_element(t) - t.addEventListener("ended", () => { - log("media", "track ended", t) - this.tracks.delete(t) - }) - t.addEventListener("mute", () => { - log("media", "track muted", t) - }) - t.addEventListener("unmute", () => { - log("media", "track unmuted", t) - }) - } - - setup_view() { - const info_el = document.createElement("div") - info_el.classList.add("info") - const name_el = document.createElement("span") - name_el.textContent = this.name - name_el.classList.add("name") - info_el.append(name_el) - this.el.append(info_el) - } - - create_track_element(t: TrackHandle) { - const is_video = t.kind == "video" - const media_el = is_video ? document.createElement("video") : document.createElement("audio") - const stream = new MediaStream([t.track]) - media_el.srcObject = stream - media_el.classList.add("media") - media_el.autoplay = true - media_el.controls = true - - if (this.local) media_el.muted = true - - - const el = document.createElement("div") - if (t.local) { - const end_button = document.createElement("button") - end_button.textContent = "End" - end_button.addEventListener("click", () => { - t.end() - }) - el.append(end_button) - } - el.append(media_el) - this.el.append(el) - t.addEventListener("ended", () => { - media_el.srcObject = null - el.remove() - }) - } -} \ No newline at end of file diff --git a/source/packets.ts b/source/packets.ts deleted file mode 100644 index f42d99b..0000000 --- a/source/packets.ts +++ /dev/null @@ -1,21 +0,0 @@ - -// copy pasted from dom.lib.d.ts because it can not be referenced in the server. -type F_RTCSdpType = "answer" | "offer" | "pranswer" | "rollback"; -interface F_RTCSessionDescriptionInit { sdp?: string; type: F_RTCSdpType; } -interface F_RTCIceCandidateInit { candidate?: string; sdpMLineIndex?: number | null; sdpMid?: string | null; usernameFragment?: string | null; } - -export interface PacketC { - sender: string, - data?: PacketS, - join?: boolean, // user just joined - leave?: boolean, // user left - stable?: boolean // user "joined" because you joined aka. user was already there -} -export interface PacketS { - receiver?: string - ice_candiate?: F_RTCIceCandidateInit - offer?: F_RTCSessionDescriptionInit - answer?: F_RTCSessionDescriptionInit -} - - diff --git a/source/server/index.ts b/source/server/index.ts deleted file mode 100644 index de2ba4e..0000000 --- a/source/server/index.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Application, Router, RouterContext, send } from "https://deno.land/x/oak@v10.4.0/mod.ts"; -import { api } from "./room.ts"; -import { bundle } from "https://deno.land/x/emit@0.1.1/mod.ts"; - -const app = new Application() -const root = new Router() - - -root.get("/", async c => { await c.send({ path: "index.html", root: `${Deno.cwd()}/public` }) }) -root.get("/room/:id", async c => { await c.send({ path: "index.html", root: `${Deno.cwd()}/public` }) }) - -root.get("/licen(c|s)e", async c => { - c.response.body = await Deno.readTextFile("LICENCE") - c.response.headers.set("Content-Type", "text/plain") -}) - -root.get("/favicon.ico", c => { c.response.status = 204 }) - -// deno-lint-ignore no-explicit-any -function respondWithType(mimeType: string, f: () => string): (c: RouterContext) => void { - return c => { - c.response.headers.set("Content-Type", mimeType) - c.response.body = f() - } -} - -let bundle_code = "" -root.get("/bundle.js", respondWithType("application/javascript", () => bundle_code)) - -root.use(api.routes()) - -function mountFilesystem(r: Router, route: string, path: string) { - r.get(route + "/(.*)", async (context) => { - console.log(context.request.url.pathname.substring(route.length)); - await send(context, context.request.url.pathname.substring(route.length), { root: Deno.cwd() + path }); - }) -} - -mountFilesystem(root, "/_style", "/public/style") -mountFilesystem(root, "/_rnnoise", "/public/rnnoise") - -app.use(root.routes()) -app.use(root.allowedMethods()) - -app.addEventListener("listen", ({ hostname, port, secure }) => { - console.log(`listening on: ${secure ? "https://" : "http://"}${hostname}:${port}`); -}); - -app.listen({ - hostname: Deno.env.get("HOSTNAME") ?? "127.0.0.1", - port: parseInt(Deno.env.get("PORT") ?? "8080") -}); - - -let refresh_needed = false -let refresh_pending = false -async function refresh() { - refresh_needed = true - if (refresh_pending) return - refresh_needed = false - refresh_pending = true - - try { - const { code } = await bundle("source/client/index.ts", { compilerOptions: { checkJs: false } }) - bundle_code = code - } catch (e) { console.error(e) } - - refresh_pending = false - if (refresh_needed) refresh() -} - -refresh() -for await (const event of Deno.watchFs("source/client")) { - if (event.kind == "modify" || event.kind == "create") { - refresh() - } -} diff --git a/source/server/room.ts b/source/server/room.ts deleted file mode 100644 index d9c1a25..0000000 --- a/source/server/room.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Router } from "https://deno.land/x/oak@v10.4.0/router.ts"; -import { PacketC, PacketS } from "../packets.ts"; - -export const api = new Router() - -type Room = Map -const rooms: Map = new Map() - -function send_packet(ws: WebSocket, packet: PacketC) { - ws.send(JSON.stringify(packet)) -} - -api.get("/signaling/:id", c => { - const ws = c.upgrade() - - const room_name = c.params.id - const room: Room = rooms.get(c.params.id) ?? new Map() - let initialized = false - let user_name = "" - - const init = (n: string) => { - if (room.get(n)) return ws.close() - initialized = true - user_name = n - rooms.set(c.params.id, room) - room.forEach(uws => send_packet(uws, { sender: user_name, join: true })) - room.forEach((_, uname) => send_packet(ws, { sender: uname, join: true, stable: true })) - room.set(user_name, ws) - console.log(`[${room_name}] ${user_name} joined`) - } - ws.onclose = () => { - room.delete(user_name) - room.forEach(uws => send_packet(uws, { sender: user_name, leave: true })) - if (room.size == 0) rooms.delete(room_name) - console.log(`[${room_name}] ${user_name} left`) - } - ws.onmessage = ev => { - const message = ev.data.toString() - if (!initialized) return init(message) - let in_packet: PacketS; - try { in_packet = JSON.parse(message) } - catch (_e) { return } - - if (JSON.stringify(in_packet) == "{}") return // drop ping - - console.log(`[${room_name}] ${user_name} -> ${in_packet.receiver ?? "*"}: ${message.substr(0, 100)}`) - const out_packet: PacketC = { sender: user_name, data: in_packet } - - if (in_packet.receiver) { - const rws = room.get(in_packet.receiver) - if (rws) send_packet(rws, out_packet) - } else { - room.forEach((uws, uname) => { - if (uname != user_name) send_packet(uws, out_packet) - }) - } - } -}) -- cgit v1.2.3-70-g09d2