diff --git a/.gitignore b/.gitignore index bf565453a5..929bda7250 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,4 @@ website/.cache website/test/stubs-layout-cache/_includes/*.js apps/android/app/release apps/multiplatform/.kotlin/sessions + diff --git a/bots/api/EVENTS.md b/bots/api/EVENTS.md index 84f59e7405..d7405ef846 100644 --- a/bots/api/EVENTS.md +++ b/bots/api/EVENTS.md @@ -62,6 +62,11 @@ This file is generated automatically. - [SentGroupInvitation](#sentgroupinvitation) - [GroupLinkConnecting](#grouplinkconnecting) +[Network connection events](#network-connection-events) +- [HostConnected](#hostconnected) +- [HostDisconnected](#hostdisconnected) +- [SubscriptionStatus](#subscriptionstatus) + [Error events](#error-events) - [MessageError](#messageerror) - [ChatError](#chaterror) @@ -685,6 +690,48 @@ Sent when bot joins group via another user link. --- +## Network connection events + + + + +### HostConnected + +Messaging or file server connected + +**Record type**: +- type: "hostConnected" +- protocol: string +- transportHost: string + +--- + + +### HostDisconnected + +Messaging or file server disconnected + +**Record type**: +- type: "hostDisconnected" +- protocol: string +- transportHost: string + +--- + + +### SubscriptionStatus + +Messaging subscription status changed + +**Record type**: +- type: "subscriptionStatus" +- server: string +- subscriptionStatus: [SubscriptionStatus](./TYPES.md#subscriptionstatus) +- connections: [string] + +--- + + ## Error events Bots may log these events for debugging. There will be many error events - this does NOT indicate a malfunction - e.g., they may happen because of bad network connectivity, or because messages may be delivered to deleted chats for a short period of time (they will be ignored). diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index 1aaa291204..cded13f3a2 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -153,6 +153,7 @@ This file is generated automatically. - [SndGroupEvent](#sndgroupevent) - [SrvError](#srverror) - [StoreError](#storeerror) +- [SubscriptionStatus](#subscriptionstatus) - [SwitchPhase](#switchphase) - [TimedMessagesGroupPreference](#timedmessagesgrouppreference) - [TimedMessagesPreference](#timedmessagespreference) @@ -3593,6 +3594,26 @@ WorkItemError: - errContext: string +--- + +## SubscriptionStatus + +**Discriminated union type**: + +Active: +- type: "active" + +Pending: +- type: "pending" + +Removed: +- type: "removed" +- subError: string + +NoSub: +- type: "noSub" + + --- ## SwitchPhase diff --git a/bots/src/API/Docs/Events.hs b/bots/src/API/Docs/Events.hs index ecb8a5ba04..130ea89846 100644 --- a/bots/src/API/Docs/Events.hs +++ b/bots/src/API/Docs/Events.hs @@ -136,6 +136,14 @@ chatEventsDocsData = ], [] ), + ( "Network connection events", + "", + [ ("CEvtHostConnected", "Messaging or file server connected"), + ("CEvtHostDisconnected", "Messaging or file server disconnected"), + ("CEvtSubscriptionStatus", "Messaging subscription status changed") + ], + [] + ), ( "Error events", "Bots may log these events for debugging. \ \There will be many error events - this does NOT indicate a malfunction - \ @@ -178,9 +186,6 @@ undocumentedEvents = "CEvtCustomChatEvent", "CEvtGroupMemberRatchetSync", "CEvtGroupMemberSwitch", - "CEvtHostConnected", - "CEvtHostDisconnected", - "CEvtSubscriptionStatus", "CEvtNewRemoteHost", "CEvtNoMemberContactCreating", "CEvtNtfMessage", diff --git a/bots/src/API/Docs/Types.hs b/bots/src/API/Docs/Types.hs index 73e427fa03..73ad90e91b 100644 --- a/bots/src/API/Docs/Types.hs +++ b/bots/src/API/Docs/Types.hs @@ -176,6 +176,7 @@ ciQuoteType = updateRecord (RecordTypeInfo name fields) = RecordTypeInfo name $ map optChatDir fields in st {recordTypes = map updateRecord records} -- need to map even though there is one constructor in this type +-- type info, JSON encoding, constructor prefix, removed constructors, string encoding for commands, description chatTypesDocsData :: [(SumTypeInfo, SumTypeJsonEncoding, String, [ConsName], Expr, Text)] chatTypesDocsData = [ ((sti @(Chat 'CTDirect)) {typeName = "AChat"}, STRecord, "", [], "", ""), @@ -332,6 +333,7 @@ chatTypesDocsData = (sti @SndGroupEvent, STUnion, "SGE", [], "", ""), (sti @SrvError, STUnion, "SrvErr", [], "", ""), (sti @StoreError, STUnion, "SE", [], "", ""), + (sti @SubscriptionStatus, STUnion, "SS", [], "", ""), (sti @SwitchPhase, STEnum, "SP", [], "", ""), (sti @TimedMessagesGroupPreference, STRecord, "", [], "", ""), (sti @TimedMessagesPreference, STRecord, "", [], "", ""), @@ -522,6 +524,7 @@ deriving instance Generic SndFileTransfer deriving instance Generic SndGroupEvent deriving instance Generic SrvError deriving instance Generic StoreError +deriving instance Generic SubscriptionStatus deriving instance Generic SwitchPhase deriving instance Generic TimedMessagesGroupPreference deriving instance Generic TimedMessagesPreference diff --git a/bots/src/API/TypeInfo.hs b/bots/src/API/TypeInfo.hs index df43374ffa..a70de72d01 100644 --- a/bots/src/API/TypeInfo.hs +++ b/bots/src/API/TypeInfo.hs @@ -194,6 +194,7 @@ toTypeInfo tr = primitiveToLower st@(ST t ps) = let t' = fstToLower t in if t' `elem` primitiveTypes then ST t' ps else st stringTypes = [ "AConnectionLink", + "AProtocolType", "AgentConnId", "AgentInvId", "AgentRcvFileId", @@ -212,6 +213,7 @@ toTypeInfo tr = "ProtocolServer", "SbKey", "SharedMsgId", + "TransportHost", "UIColor", "UserPwd", "XContactId" diff --git a/packages/simplex-chat-client/types/typescript/README.md b/packages/simplex-chat-client/types/typescript/README.md index ad13a5d76e..e30cf0d0c3 100644 --- a/packages/simplex-chat-client/types/typescript/README.md +++ b/packages/simplex-chat-client/types/typescript/README.md @@ -2,7 +2,7 @@ This TypeScript library provides auto-generated types for bots API: commands and responses, events and all types they use. -It is used in [simplex-chat](https://www.npmjs.com/package/simplex-chat) library that uses WebSockets interface of SimpleX Chat CLI. +It is used in [simplex-chat](https://www.npmjs.com/package/simplex-chat) Node.js library. [API reference](https://github.com/simplex-chat/simplex-chat/tree/stable/bots). diff --git a/packages/simplex-chat-client/types/typescript/package.json b/packages/simplex-chat-client/types/typescript/package.json index c24b93fbed..a135b286c2 100644 --- a/packages/simplex-chat-client/types/typescript/package.json +++ b/packages/simplex-chat-client/types/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@simplex-chat/types", - "version": "0.2.0", + "version": "0.3.0", "description": "TypeScript types for SimpleX Chat bot libraries", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -35,7 +35,7 @@ "bugs": { "url": "https://github.com/simplex-chat/simplex-chat/issues" }, - "homepage": "https://github.com/simplex-chat/simplex-chat#readme", + "homepage": "https://github.com/simplex-chat/simplex-chat/tree/stable/packages/simplex-chat-client/types/typescript#readme", "dependencies": { "typescript": "^5.9.2" } diff --git a/packages/simplex-chat-client/types/typescript/src/events.ts b/packages/simplex-chat-client/types/typescript/src/events.ts index d7a0419bbe..cb6ba85c8b 100644 --- a/packages/simplex-chat-client/types/typescript/src/events.ts +++ b/packages/simplex-chat-client/types/typescript/src/events.ts @@ -46,6 +46,9 @@ export type ChatEvent = | CEvt.JoinedGroupMemberConnecting | CEvt.SentGroupInvitation | CEvt.GroupLinkConnecting + | CEvt.HostConnected + | CEvt.HostDisconnected + | CEvt.SubscriptionStatus | CEvt.MessageError | CEvt.ChatError | CEvt.ChatErrors @@ -94,6 +97,9 @@ export namespace CEvt { | "joinedGroupMemberConnecting" | "sentGroupInvitation" | "groupLinkConnecting" + | "hostConnected" + | "hostDisconnected" + | "subscriptionStatus" | "messageError" | "chatError" | "chatErrors" @@ -411,6 +417,25 @@ export namespace CEvt { hostMember: T.GroupMember } + export interface HostConnected extends Interface { + type: "hostConnected" + protocol: string + transportHost: string + } + + export interface HostDisconnected extends Interface { + type: "hostDisconnected" + protocol: string + transportHost: string + } + + export interface SubscriptionStatus extends Interface { + type: "subscriptionStatus" + server: string + subscriptionStatus: T.SubscriptionStatus + connections: string[] + } + export interface MessageError extends Interface { type: "messageError" user: T.User diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index bca0179a91..c4371082cc 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -4299,6 +4299,37 @@ export namespace StoreError { } } +export type SubscriptionStatus = + | SubscriptionStatus.Active + | SubscriptionStatus.Pending + | SubscriptionStatus.Removed + | SubscriptionStatus.NoSub + +export namespace SubscriptionStatus { + export type Tag = "active" | "pending" | "removed" | "noSub" + + interface Interface { + type: Tag + } + + export interface Active extends Interface { + type: "active" + } + + export interface Pending extends Interface { + type: "pending" + } + + export interface Removed extends Interface { + type: "removed" + subError: string + } + + export interface NoSub extends Interface { + type: "noSub" + } +} + export enum SwitchPhase { Started = "started", Confirmed = "confirmed", diff --git a/packages/simplex-chat-client/typescript/README.md b/packages/simplex-chat-client/typescript/README.md index c1756dc82c..a67e822de2 100644 --- a/packages/simplex-chat-client/typescript/README.md +++ b/packages/simplex-chat-client/typescript/README.md @@ -1,4 +1,8 @@ -# SimpleX Chat JavaScript client +# SimpleX Chat JavaScript WebRTC client + +**THIS PACKAGE IS DEPRECATED** + +Use [SimpleX Chat Node.js library](https://github.com/simplex-chat/simplex-chat/tree/stable/packages/simplex-chat-nodejs#readme) instead of this package. This is a TypeScript library that defines WebSocket API client for [SimpleX Chat terminal CLI](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/CLI.md) that should be run as a WebSockets server on any port: @@ -24,7 +28,7 @@ Please share your use cases and implementations. ## Quick start ``` -npm i simplex-chat +npm i @simplex-chat/webrtc-client@6.5.0-beta.3 npm run build ``` diff --git a/packages/simplex-chat-client/typescript/package.json b/packages/simplex-chat-client/typescript/package.json index b721dbcce2..c9d6165336 100644 --- a/packages/simplex-chat-client/typescript/package.json +++ b/packages/simplex-chat-client/typescript/package.json @@ -1,6 +1,6 @@ { - "name": "simplex-chat", - "version": "0.3.0", + "name": "@simplex-chat/webrtc-client", + "version": "6.5.0-beta.3", "description": "SimpleX Chat client", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -38,12 +38,12 @@ "bugs": { "url": "https://github.com/simplex-chat/simplex-chat/issues" }, - "homepage": "https://github.com/simplex-chat/simplex-chat/packages/simplex-chat-client/typescript#readme", + "homepage": "https://github.com/simplex-chat/simplex-chat/tree/stable/packages/simplex-chat-client/typescript#readme", "dependencies": { + "@simplex-chat/types": "^0.3.0", "isomorphic-ws": "^4.0.1" }, "devDependencies": { - "@simplex-chat/types": "^0.1.0", "@types/jest": "^27.5.1", "@types/node": "^18.11.18", "@typescript-eslint/eslint-plugin": "^5.23.0", diff --git a/packages/simplex-chat-client/typescript/tests/client.test.ts b/packages/simplex-chat-client/typescript/tests/client.test.ts index 9cd0a365f1..1c5c4d4baa 100644 --- a/packages/simplex-chat-client/typescript/tests/client.test.ts +++ b/packages/simplex-chat-client/typescript/tests/client.test.ts @@ -24,7 +24,7 @@ describe.skip("ChatClient (expects SimpleX Chat server with a user, without cont const r2 = await c.msgQ.dequeue() assert.strictEqual(r1.type, "contactConnecting") assert.strictEqual(r2.type, "contactConnected") - const contact1 = (r1 as CEvt.ContactConnected).contact + const contact1 = (r1 as CEvt.ContactConnecting).contact // const contact2 = (r2 as C.CRContactConnected).contact const r3 = await c.apiSendTextMessage(T.ChatType.Direct, contact1.contactId, "hello") assert(r3[0].chatItem.content.type === "sndMsgContent" && r3[0].chatItem.content.msgContent.text === "hello") diff --git a/packages/simplex-chat-nodejs/.gitignore b/packages/simplex-chat-nodejs/.gitignore new file mode 100644 index 0000000000..322e38bfda --- /dev/null +++ b/packages/simplex-chat-nodejs/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +package-lock.json +.vscode +build/ +libs/ +dist/ +coverage/ +tmp/ diff --git a/packages/simplex-chat-nodejs/.npmignore b/packages/simplex-chat-nodejs/.npmignore new file mode 100644 index 0000000000..26fdd85dff --- /dev/null +++ b/packages/simplex-chat-nodejs/.npmignore @@ -0,0 +1,3 @@ +libs/ +build/ +node_modules/ diff --git a/packages/simplex-chat-nodejs/LICENSE b/packages/simplex-chat-nodejs/LICENSE new file mode 100644 index 0000000000..0ad25db4bd --- /dev/null +++ b/packages/simplex-chat-nodejs/LICENSE @@ -0,0 +1,661 @@ + 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 +. diff --git a/packages/simplex-chat-nodejs/README.md b/packages/simplex-chat-nodejs/README.md new file mode 100644 index 0000000000..982b8d0553 --- /dev/null +++ b/packages/simplex-chat-nodejs/README.md @@ -0,0 +1,90 @@ +# SimpleX Chat Node.js library + +This library replaced now deprecated [SimpleX Chat WebRTC TypeScript client](https://www.npmjs.com/package/@simplex-chat/webrtc-client). + +## Use cases + +- chat bots: you can implement any logic of connecting with and communicating with SimpleX Chat users. Using chat groups a chat bot can connect SimpleX Chat users with each other. +- control of the equipment: e.g. servers or home automation. SimpleX Chat provides secure and authorised connections, so this is more secure than using rest APIs. +- any scenarios of scripted message sending. +- chat and chat-based interfaces. + +Please share your use cases and implementations. + +## Quick start: a simple bot + +``` +npm i simplex-chat@6.5.0-beta.4.2 +``` + +Simple bot that replies with squares of numbers you send to it: + +```javascript +(async () => { + const {bot} = await import("simplex-chat") + // if you are running from this GitHub repo: + // const {bot} = await import("../dist/index.js") + const [chat, _user, _address] = await bot.run({ + profile: {displayName: "Squaring bot example", fullName: ""}, + dbOpts: {dbFilePrefix: "./squaring_bot", dbKey: ""}, + options: { + addressSettings: {welcomeMessage: "If you send me a number, I will calculate its square."}, + }, + onMessage: async (ci, content) => { + const n = +content.text + const reply = typeof n === "number" && !isNaN(n) + ? `${n} * ${n} = ${n * n}` + : `this is not a number` + await chat.apiSendTextReply(ci, reply) + } + }) +})() +``` + +If you installed this package as dependency, you can run this example with: + +```sh +node ./node_modules/simplex-chat/examples/squaring-bot-readme.js` +``` + +If you cloned this repository, you can: + +``` +cd ./packages/simplex-chat-nodejs +npm install +npm run build +node ./examples/squaring-bot-readme.js +``` + +There is an example with more options in [./examples/squaring-bot.ts](./examples/squaring-bot.ts). + +You can run it with: `npx ts-node ./examples/squaring-bot.ts` + +## Documentation + +The library docs are [here](./docs/README.md). + +Library provides these modules: +- [bot](./docs/Namespace.bot.md): a simple declarative API to run a chat-bot with a single function call. It automates creating and updating of the bot profile, address and bot commands shown in the app UI. +- [api](./docs/Namespace.api.md): an API to send chat commands and receive chat events to/from chat core. You need to use it in bot event handlers, and for any other use cases. +- [core](./docs/Namespace.core.md): a low level API to the core library - the same that is used in desktop clients. You are unlikely to ever need to use this module directly. +- [util](./docs/Namespace.util.md): useful functions for chat events and types. + + +This library uses [@simplex-chat/types](https://www.npmjs.com/package/@simplex-chat/types) package with auto-generated [bot API types](../../bots/api/README.md). + +## Supported chat functions + +Library provides types and functions to: + +- create and change user profile (although, in most cases you can do it manually, via SimpleX Chat terminal app). +- create and accept invitations or connect with the contacts. +- create and manage long-term user address, accepting connection requests automatically. +- send, receive, delete and update messages, and add message reactions. +- create, join and manage group. +- send and receive files. +- etc. + +## License + +[AGPL v3](./LICENSE) diff --git a/packages/simplex-chat-nodejs/binding.gyp b/packages/simplex-chat-nodejs/binding.gyp new file mode 100644 index 0000000000..addb293f5b --- /dev/null +++ b/packages/simplex-chat-nodejs/binding.gyp @@ -0,0 +1,35 @@ +{ + "targets": [ + { + "target_name": "simplex", + "sources": [ "cpp/simplex.cc" ], + "include_dirs": [ + " +#include +#include +#include +#include +#include "simplex.h" + +namespace simplex { + +using namespace Napi; + +void haskell_init() { + int argc = 6; + const char *argv[] = { + "simplex", + "+RTS", // requires `hs_init_with_rtsopts` + "-A64m", // chunk size for new allocations + "-H64m", // initial heap size + "-xn", // non-moving GC + "--install-signal-handlers=no", + nullptr}; + char **pargv = const_cast(argv); + hs_init_with_rtsopts(&argc, &pargv); +} + +class ResultAsyncWorker : public AsyncWorker { + public: + using ExecuteFn = std::function; + using ResultProcessor = std::function; + + ResultAsyncWorker(Function& callback, ExecuteFn execute_fn, ResultProcessor result_processor = nullptr) + : AsyncWorker(callback), execute_fn_(std::move(execute_fn)), result_processor_(std::move(result_processor)) {} + + void Execute() override { + execute_fn_(this); + } + + void OnOK() override { + HandleScope scope(Env()); + if (result_processor_) { + result_processor_(this, Env()); + } else { + Callback().Call({Env().Null(), String::New(Env(), result_)}); + } + } + + void OnError(const Error& e) override { + HandleScope scope(Env()); + Callback().Call({e.Value(), Env().Undefined()}); + } + + void SetResult(std::string result) { + result_ = std::move(result); + } + + void SetWorkerError(const std::string& msg) { + SetError(msg); + } + + const std::string& GetStringResult() const { + return result_; + } + + void SetCtrl(uintptr_t ctrl) { + ctrl_ = ctrl; + } + + uintptr_t GetCtrl() const { + return ctrl_; + } + + protected: + std::string result_; + uintptr_t ctrl_ = 0; + + private: + ExecuteFn execute_fn_; + ResultProcessor result_processor_; +}; + +class BinaryAsyncWorker : public AsyncWorker { + public: + using ExecuteFn = std::function; + + BinaryAsyncWorker(Function& callback, ExecuteFn execute_fn) + : AsyncWorker(callback), execute_fn_(std::move(execute_fn)) {} + + void Execute() override { + execute_fn_(this); + } + + void OnOK() override { + HandleScope scope(Env()); + if (original_buf == nullptr || binary_len == 0) { + Callback().Call({Env().Null(), Env().Undefined()}); + return; + } + char* data_ptr = original_buf + 5; + auto finalizer = [](Napi::Env env, char* finalize_data, char* orig) { + free(orig); + }; + Napi::Buffer buffer = Napi::Buffer::New(Env(), data_ptr, binary_len, finalizer, original_buf); + Callback().Call({Env().Null(), buffer}); + } + + void OnError(const Error& e) override { + HandleScope scope(Env()); + Callback().Call({e.Value(), Env().Undefined()}); + } + + void SetWorkerError(const std::string& msg) { + SetError(msg); + } + + char* original_buf = nullptr; + size_t binary_len = 0; + + private: + ExecuteFn execute_fn_; +}; + +// Helper for converting chat_ctrl pointer to BigInt +Napi::BigInt ToChatCtrlBigInt(Napi::Env env, uintptr_t ctrl) { + return Napi::BigInt::New(env, static_cast(ctrl)); +} + +// Helper for converting BigInt to chat_ctrl pointer +chat_ctrl FromChatCtrlBigInt(const Napi::Value& value) { + Napi::Env env = value.Env(); + if (!value.IsBigInt()) { + Napi::TypeError::New(env, "Expected BigInt for ctrl").ThrowAsJavaScriptException(); + return nullptr; + } + Napi::BigInt big = value.As(); + bool lossless; + uint64_t val = big.Uint64Value(&lossless); + if (!lossless) { + Napi::TypeError::New(env, "BigInt too large for ctrl").ThrowAsJavaScriptException(); + return nullptr; + } + return reinterpret_cast(val); +} + +// Helper for handling common C result patterns (no empty check) +void HandleCResult(ResultAsyncWorker* worker, char* c_res, const std::string& func_name) { + if (c_res == nullptr) { + worker->SetWorkerError(func_name + " failed"); + return; + } + std::string res = c_res; + free(c_res); + worker->SetResult(res); +} + +Napi::Promise CreatePromiseAndCallback(Env env, Function& cb_out) { + Promise::Deferred deferred = Promise::Deferred::New(env); + cb_out = Function::New(env, [deferred](const CallbackInfo& args) { + if (!args[0].IsNull() && !args[0].IsUndefined()) { + deferred.Reject(args[0]); + } else { + deferred.Resolve(args[1]); + } + }); + return deferred.Promise(); +} + +// Common result processors +ResultAsyncWorker::ResultProcessor MigrateResultProcessor() { + return [](ResultAsyncWorker* worker, Napi::Env env) { + Napi::Array arr = Napi::Array::New(env, 2); + arr.Set(0u, ToChatCtrlBigInt(env, worker->GetCtrl())); + arr.Set(1u, Napi::String::New(env, worker->GetStringResult())); + worker->Callback().Call({env.Null(), arr}); + }; +} + +// Refactored functions using common patterns + +Value ChatMigrateInit(const CallbackInfo& args) { + Env env = args.Env(); + if (args.Length() < 3 || !args[0].IsString() || !args[1].IsString() || !args[2].IsString()) { + TypeError::New(env, "Expected three string arguments").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + std::string path = args[0].As().Utf8Value(); + std::string key = args[1].As().Utf8Value(); + std::string confirm = args[2].As().Utf8Value(); + + Function cb; + Promise promise = CreatePromiseAndCallback(env, cb); + + auto execute_fn = [path, key, confirm](ResultAsyncWorker* worker) { + chat_ctrl ctrl = nullptr; + char* c_res = chat_migrate_init(path.c_str(), key.c_str(), confirm.c_str(), &ctrl); + worker->SetCtrl(reinterpret_cast(ctrl)); + HandleCResult(worker, c_res, "chat_migrate_init"); + }; + + ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn), MigrateResultProcessor()); + worker->Queue(); + + return promise; +} + +Value ChatCloseStore(const CallbackInfo& args) { + Env env = args.Env(); + if (args.Length() < 1 || !args[0].IsBigInt()) { + TypeError::New(env, "Expected bigint (ctrl)").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + chat_ctrl ctrl = FromChatCtrlBigInt(args[0]); + + Function cb; + Promise promise = CreatePromiseAndCallback(env, cb); + + auto execute_fn = [ctrl](ResultAsyncWorker* worker) { + char* c_res = chat_close_store(ctrl); + HandleCResult(worker, c_res, "chat_close_store"); + }; + + ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn)); + worker->Queue(); + + return promise; +} + +Value ChatSendCmd(const CallbackInfo& args) { + Env env = args.Env(); + if (args.Length() < 2 || !args[0].IsBigInt() || !args[1].IsString()) { + TypeError::New(env, "Expected bigint (ctrl) and string (cmd)").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + chat_ctrl ctrl = FromChatCtrlBigInt(args[0]); + std::string cmd = args[1].As().Utf8Value(); + + Function cb; + Promise promise = CreatePromiseAndCallback(env, cb); + + auto execute_fn = [ctrl, cmd](ResultAsyncWorker* worker) { + char* c_res = chat_send_cmd(ctrl, cmd.c_str()); + HandleCResult(worker, c_res, "chat_send_cmd"); + }; + + ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn)); + worker->Queue(); + + return promise; +} + +Value ChatRecvMsgWait(const CallbackInfo& args) { + Env env = args.Env(); + if (args.Length() < 2 || !args[0].IsBigInt() || !args[1].IsNumber()) { + TypeError::New(env, "Expected bigint (ctrl), number (wait)").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + chat_ctrl ctrl = FromChatCtrlBigInt(args[0]); + int wait = static_cast(args[1].As().Int32Value()); + + Function cb; + Promise promise = CreatePromiseAndCallback(env, cb); + + auto execute_fn = [ctrl, wait](ResultAsyncWorker* worker) { + char* c_res = chat_recv_msg_wait(ctrl, wait); + HandleCResult(worker, c_res, "chat_recv_msg_wait"); + }; + + ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn)); + worker->Queue(); + + return promise; +} + +Value ChatWriteFile(const CallbackInfo& args) { + Env env = args.Env(); + if (args.Length() < 3 || !args[0].IsBigInt() || !args[1].IsString() || !args[2].IsArrayBuffer()) { + TypeError::New(env, "Expected bigint (ctrl), string (path), ArrayBuffer").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + chat_ctrl ctrl = FromChatCtrlBigInt(args[0]); + std::string path = args[1].As().Utf8Value(); + ArrayBuffer ab = args[2].As(); + char* data = static_cast(ab.Data()); + size_t len = ab.ByteLength(); + + Function cb; + Promise promise = CreatePromiseAndCallback(env, cb); + + auto execute_fn = [ctrl, path, ab, data, len](ResultAsyncWorker* worker) { + (void)ab; // to keep ArrayBuffer alive + char* c_res = chat_write_file(ctrl, path.c_str(), data, static_cast(len)); + HandleCResult(worker, c_res, "chat_write_file"); + }; + + ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn)); + worker->Queue(); + + return promise; +} + +Value ChatReadFile(const CallbackInfo& args) { + Env env = args.Env(); + if (args.Length() < 3 || !args[0].IsString() || !args[1].IsString() || !args[2].IsString()) { + TypeError::New(env, "Expected three strings (path, key, nonce)").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + std::string path = args[0].As().Utf8Value(); + std::string key = args[1].As().Utf8Value(); + std::string nonce = args[2].As().Utf8Value(); + + Function cb; + Promise promise = CreatePromiseAndCallback(env, cb); + + auto execute_fn = [path, key, nonce](BinaryAsyncWorker* worker) { + char* buf = chat_read_file(path.c_str(), key.c_str(), nonce.c_str()); + if (buf == nullptr) { + worker->SetWorkerError("chat_read_file failed"); + return; + } + char status = buf[0]; + if (status == 1) { + std::string err = buf + 1; + free(buf); + worker->SetWorkerError(err); + return; + } else if (status == 0) { + uint32_t len = *(uint32_t*)(buf + 1); + worker->original_buf = buf; + worker->binary_len = len; + } else { + free(buf); + worker->SetWorkerError("Unexpected status from chat_read_file"); + return; + } + }; + + BinaryAsyncWorker* worker = new BinaryAsyncWorker(cb, std::move(execute_fn)); + worker->Queue(); + + return promise; +} + +Value ChatEncryptFile(const CallbackInfo& args) { + Env env = args.Env(); + if (args.Length() < 3 || !args[0].IsBigInt() || !args[1].IsString() || !args[2].IsString()) { + TypeError::New(env, "Expected bigint (ctrl), two strings (fromPath, toPath)").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + chat_ctrl ctrl = FromChatCtrlBigInt(args[0]); + std::string fromPath = args[1].As().Utf8Value(); + std::string toPath = args[2].As().Utf8Value(); + + Function cb; + Promise promise = CreatePromiseAndCallback(env, cb); + + auto execute_fn = [ctrl, fromPath, toPath](ResultAsyncWorker* worker) { + char* c_res = chat_encrypt_file(ctrl, fromPath.c_str(), toPath.c_str()); + HandleCResult(worker, c_res, "chat_encrypt_file"); + }; + + ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn)); + worker->Queue(); + + return promise; +} + +Value ChatDecryptFile(const CallbackInfo& args) { + Env env = args.Env(); + if (args.Length() < 4 || !args[0].IsString() || !args[1].IsString() || !args[2].IsString() || !args[3].IsString()) { + TypeError::New(env, "Expected four strings (fromPath, key, nonce, toPath)").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + std::string fromPath = args[0].As().Utf8Value(); + std::string key = args[1].As().Utf8Value(); + std::string nonce = args[2].As().Utf8Value(); + std::string toPath = args[3].As().Utf8Value(); + + Function cb; + Promise promise = CreatePromiseAndCallback(env, cb); + + auto execute_fn = [fromPath, key, nonce, toPath](ResultAsyncWorker* worker) { + char* c_res = chat_decrypt_file(fromPath.c_str(), key.c_str(), nonce.c_str(), toPath.c_str()); + HandleCResult(worker, c_res, "chat_decrypt_file"); + }; + + ResultAsyncWorker* worker = new ResultAsyncWorker(cb, std::move(execute_fn)); + worker->Queue(); + + return promise; +} + +Object Init(Env env, Object exports) { + haskell_init(); + exports.Set("chat_migrate_init", Function::New(env, ChatMigrateInit)); + exports.Set("chat_close_store", Function::New(env, ChatCloseStore)); + exports.Set("chat_send_cmd", Function::New(env, ChatSendCmd)); + exports.Set("chat_recv_msg_wait", Function::New(env, ChatRecvMsgWait)); + exports.Set("chat_write_file", Function::New(env, ChatWriteFile)); + exports.Set("chat_read_file", Function::New(env, ChatReadFile)); + exports.Set("chat_encrypt_file", Function::New(env, ChatEncryptFile)); + exports.Set("chat_decrypt_file", Function::New(env, ChatDecryptFile)); + return exports; +} + +NODE_API_MODULE(simplex, Init) + +} diff --git a/packages/simplex-chat-nodejs/cpp/simplex.h b/packages/simplex-chat-nodejs/cpp/simplex.h new file mode 100644 index 0000000000..8e579626ed --- /dev/null +++ b/packages/simplex-chat-nodejs/cpp/simplex.h @@ -0,0 +1,46 @@ +// +// simplex.h +// SimpleX +// +// Created by Evgeny on 30/05/2022. +// Copyright © 2022 SimpleX Chat. All rights reserved. +// + +#ifndef SimpleX_h +#define SimpleX_h + +extern "C" void hs_init(int argc, char **argv[]); +extern "C" void hs_init_with_rtsopts(int * argc, char **argv[]); + +typedef long* chat_ctrl; + +// the last parameter is used to return the pointer to chat controller +extern "C" char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl); +extern "C" char *chat_close_store(chat_ctrl ctrl); +extern "C" char *chat_reopen_store(chat_ctrl ctrl); +extern "C" char *chat_send_cmd(chat_ctrl ctrl, const char *cmd); +extern "C" char *chat_recv_msg_wait(chat_ctrl ctrl, const int wait); +extern "C" char *chat_parse_markdown(const char *str); +extern "C" char *chat_parse_server(const char *str); +extern "C" char *chat_password_hash(const char *pwd, const char *salt); +extern "C" char *chat_valid_name(const char *name); +extern "C" int chat_json_length(const char *str); +extern "C" char *chat_encrypt_media(chat_ctrl ctrl, const char *key, const char *frame, const int len); +extern "C" char *chat_decrypt_media(const char *key, const char *frame, const int len); + +// chat_write_file returns null-terminated string with JSON of WriteFileResult +extern "C" char *chat_write_file(chat_ctrl ctrl, const char *path, const char *data, const int len); + +// chat_read_file returns a buffer with: +// result status (1 byte), then if +// status == 0 (success): buffer length (uint32, 4 bytes), buffer of specified length. +// status == 1 (error): null-terminated error message string. +extern "C" char *chat_read_file(const char *path, const char *key, const char *nonce); + +// chat_encrypt_file returns null-terminated string with JSON of WriteFileResult +extern "C" char *chat_encrypt_file(chat_ctrl ctrl, const char *fromPath, const char *toPath); + +// chat_decrypt_file returns null-terminated string with the error message +extern "C" char *chat_decrypt_file(const char *fromPath, const char *key, const char *nonce, const char *toPath); + +#endif /* simplex_h */ \ No newline at end of file diff --git a/packages/simplex-chat-nodejs/docs/Namespace.api.md b/packages/simplex-chat-nodejs/docs/Namespace.api.md new file mode 100644 index 0000000000..a1b3d2ca5a --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/Namespace.api.md @@ -0,0 +1,32 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / api + +# api + +An API to send chat commands and receive chat events to/from chat core. +You need to use it in bot event handlers, and for any other use cases. + +## Enumerations + +- [ConnReqType](api.Enumeration.ConnReqType.md) + +## Classes + +- [ChatApi](api.Class.ChatApi.md) +- [ChatCommandError](api.Class.ChatCommandError.md) + +## Interfaces + +- [BotAddressSettings](api.Interface.BotAddressSettings.md) + +## Type Aliases + +- [EventSubscriberFunc](api.TypeAlias.EventSubscriberFunc.md) +- [EventSubscribers](api.TypeAlias.EventSubscribers.md) + +## Variables + +- [defaultBotAddressSettings](api.Variable.defaultBotAddressSettings.md) diff --git a/packages/simplex-chat-nodejs/docs/Namespace.bot.md b/packages/simplex-chat-nodejs/docs/Namespace.bot.md new file mode 100644 index 0000000000..447b0b6f68 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/Namespace.bot.md @@ -0,0 +1,20 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / bot + +# bot + +A simple declarative API to run a chat-bot with a single function call. +It automates creating and updating of the bot profile, address and bot commands shown in the app UI. + +## Interfaces + +- [BotConfig](bot.Interface.BotConfig.md) +- [BotDbOpts](bot.Interface.BotDbOpts.md) +- [BotOptions](bot.Interface.BotOptions.md) + +## Functions + +- [run](bot.Function.run.md) diff --git a/packages/simplex-chat-nodejs/docs/Namespace.core.md b/packages/simplex-chat-nodejs/docs/Namespace.core.md new file mode 100644 index 0000000000..82b0d9ffba --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/Namespace.core.md @@ -0,0 +1,48 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / core + +# core + +A low level API to the core library - the same that is used in desktop clients. +You are unlikely to ever need to use this module directly. + +## Namespaces + +- [DBMigrationError](core.Namespace.DBMigrationError.md) +- [MigrationError](core.Namespace.MigrationError.md) +- [MTRError](core.Namespace.MTRError.md) + +## Enumerations + +- [MigrationConfirmation](core.Enumeration.MigrationConfirmation.md) + +## Classes + +- [ChatAPIError](core.Class.ChatAPIError.md) +- [ChatInitError](core.Class.ChatInitError.md) + +## Interfaces + +- [APIResult](core.Interface.APIResult.md) +- [CryptoArgs](core.Interface.CryptoArgs.md) +- [UpMigration](core.Interface.UpMigration.md) + +## Type Aliases + +- [DBMigrationError](core.TypeAlias.DBMigrationError.md) +- [MigrationError](core.TypeAlias.MigrationError.md) +- [MTRError](core.TypeAlias.MTRError.md) + +## Functions + +- [chatCloseStore](core.Function.chatCloseStore.md) +- [chatDecryptFile](core.Function.chatDecryptFile.md) +- [chatEncryptFile](core.Function.chatEncryptFile.md) +- [chatMigrateInit](core.Function.chatMigrateInit.md) +- [chatReadFile](core.Function.chatReadFile.md) +- [chatRecvMsgWait](core.Function.chatRecvMsgWait.md) +- [chatSendCmd](core.Function.chatSendCmd.md) +- [chatWriteFile](core.Function.chatWriteFile.md) diff --git a/packages/simplex-chat-nodejs/docs/Namespace.util.md b/packages/simplex-chat-nodejs/docs/Namespace.util.md new file mode 100644 index 0000000000..a6e7d9c7f3 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/Namespace.util.md @@ -0,0 +1,25 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / util + +# util + +Useful functions for chat events and types. + +## Interfaces + +- [BotCommand](util.Interface.BotCommand.md) + +## Functions + +- [botAddressSettings](util.Function.botAddressSettings.md) +- [chatInfoName](util.Function.chatInfoName.md) +- [chatInfoRef](util.Function.chatInfoRef.md) +- [ciBotCommand](util.Function.ciBotCommand.md) +- [ciContentText](util.Function.ciContentText.md) +- [contactAddressStr](util.Function.contactAddressStr.md) +- [fromLocalProfile](util.Function.fromLocalProfile.md) +- [reactionText](util.Function.reactionText.md) +- [senderName](util.Function.senderName.md) diff --git a/packages/simplex-chat-nodejs/docs/README.md b/packages/simplex-chat-nodejs/docs/README.md new file mode 100644 index 0000000000..f954730ed6 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/README.md @@ -0,0 +1,12 @@ +**simplex-chat** + +*** + +# simplex-chat + +## Namespaces + +- [api](Namespace.api.md) +- [bot](Namespace.bot.md) +- [core](Namespace.core.md) +- [util](Namespace.util.md) diff --git a/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md b/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md new file mode 100644 index 0000000000..6812740aa2 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md @@ -0,0 +1,1602 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [api](Namespace.api.md) / ChatApi + +# Class: ChatApi + +Defined in: [src/api.ts:62](../src/api.ts#L62) + +Main API class for interacting with the chat core library. + +## Properties + +### ctrl\_ + +> `protected` **ctrl\_**: `bigint` \| `undefined` + +Defined in: [src/api.ts:68](../src/api.ts#L68) + +## Accessors + +### ctrl + +#### Get Signature + +> **get** **ctrl**(): `bigint` + +Defined in: [src/api.ts:295](../src/api.ts#L295) + +Chat controller reference + +##### Returns + +`bigint` + +*** + +### initialized + +#### Get Signature + +> **get** **initialized**(): `boolean` + +Defined in: [src/api.ts:281](../src/api.ts#L281) + +Chat controller is initialized + +##### Returns + +`boolean` + +*** + +### started + +#### Get Signature + +> **get** **started**(): `boolean` + +Defined in: [src/api.ts:288](../src/api.ts#L288) + +Chat controller is started + +##### Returns + +`boolean` + +## Methods + +### apiAcceptContactRequest() + +> **apiAcceptContactRequest**(`contactReqId`): `Promise`\<`Contact`\> + +Defined in: [src/api.ts:697](../src/api.ts#L697) + +Accept contact request. +Network usage: interactive. + +#### Parameters + +##### contactReqId + +`number` + +#### Returns + +`Promise`\<`Contact`\> + +*** + +### apiAcceptMember() + +> **apiAcceptMember**(`groupId`, `groupMemberId`, `memberRole`): `Promise`\<`GroupMember`\> + +Defined in: [src/api.ts:517](../src/api.ts#L517) + +Accept group member. Requires Admin role. +Network usage: background. + +#### Parameters + +##### groupId + +`number` + +##### groupMemberId + +`number` + +##### memberRole + +`GroupMemberRole` + +#### Returns + +`Promise`\<`GroupMember`\> + +*** + +### apiAddMember() + +> **apiAddMember**(`groupId`, `contactId`, `memberRole`): `Promise`\<`GroupMember`\> + +Defined in: [src/api.ts:497](../src/api.ts#L497) + +Add contact to group. Requires bot to have Admin role. +Network usage: interactive. + +#### Parameters + +##### groupId + +`number` + +##### contactId + +`number` + +##### memberRole + +`GroupMemberRole` + +#### Returns + +`Promise`\<`GroupMember`\> + +*** + +### apiBlockMembersForAll() + +> **apiBlockMembersForAll**(`groupId`, `groupMemberIds`, `blocked`): `Promise`\<`void`\> + +Defined in: [src/api.ts:537](../src/api.ts#L537) + +Block members. Requires Moderator role. +Network usage: background. + +#### Parameters + +##### groupId + +`number` + +##### groupMemberIds + +`number`[] + +##### blocked + +`boolean` + +#### Returns + +`Promise`\<`void`\> + +*** + +### apiCancelFile() + +> **apiCancelFile**(`fileId`): `Promise`\<`void`\> + +Defined in: [src/api.ts:487](../src/api.ts#L487) + +Cancel file. +Network usage: background. + +#### Parameters + +##### fileId + +`number` + +#### Returns + +`Promise`\<`void`\> + +*** + +### apiChatItemReaction() + +> **apiChatItemReaction**(`chatType`, `chatId`, `chatItemId`, `add`, `reaction`): `Promise`\<`ChatItemDeletion`[]\> + +Defined in: [src/api.ts:461](../src/api.ts#L461) + +Add/remove message reaction. +Network usage: background. + +#### Parameters + +##### chatType + +`ChatType` + +##### chatId + +`number` + +##### chatItemId + +`number` + +##### add + +`boolean` + +##### reaction + +`MsgReaction` + +#### Returns + +`Promise`\<`ChatItemDeletion`[]\> + +*** + +### apiConnect() + +> **apiConnect**(`userId`, `incognito`, `preparedLink?`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\> + +Defined in: [src/api.ts:666](../src/api.ts#L666) + +Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link +Network usage: interactive. + +#### Parameters + +##### userId + +`number` + +##### incognito + +`boolean` + +##### preparedLink? + +`CreatedConnLink` + +#### Returns + +`Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\> + +*** + +### apiConnectActiveUser() + +> **apiConnectActiveUser**(`connLink`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\> + +Defined in: [src/api.ts:675](../src/api.ts#L675) + +Connect via SimpleX link as string in the active user profile. +Network usage: interactive. + +#### Parameters + +##### connLink + +`string` + +#### Returns + +`Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\> + +*** + +### apiConnectPlan() + +> **apiConnectPlan**(`userId`, `connectionLink`): `Promise`\<\[`ConnectionPlan`, `CreatedConnLink`\]\> + +Defined in: [src/api.ts:656](../src/api.ts#L656) + +Determine SimpleX link type and if the bot is already connected via this link. +Network usage: interactive. + +#### Parameters + +##### userId + +`number` + +##### connectionLink + +`string` + +#### Returns + +`Promise`\<\[`ConnectionPlan`, `CreatedConnLink`\]\> + +*** + +### apiCreateActiveUser() + +> **apiCreateActiveUser**(`profile?`): `Promise`\<`User`\> + +Defined in: [src/api.ts:774](../src/api.ts#L774) + +Create new user profile +Network usage: no. + +#### Parameters + +##### profile? + +`Profile` + +#### Returns + +`Promise`\<`User`\> + +*** + +### apiCreateGroupLink() + +> **apiCreateGroupLink**(`groupId`, `memberRole`): `Promise`\<`string`\> + +Defined in: [src/api.ts:597](../src/api.ts#L597) + +Create group link. +Network usage: interactive. + +#### Parameters + +##### groupId + +`number` + +##### memberRole + +`GroupMemberRole` + +#### Returns + +`Promise`\<`string`\> + +*** + +### apiCreateLink() + +> **apiCreateLink**(`userId`): `Promise`\<`string`\> + +Defined in: [src/api.ts:643](../src/api.ts#L643) + +Create 1-time invitation link. +Network usage: interactive. + +#### Parameters + +##### userId + +`number` + +#### Returns + +`Promise`\<`string`\> + +*** + +### apiCreateUserAddress() + +> **apiCreateUserAddress**(`userId`): `Promise`\<`CreatedConnLink`\> + +Defined in: [src/api.ts:312](../src/api.ts#L312) + +Create bot address. +Network usage: interactive. + +#### Parameters + +##### userId + +`number` + +#### Returns + +`Promise`\<`CreatedConnLink`\> + +*** + +### apiDeleteChat() + +> **apiDeleteChat**(`chatType`, `chatId`, `deleteMode`): `Promise`\<`void`\> + +Defined in: [src/api.ts:737](../src/api.ts#L737) + +Delete chat. +Network usage: background. + +#### Parameters + +##### chatType + +`ChatType` + +##### chatId + +`number` + +##### deleteMode + +`ChatDeleteMode` = `...` + +#### Returns + +`Promise`\<`void`\> + +*** + +### apiDeleteChatItems() + +> **apiDeleteChatItems**(`chatType`, `chatId`, `chatItemIds`, `deleteMode`): `Promise`\<`ChatItemDeletion`[]\> + +Defined in: [src/api.ts:436](../src/api.ts#L436) + +Delete message. +Network usage: background. + +#### Parameters + +##### chatType + +`ChatType` + +##### chatId + +`number` + +##### chatItemIds + +`number`[] + +##### deleteMode + +`CIDeleteMode` + +#### Returns + +`Promise`\<`ChatItemDeletion`[]\> + +*** + +### apiDeleteGroupLink() + +> **apiDeleteGroupLink**(`groupId`): `Promise`\<`void`\> + +Defined in: [src/api.ts:619](../src/api.ts#L619) + +Delete group link. +Network usage: background. + +#### Parameters + +##### groupId + +`number` + +#### Returns + +`Promise`\<`void`\> + +*** + +### apiDeleteMemberChatItem() + +> **apiDeleteMemberChatItem**(`groupId`, `chatItemIds`): `Promise`\<`ChatItemDeletion`[]\> + +Defined in: [src/api.ts:451](../src/api.ts#L451) + +Moderate message. Requires Moderator role (and higher than message author's). +Network usage: background. + +#### Parameters + +##### groupId + +`number` + +##### chatItemIds + +`number`[] + +#### Returns + +`Promise`\<`ChatItemDeletion`[]\> + +*** + +### apiDeleteUser() + +> **apiDeleteUser**(`userId`, `delSMPQueues`, `viewPwd?`): `Promise`\<`void`\> + +Defined in: [src/api.ts:804](../src/api.ts#L804) + +Delete user profile. +Network usage: background. + +#### Parameters + +##### userId + +`number` + +##### delSMPQueues + +`boolean` + +##### viewPwd? + +`string` + +#### Returns + +`Promise`\<`void`\> + +*** + +### apiDeleteUserAddress() + +> **apiDeleteUserAddress**(`userId`): `Promise`\<`void`\> + +Defined in: [src/api.ts:322](../src/api.ts#L322) + +Deletes a user address. +Network usage: background. + +#### Parameters + +##### userId + +`number` + +#### Returns + +`Promise`\<`void`\> + +*** + +### apiGetActiveUser() + +> **apiGetActiveUser**(): `Promise`\<`User` \| `undefined`\> + +Defined in: [src/api.ts:754](../src/api.ts#L754) + +Get active user profile +Network usage: no. + +#### Returns + +`Promise`\<`User` \| `undefined`\> + +*** + +### apiGetGroupLink() + +> **apiGetGroupLink**(`groupId`): `Promise`\<`GroupLink`\> + +Defined in: [src/api.ts:628](../src/api.ts#L628) + +Get group link. +Network usage: no. + +#### Parameters + +##### groupId + +`number` + +#### Returns + +`Promise`\<`GroupLink`\> + +*** + +### apiGetGroupLinkStr() + +> **apiGetGroupLinkStr**(`groupId`): `Promise`\<`string`\> + +Defined in: [src/api.ts:634](../src/api.ts#L634) + +#### Parameters + +##### groupId + +`number` + +#### Returns + +`Promise`\<`string`\> + +*** + +### apiGetUserAddress() + +> **apiGetUserAddress**(`userId`): `Promise`\<`UserContactLink` \| `undefined`\> + +Defined in: [src/api.ts:332](../src/api.ts#L332) + +Get bot address and settings. +Network usage: no. + +#### Parameters + +##### userId + +`number` + +#### Returns + +`Promise`\<`UserContactLink` \| `undefined`\> + +*** + +### apiJoinGroup() + +> **apiJoinGroup**(`groupId`): `Promise`\<`GroupInfo`\> + +Defined in: [src/api.ts:507](../src/api.ts#L507) + +Join group. +Network usage: interactive. + +#### Parameters + +##### groupId + +`number` + +#### Returns + +`Promise`\<`GroupInfo`\> + +*** + +### apiLeaveGroup() + +> **apiLeaveGroup**(`groupId`): `Promise`\<`GroupInfo`\> + +Defined in: [src/api.ts:557](../src/api.ts#L557) + +Leave group. +Network usage: background. + +#### Parameters + +##### groupId + +`number` + +#### Returns + +`Promise`\<`GroupInfo`\> + +*** + +### apiListContacts() + +> **apiListContacts**(`userId`): `Promise`\<`Contact`[]\> + +Defined in: [src/api.ts:717](../src/api.ts#L717) + +Get contacts. +Network usage: no. + +#### Parameters + +##### userId + +`number` + +#### Returns + +`Promise`\<`Contact`[]\> + +*** + +### apiListGroups() + +> **apiListGroups**(`userId`, `contactId?`, `search?`): `Promise`\<`GroupInfo`[]\> + +Defined in: [src/api.ts:727](../src/api.ts#L727) + +Get groups. +Network usage: no. + +#### Parameters + +##### userId + +`number` + +##### contactId? + +`number` + +##### search? + +`string` + +#### Returns + +`Promise`\<`GroupInfo`[]\> + +*** + +### apiListMembers() + +> **apiListMembers**(`groupId`): `Promise`\<`GroupMember`[]\> + +Defined in: [src/api.ts:567](../src/api.ts#L567) + +Get group members. +Network usage: no. + +#### Parameters + +##### groupId + +`number` + +#### Returns + +`Promise`\<`GroupMember`[]\> + +*** + +### apiListUsers() + +> **apiListUsers**(): `Promise`\<`UserInfo`[]\> + +Defined in: [src/api.ts:784](../src/api.ts#L784) + +Get all user profiles +Network usage: no. + +#### Returns + +`Promise`\<`UserInfo`[]\> + +*** + +### apiNewGroup() + +> **apiNewGroup**(`userId`, `groupProfile`): `Promise`\<`GroupInfo`\> + +Defined in: [src/api.ts:577](../src/api.ts#L577) + +Create group. +Network usage: no. + +#### Parameters + +##### userId + +`number` + +##### groupProfile + +`GroupProfile` + +#### Returns + +`Promise`\<`GroupInfo`\> + +*** + +### apiReceiveFile() + +> **apiReceiveFile**(`fileId`): `Promise`\<`AChatItem`\> + +Defined in: [src/api.ts:477](../src/api.ts#L477) + +Receive file. +Network usage: no. + +#### Parameters + +##### fileId + +`number` + +#### Returns + +`Promise`\<`AChatItem`\> + +*** + +### apiRejectContactRequest() + +> **apiRejectContactRequest**(`contactReqId`): `Promise`\<`void`\> + +Defined in: [src/api.ts:707](../src/api.ts#L707) + +Reject contact request. The user who sent the request is **not notified**. +Network usage: no. + +#### Parameters + +##### contactReqId + +`number` + +#### Returns + +`Promise`\<`void`\> + +*** + +### apiRemoveMembers() + +> **apiRemoveMembers**(`groupId`, `memberIds`, `withMessages`): `Promise`\<`GroupMember`[]\> + +Defined in: [src/api.ts:547](../src/api.ts#L547) + +Remove members. Requires Admin role. +Network usage: background. + +#### Parameters + +##### groupId + +`number` + +##### memberIds + +`number`[] + +##### withMessages + +`boolean` = `false` + +#### Returns + +`Promise`\<`GroupMember`[]\> + +*** + +### apiSendMessages() + +> **apiSendMessages**(`chat`, `messages`, `liveMessage`): `Promise`\<`AChatItem`[]\> + +Defined in: [src/api.ts:381](../src/api.ts#L381) + +Send messages. +Network usage: background. + +#### Parameters + +##### chat + +`ChatInfo` | `ChatRef` | \[`ChatType`, `number`\] + +##### messages + +`ComposedMessage`[] + +##### liveMessage + +`boolean` = `false` + +#### Returns + +`Promise`\<`AChatItem`[]\> + +*** + +### apiSendTextMessage() + +> **apiSendTextMessage**(`chat`, `text`, `inReplyTo?`): `Promise`\<`AChatItem`[]\> + +Defined in: [src/api.ts:403](../src/api.ts#L403) + +Send text message. +Network usage: background. + +#### Parameters + +##### chat + +`ChatInfo` | `ChatRef` | \[`ChatType`, `number`\] + +##### text + +`string` + +##### inReplyTo? + +`number` + +#### Returns + +`Promise`\<`AChatItem`[]\> + +*** + +### apiSendTextReply() + +> **apiSendTextReply**(`chatItem`, `text`): `Promise`\<`AChatItem`[]\> + +Defined in: [src/api.ts:411](../src/api.ts#L411) + +Send text message in reply to received message. +Network usage: background. + +#### Parameters + +##### chatItem + +`AChatItem` + +##### text + +`string` + +#### Returns + +`Promise`\<`AChatItem`[]\> + +*** + +### apiSetActiveUser() + +> **apiSetActiveUser**(`userId`, `viewPwd?`): `Promise`\<`User`\> + +Defined in: [src/api.ts:794](../src/api.ts#L794) + +Set active user profile +Network usage: no. + +#### Parameters + +##### userId + +`number` + +##### viewPwd? + +`string` + +#### Returns + +`Promise`\<`User`\> + +*** + +### apiSetAddressSettings() + +> **apiSetAddressSettings**(`userId`, `__namedParameters`): `Promise`\<`void`\> + +Defined in: [src/api.ts:364](../src/api.ts#L364) + +Set bot address settings. +Network usage: interactive. + +#### Parameters + +##### userId + +`number` + +##### \_\_namedParameters + +[`BotAddressSettings`](api.Interface.BotAddressSettings.md) + +#### Returns + +`Promise`\<`void`\> + +*** + +### apiSetContactPrefs() + +> **apiSetContactPrefs**(`contactId`, `preferences`): `Promise`\<`void`\> + +Defined in: [src/api.ts:830](../src/api.ts#L830) + +Configure chat preference overrides for the contact. +Network usage: background. + +#### Parameters + +##### contactId + +`number` + +##### preferences + +`Preferences` + +#### Returns + +`Promise`\<`void`\> + +*** + +### apiSetGroupLinkMemberRole() + +> **apiSetGroupLinkMemberRole**(`groupId`, `memberRole`): `Promise`\<`void`\> + +Defined in: [src/api.ts:610](../src/api.ts#L610) + +Set member role for group link. +Network usage: no. + +#### Parameters + +##### groupId + +`number` + +##### memberRole + +`GroupMemberRole` + +#### Returns + +`Promise`\<`void`\> + +*** + +### apiSetMembersRole() + +> **apiSetMembersRole**(`groupId`, `groupMemberIds`, `memberRole`): `Promise`\<`void`\> + +Defined in: [src/api.ts:527](../src/api.ts#L527) + +Set members role. Requires Admin role. +Network usage: background. + +#### Parameters + +##### groupId + +`number` + +##### groupMemberIds + +`number`[] + +##### memberRole + +`GroupMemberRole` + +#### Returns + +`Promise`\<`void`\> + +*** + +### apiSetProfileAddress() + +> **apiSetProfileAddress**(`userId`, `enable`): `Promise`\<`UserProfileUpdateSummary`\> + +Defined in: [src/api.ts:350](../src/api.ts#L350) + +Add address to bot profile. +Network usage: interactive. + +#### Parameters + +##### userId + +`number` + +##### enable + +`boolean` + +#### Returns + +`Promise`\<`UserProfileUpdateSummary`\> + +*** + +### apiUpdateChatItem() + +> **apiUpdateChatItem**(`chatType`, `chatId`, `chatItemId`, `msgContent`, `liveMessage`): `Promise`\<`ChatItem`\> + +Defined in: [src/api.ts:419](../src/api.ts#L419) + +Update message. +Network usage: background. + +#### Parameters + +##### chatType + +`ChatType` + +##### chatId + +`number` + +##### chatItemId + +`number` + +##### msgContent + +`MsgContent` + +##### liveMessage + +`false` + +#### Returns + +`Promise`\<`ChatItem`\> + +*** + +### apiUpdateGroupProfile() + +> **apiUpdateGroupProfile**(`groupId`, `groupProfile`): `Promise`\<`GroupInfo`\> + +Defined in: [src/api.ts:587](../src/api.ts#L587) + +Update group profile. +Network usage: background. + +#### Parameters + +##### groupId + +`number` + +##### groupProfile + +`GroupProfile` + +#### Returns + +`Promise`\<`GroupInfo`\> + +*** + +### apiUpdateProfile() + +> **apiUpdateProfile**(`userId`, `profile`): `Promise`\<`UserProfileUpdateSummary` \| `undefined`\> + +Defined in: [src/api.ts:814](../src/api.ts#L814) + +Update user profile. +Network usage: background. + +#### Parameters + +##### userId + +`number` + +##### profile + +`Profile` + +#### Returns + +`Promise`\<`UserProfileUpdateSummary` \| `undefined`\> + +*** + +### close() + +> **close**(): `Promise`\<`void`\> + +Defined in: [src/api.ts:114](../src/api.ts#L114) + +Close chat database. +Usually doesn't need to be called in chat bots. + +#### Returns + +`Promise`\<`void`\> + +*** + +### off() + +> **off**\<`K`\>(`event`, `subscriber`): `void` + +Defined in: [src/api.ts:253](../src/api.ts#L253) + +Unsubscribe all or a specific handler from a specific event. + +#### Type Parameters + +##### K + +`K` *extends* `Tag` + +#### Parameters + +##### event + +`K` + +The event type to unsubscribe from. + +##### subscriber + +An optional subscriber function for the event. + +[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`K`\> | `undefined` + +#### Returns + +`void` + +*** + +### offAny() + +> **offAny**(`receiver`): `void` + +Defined in: [src/api.ts:269](../src/api.ts#L269) + +Unsubscribe all or a specific handler from any events. + +#### Parameters + +##### receiver + +An optional subscriber function for the event. + +[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`Tag`\> | `undefined` + +#### Returns + +`void` + +*** + +### on() + +#### Call Signature + +> **on**\<`K`\>(`subscribers`): `void` + +Defined in: [src/api.ts:163](../src/api.ts#L163) + +Subscribe multiple event handlers at once. + +##### Type Parameters + +###### K + +`K` *extends* `Tag` + +##### Parameters + +###### subscribers + +[`EventSubscribers`](api.TypeAlias.EventSubscribers.md) + +An object mapping event types (CEvt.Tag) to their subscriber functions. + +##### Returns + +`void` + +##### Throws + +If the same function is subscribed to event. + +#### Call Signature + +> **on**\<`K`\>(`event`, `subscriber`): `void` + +Defined in: [src/api.ts:171](../src/api.ts#L171) + +Subscribe a handler to a specific event. + +##### Type Parameters + +###### K + +`K` *extends* `Tag` + +##### Parameters + +###### event + +`K` + +The event type to subscribe to. + +###### subscriber + +[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`K`\> + +The subscriber function for the event. + +##### Returns + +`void` + +##### Throws + +If the same function is subscribed to event. + +*** + +### onAny() + +> **onAny**(`receiver`): `void` + +Defined in: [src/api.ts:194](../src/api.ts#L194) + +Subscribe a handler to any event. + +#### Parameters + +##### receiver + +[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`Tag`\> + +The receiver function for any event. + +#### Returns + +`void` + +#### Throws + +If the same function is subscribed to event. + +*** + +### once() + +> **once**\<`K`\>(`event`, `subscriber`): `void` + +Defined in: [src/api.ts:205](../src/api.ts#L205) + +Subscribe a handler to a specific event to be delivered one time. + +#### Type Parameters + +##### K + +`K` *extends* `Tag` + +#### Parameters + +##### event + +`K` + +The event type to subscribe to. + +##### subscriber + +[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`K`\> + +The subscriber function for the event. + +#### Returns + +`void` + +#### Throws + +If the same function is subscribed to event. + +*** + +### recvChatEvent() + +> **recvChatEvent**(`wait`): `Promise`\<`ChatEvent` \| `undefined`\> + +Defined in: [src/api.ts:304](../src/api.ts#L304) + +#### Parameters + +##### wait + +`number` = `5_000_000` + +#### Returns + +`Promise`\<`ChatEvent` \| `undefined`\> + +*** + +### sendChatCmd() + +> **sendChatCmd**(`cmd`): `Promise`\<`ChatResponse`\> + +Defined in: [src/api.ts:300](../src/api.ts#L300) + +#### Parameters + +##### cmd + +`string` + +#### Returns + +`Promise`\<`ChatResponse`\> + +*** + +### startChat() + +> **startChat**(): `Promise`\<`void`\> + +Defined in: [src/api.ts:88](../src/api.ts#L88) + +Start chat controller. Must be called with the existing user profile. + +#### Returns + +`Promise`\<`void`\> + +*** + +### stopChat() + +> **stopChat**(): `Promise`\<`void`\> + +Defined in: [src/api.ts:102](../src/api.ts#L102) + +Stop chat controller. +Must be called before closing the database. +Usually doesn't need to be called in chat bots. + +#### Returns + +`Promise`\<`void`\> + +*** + +### wait() + +#### Call Signature + +> **wait**\<`K`\>(`event`): `Promise`\<`ChatEvent` & \{ `type`: `K`; \}\> + +Defined in: [src/api.ts:213](../src/api.ts#L213) + +Waits for specific event, with an optional predicate. +Returns `undefined` on timeout if specified. + +##### Type Parameters + +###### K + +`K` *extends* `Tag` + +##### Parameters + +###### event + +`K` + +##### Returns + +`Promise`\<`ChatEvent` & \{ `type`: `K`; \}\> + +#### Call Signature + +> **wait**\<`K`\>(`event`, `predicate`): `Promise`\<`ChatEvent` & \{ `type`: `K`; \}\> + +Defined in: [src/api.ts:214](../src/api.ts#L214) + +Waits for specific event, with an optional predicate. +Returns `undefined` on timeout if specified. + +##### Type Parameters + +###### K + +`K` *extends* `Tag` + +##### Parameters + +###### event + +`K` + +###### predicate + +(`event`) => `boolean` | `undefined` + +##### Returns + +`Promise`\<`ChatEvent` & \{ `type`: `K`; \}\> + +#### Call Signature + +> **wait**\<`K`\>(`event`, `timeout`): `Promise`\ + +Defined in: [src/api.ts:215](../src/api.ts#L215) + +Waits for specific event, with an optional predicate. +Returns `undefined` on timeout if specified. + +##### Type Parameters + +###### K + +`K` *extends* `Tag` + +##### Parameters + +###### event + +`K` + +###### timeout + +`number` + +##### Returns + +`Promise`\ + +#### Call Signature + +> **wait**\<`K`\>(`event`, `predicate`, `timeout`): `Promise`\ + +Defined in: [src/api.ts:216](../src/api.ts#L216) + +Waits for specific event, with an optional predicate. +Returns `undefined` on timeout if specified. + +##### Type Parameters + +###### K + +`K` *extends* `Tag` + +##### Parameters + +###### event + +`K` + +###### predicate + +(`event`) => `boolean` | `undefined` + +###### timeout + +`number` + +##### Returns + +`Promise`\ + +*** + +### init() + +> `static` **init**(`dbFilePrefix`, `dbKey?`, `confirm?`): `Promise`\<`ChatApi`\> + +Defined in: [src/api.ts:76](../src/api.ts#L76) + +Initializes the ChatApi. + +#### Parameters + +##### dbFilePrefix + +`string` + +File prefix for the database files. + +##### dbKey? + +`string` = `""` + +Database encryption key. + +##### confirm? + +[`MigrationConfirmation`](core.Enumeration.MigrationConfirmation.md) = `core.MigrationConfirmation.YesUp` + +Migration confirmation mode. + +#### Returns + +`Promise`\<`ChatApi`\> diff --git a/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md b/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md new file mode 100644 index 0000000000..a4955cb3d9 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md @@ -0,0 +1,205 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [api](Namespace.api.md) / ChatCommandError + +# Class: ChatCommandError + +Defined in: [src/api.ts:5](../src/api.ts#L5) + +## Extends + +- `Error` + +## Constructors + +### Constructor + +> **new ChatCommandError**(`message`, `response`): `ChatCommandError` + +Defined in: [src/api.ts:6](../src/api.ts#L6) + +#### Parameters + +##### message + +`string` + +##### response + +`ChatResponse` + +#### Returns + +`ChatCommandError` + +#### Overrides + +`Error.constructor` + +## Properties + +### message + +> **message**: `string` + +Defined in: [src/api.ts:6](../src/api.ts#L6) + +#### Inherited from + +`Error.message` + +*** + +### name + +> **name**: `string` + +Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typescript/lib/lib.es5.d.ts#L1076) + +#### Inherited from + +`Error.name` + +*** + +### response + +> **response**: `ChatResponse` + +Defined in: [src/api.ts:6](../src/api.ts#L6) + +*** + +### stack? + +> `optional` **stack**: `string` + +Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078) + +#### Inherited from + +`Error.stack` + +*** + +### stackTraceLimit + +> `static` **stackTraceLimit**: `number` + +Defined in: [node\_modules/@types/node/globals.d.ts:67](../node_modules/@types/node/globals.d.ts#L67) + +The `Error.stackTraceLimit` property specifies the number of stack frames +collected by a stack trace (whether generated by `new Error().stack` or +`Error.captureStackTrace(obj)`). + +The default value is `10` but may be set to any valid JavaScript number. Changes +will affect any stack trace captured _after_ the value has been changed. + +If set to a non-number value, or set to a negative number, stack traces will +not capture any frames. + +#### Inherited from + +`Error.stackTraceLimit` + +## Methods + +### captureStackTrace() + +> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void` + +Defined in: [node\_modules/@types/node/globals.d.ts:51](../node_modules/@types/node/globals.d.ts#L51) + +Creates a `.stack` property on `targetObject`, which when accessed returns +a string representing the location in the code at which +`Error.captureStackTrace()` was called. + +```js +const myObject = {}; +Error.captureStackTrace(myObject); +myObject.stack; // Similar to `new Error().stack` +``` + +The first line of the trace will be prefixed with +`${myObject.name}: ${myObject.message}`. + +The optional `constructorOpt` argument accepts a function. If given, all frames +above `constructorOpt`, including `constructorOpt`, will be omitted from the +generated stack trace. + +The `constructorOpt` argument is useful for hiding implementation +details of error generation from the user. For instance: + +```js +function a() { + b(); +} + +function b() { + c(); +} + +function c() { + // Create an error without stack trace to avoid calculating the stack trace twice. + const { stackTraceLimit } = Error; + Error.stackTraceLimit = 0; + const error = new Error(); + Error.stackTraceLimit = stackTraceLimit; + + // Capture the stack trace above function b + Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace + throw error; +} + +a(); +``` + +#### Parameters + +##### targetObject + +`object` + +##### constructorOpt? + +`Function` + +#### Returns + +`void` + +#### Inherited from + +`Error.captureStackTrace` + +*** + +### prepareStackTrace() + +> `static` **prepareStackTrace**(`err`, `stackTraces`): `any` + +Defined in: [node\_modules/@types/node/globals.d.ts:55](../node_modules/@types/node/globals.d.ts#L55) + +#### Parameters + +##### err + +`Error` + +##### stackTraces + +`CallSite`[] + +#### Returns + +`any` + +#### See + +https://v8.dev/docs/stack-trace-api#customizing-stack-traces + +#### Inherited from + +`Error.prepareStackTrace` diff --git a/packages/simplex-chat-nodejs/docs/api.Enumeration.ConnReqType.md b/packages/simplex-chat-nodejs/docs/api.Enumeration.ConnReqType.md new file mode 100644 index 0000000000..dcabb85b67 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/api.Enumeration.ConnReqType.md @@ -0,0 +1,27 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [api](Namespace.api.md) / ConnReqType + +# Enumeration: ConnReqType + +Defined in: [src/api.ts:15](../src/api.ts#L15) + +Connection request types. + +## Enumeration Members + +### Contact + +> **Contact**: `"contact"` + +Defined in: [src/api.ts:17](../src/api.ts#L17) + +*** + +### Invitation + +> **Invitation**: `"invitation"` + +Defined in: [src/api.ts:16](../src/api.ts#L16) diff --git a/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md b/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md new file mode 100644 index 0000000000..efb4a75e81 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md @@ -0,0 +1,60 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [api](Namespace.api.md) / BotAddressSettings + +# Interface: BotAddressSettings + +Defined in: [src/api.ts:23](../src/api.ts#L23) + +Bot address settings. + +## Properties + +### autoAccept? + +> `optional` **autoAccept**: `boolean` + +Defined in: [src/api.ts:28](../src/api.ts#L28) + +Automatically accept contact requests. + +#### Default + +```ts +true +``` + +*** + +### businessAddress? + +> `optional` **businessAddress**: `boolean` + +Defined in: [src/api.ts:41](../src/api.ts#L41) + +Business contact address. +For all requests business chats will be created where other participants can be added. + +#### Default + +```ts +false +``` + +*** + +### welcomeMessage? + +> `optional` **welcomeMessage**: `string` \| `MsgContent` + +Defined in: [src/api.ts:34](../src/api.ts#L34) + +Optional welcome message to show before connection to the users. + +#### Default + +```ts +undefined (no welcome message) +``` diff --git a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md new file mode 100644 index 0000000000..6197befc8a --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md @@ -0,0 +1,27 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [api](Namespace.api.md) / EventSubscriberFunc + +# Type Alias: EventSubscriberFunc()\ + +> **EventSubscriberFunc**\<`K`\> = (`event`) => `void` \| `Promise`\<`void`\> + +Defined in: [src/api.ts:50](../src/api.ts#L50) + +## Type Parameters + +### K + +`K` *extends* `CEvt.Tag` + +## Parameters + +### event + +`ChatEvent` & \{ `type`: `K`; \} + +## Returns + +`void` \| `Promise`\<`void`\> diff --git a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscribers.md b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscribers.md new file mode 100644 index 0000000000..3b63d11bd4 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscribers.md @@ -0,0 +1,11 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [api](Namespace.api.md) / EventSubscribers + +# Type Alias: EventSubscribers + +> **EventSubscribers** = `{ [K in CEvt.Tag]?: EventSubscriberFunc }` + +Defined in: [src/api.ts:52](../src/api.ts#L52) diff --git a/packages/simplex-chat-nodejs/docs/api.Variable.defaultBotAddressSettings.md b/packages/simplex-chat-nodejs/docs/api.Variable.defaultBotAddressSettings.md new file mode 100644 index 0000000000..f076ee0fad --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/api.Variable.defaultBotAddressSettings.md @@ -0,0 +1,11 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [api](Namespace.api.md) / defaultBotAddressSettings + +# Variable: defaultBotAddressSettings + +> `const` **defaultBotAddressSettings**: [`BotAddressSettings`](api.Interface.BotAddressSettings.md) + +Defined in: [src/api.ts:44](../src/api.ts#L44) diff --git a/packages/simplex-chat-nodejs/docs/bot.Function.run.md b/packages/simplex-chat-nodejs/docs/bot.Function.run.md new file mode 100644 index 0000000000..bc31ad01a8 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/bot.Function.run.md @@ -0,0 +1,21 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [bot](Namespace.bot.md) / run + +# Function: run() + +> **run**(`__namedParameters`): `Promise`\<\[[`ChatApi`](api.Class.ChatApi.md), `User`, `UserContactLink` \| `undefined`\]\> + +Defined in: [src/bot.ts:49](../src/bot.ts#L49) + +## Parameters + +### \_\_namedParameters + +[`BotConfig`](bot.Interface.BotConfig.md) + +## Returns + +`Promise`\<\[[`ChatApi`](api.Class.ChatApi.md), `User`, `UserContactLink` \| `undefined`\]\> diff --git a/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md b/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md new file mode 100644 index 0000000000..0951c5a129 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md @@ -0,0 +1,75 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [bot](Namespace.bot.md) / BotConfig + +# Interface: BotConfig + +Defined in: [src/bot.ts:37](../src/bot.ts#L37) + +## Properties + +### dbOpts + +> **dbOpts**: [`BotDbOpts`](bot.Interface.BotDbOpts.md) + +Defined in: [src/bot.ts:39](../src/bot.ts#L39) + +*** + +### events? + +> `optional` **events**: [`EventSubscribers`](api.TypeAlias.EventSubscribers.md) + +Defined in: [src/bot.ts:46](../src/bot.ts#L46) + +*** + +### onCommands? + +> `optional` **onCommands**: \{\[`key`: `string`\]: (`chatItem`, `command`) => `void` \| `Promise`\<`void`\> \| `undefined`; \} + +Defined in: [src/bot.ts:43](../src/bot.ts#L43) + +#### Index Signature + +\[`key`: `string`\]: (`chatItem`, `command`) => `void` \| `Promise`\<`void`\> \| `undefined` + +*** + +### onMessage()? + +> `optional` **onMessage**: (`chatItem`, `content`) => `void` \| `Promise`\<`void`\> + +Defined in: [src/bot.ts:41](../src/bot.ts#L41) + +#### Parameters + +##### chatItem + +`AChatItem` + +##### content + +`MsgContent` + +#### Returns + +`void` \| `Promise`\<`void`\> + +*** + +### options + +> **options**: [`BotOptions`](bot.Interface.BotOptions.md) + +Defined in: [src/bot.ts:40](../src/bot.ts#L40) + +*** + +### profile + +> **profile**: `Profile` + +Defined in: [src/bot.ts:38](../src/bot.ts#L38) diff --git a/packages/simplex-chat-nodejs/docs/bot.Interface.BotDbOpts.md b/packages/simplex-chat-nodejs/docs/bot.Interface.BotDbOpts.md new file mode 100644 index 0000000000..7a9f113f6a --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/bot.Interface.BotDbOpts.md @@ -0,0 +1,33 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [bot](Namespace.bot.md) / BotDbOpts + +# Interface: BotDbOpts + +Defined in: [src/bot.ts:7](../src/bot.ts#L7) + +## Properties + +### confirmMigrations? + +> `optional` **confirmMigrations**: [`MigrationConfirmation`](core.Enumeration.MigrationConfirmation.md) + +Defined in: [src/bot.ts:10](../src/bot.ts#L10) + +*** + +### dbFilePrefix + +> **dbFilePrefix**: `string` + +Defined in: [src/bot.ts:8](../src/bot.ts#L8) + +*** + +### dbKey? + +> `optional` **dbKey**: `string` + +Defined in: [src/bot.ts:9](../src/bot.ts#L9) diff --git a/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md b/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md new file mode 100644 index 0000000000..44d4380e5a --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md @@ -0,0 +1,81 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [bot](Namespace.bot.md) / BotOptions + +# Interface: BotOptions + +Defined in: [src/bot.ts:13](../src/bot.ts#L13) + +## Properties + +### addressSettings? + +> `optional` **addressSettings**: [`BotAddressSettings`](api.Interface.BotAddressSettings.md) + +Defined in: [src/bot.ts:17](../src/bot.ts#L17) + +*** + +### allowFiles? + +> `optional` **allowFiles**: `boolean` + +Defined in: [src/bot.ts:18](../src/bot.ts#L18) + +*** + +### commands? + +> `optional` **commands**: `ChatBotCommand`[] + +Defined in: [src/bot.ts:19](../src/bot.ts#L19) + +*** + +### createAddress? + +> `optional` **createAddress**: `boolean` + +Defined in: [src/bot.ts:14](../src/bot.ts#L14) + +*** + +### logContacts? + +> `optional` **logContacts**: `boolean` + +Defined in: [src/bot.ts:21](../src/bot.ts#L21) + +*** + +### logNetwork? + +> `optional` **logNetwork**: `boolean` + +Defined in: [src/bot.ts:22](../src/bot.ts#L22) + +*** + +### updateAddress? + +> `optional` **updateAddress**: `boolean` + +Defined in: [src/bot.ts:15](../src/bot.ts#L15) + +*** + +### updateProfile? + +> `optional` **updateProfile**: `boolean` + +Defined in: [src/bot.ts:16](../src/bot.ts#L16) + +*** + +### useBotProfile? + +> `optional` **useBotProfile**: `boolean` + +Defined in: [src/bot.ts:20](../src/bot.ts#L20) diff --git a/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md b/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md new file mode 100644 index 0000000000..c6082f2985 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md @@ -0,0 +1,205 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / ChatAPIError + +# Class: ChatAPIError + +Defined in: [src/core.ts:92](../src/core.ts#L92) + +## Extends + +- `Error` + +## Constructors + +### Constructor + +> **new ChatAPIError**(`message`, `chatError`): `ChatAPIError` + +Defined in: [src/core.ts:93](../src/core.ts#L93) + +#### Parameters + +##### message + +`string` + +##### chatError + +`ChatError` | `undefined` + +#### Returns + +`ChatAPIError` + +#### Overrides + +`Error.constructor` + +## Properties + +### chatError + +> **chatError**: `ChatError` \| `undefined` = `undefined` + +Defined in: [src/core.ts:93](../src/core.ts#L93) + +*** + +### message + +> **message**: `string` + +Defined in: [src/core.ts:93](../src/core.ts#L93) + +#### Inherited from + +`Error.message` + +*** + +### name + +> **name**: `string` + +Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typescript/lib/lib.es5.d.ts#L1076) + +#### Inherited from + +`Error.name` + +*** + +### stack? + +> `optional` **stack**: `string` + +Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078) + +#### Inherited from + +`Error.stack` + +*** + +### stackTraceLimit + +> `static` **stackTraceLimit**: `number` + +Defined in: [node\_modules/@types/node/globals.d.ts:67](../node_modules/@types/node/globals.d.ts#L67) + +The `Error.stackTraceLimit` property specifies the number of stack frames +collected by a stack trace (whether generated by `new Error().stack` or +`Error.captureStackTrace(obj)`). + +The default value is `10` but may be set to any valid JavaScript number. Changes +will affect any stack trace captured _after_ the value has been changed. + +If set to a non-number value, or set to a negative number, stack traces will +not capture any frames. + +#### Inherited from + +`Error.stackTraceLimit` + +## Methods + +### captureStackTrace() + +> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void` + +Defined in: [node\_modules/@types/node/globals.d.ts:51](../node_modules/@types/node/globals.d.ts#L51) + +Creates a `.stack` property on `targetObject`, which when accessed returns +a string representing the location in the code at which +`Error.captureStackTrace()` was called. + +```js +const myObject = {}; +Error.captureStackTrace(myObject); +myObject.stack; // Similar to `new Error().stack` +``` + +The first line of the trace will be prefixed with +`${myObject.name}: ${myObject.message}`. + +The optional `constructorOpt` argument accepts a function. If given, all frames +above `constructorOpt`, including `constructorOpt`, will be omitted from the +generated stack trace. + +The `constructorOpt` argument is useful for hiding implementation +details of error generation from the user. For instance: + +```js +function a() { + b(); +} + +function b() { + c(); +} + +function c() { + // Create an error without stack trace to avoid calculating the stack trace twice. + const { stackTraceLimit } = Error; + Error.stackTraceLimit = 0; + const error = new Error(); + Error.stackTraceLimit = stackTraceLimit; + + // Capture the stack trace above function b + Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace + throw error; +} + +a(); +``` + +#### Parameters + +##### targetObject + +`object` + +##### constructorOpt? + +`Function` + +#### Returns + +`void` + +#### Inherited from + +`Error.captureStackTrace` + +*** + +### prepareStackTrace() + +> `static` **prepareStackTrace**(`err`, `stackTraces`): `any` + +Defined in: [node\_modules/@types/node/globals.d.ts:55](../node_modules/@types/node/globals.d.ts#L55) + +#### Parameters + +##### err + +`Error` + +##### stackTraces + +`CallSite`[] + +#### Returns + +`any` + +#### See + +https://v8.dev/docs/stack-trace-api#customizing-stack-traces + +#### Inherited from + +`Error.prepareStackTrace` diff --git a/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md b/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md new file mode 100644 index 0000000000..eff5123fb0 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md @@ -0,0 +1,205 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / ChatInitError + +# Class: ChatInitError + +Defined in: [src/core.ts:116](../src/core.ts#L116) + +## Extends + +- `Error` + +## Constructors + +### Constructor + +> **new ChatInitError**(`message`, `dbMigrationError`): `ChatInitError` + +Defined in: [src/core.ts:117](../src/core.ts#L117) + +#### Parameters + +##### message + +`string` + +##### dbMigrationError + +[`DBMigrationError`](core.TypeAlias.DBMigrationError.md) + +#### Returns + +`ChatInitError` + +#### Overrides + +`Error.constructor` + +## Properties + +### dbMigrationError + +> **dbMigrationError**: [`DBMigrationError`](core.TypeAlias.DBMigrationError.md) + +Defined in: [src/core.ts:117](../src/core.ts#L117) + +*** + +### message + +> **message**: `string` + +Defined in: [src/core.ts:117](../src/core.ts#L117) + +#### Inherited from + +`Error.message` + +*** + +### name + +> **name**: `string` + +Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typescript/lib/lib.es5.d.ts#L1076) + +#### Inherited from + +`Error.name` + +*** + +### stack? + +> `optional` **stack**: `string` + +Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078) + +#### Inherited from + +`Error.stack` + +*** + +### stackTraceLimit + +> `static` **stackTraceLimit**: `number` + +Defined in: [node\_modules/@types/node/globals.d.ts:67](../node_modules/@types/node/globals.d.ts#L67) + +The `Error.stackTraceLimit` property specifies the number of stack frames +collected by a stack trace (whether generated by `new Error().stack` or +`Error.captureStackTrace(obj)`). + +The default value is `10` but may be set to any valid JavaScript number. Changes +will affect any stack trace captured _after_ the value has been changed. + +If set to a non-number value, or set to a negative number, stack traces will +not capture any frames. + +#### Inherited from + +`Error.stackTraceLimit` + +## Methods + +### captureStackTrace() + +> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void` + +Defined in: [node\_modules/@types/node/globals.d.ts:51](../node_modules/@types/node/globals.d.ts#L51) + +Creates a `.stack` property on `targetObject`, which when accessed returns +a string representing the location in the code at which +`Error.captureStackTrace()` was called. + +```js +const myObject = {}; +Error.captureStackTrace(myObject); +myObject.stack; // Similar to `new Error().stack` +``` + +The first line of the trace will be prefixed with +`${myObject.name}: ${myObject.message}`. + +The optional `constructorOpt` argument accepts a function. If given, all frames +above `constructorOpt`, including `constructorOpt`, will be omitted from the +generated stack trace. + +The `constructorOpt` argument is useful for hiding implementation +details of error generation from the user. For instance: + +```js +function a() { + b(); +} + +function b() { + c(); +} + +function c() { + // Create an error without stack trace to avoid calculating the stack trace twice. + const { stackTraceLimit } = Error; + Error.stackTraceLimit = 0; + const error = new Error(); + Error.stackTraceLimit = stackTraceLimit; + + // Capture the stack trace above function b + Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace + throw error; +} + +a(); +``` + +#### Parameters + +##### targetObject + +`object` + +##### constructorOpt? + +`Function` + +#### Returns + +`void` + +#### Inherited from + +`Error.captureStackTrace` + +*** + +### prepareStackTrace() + +> `static` **prepareStackTrace**(`err`, `stackTraces`): `any` + +Defined in: [node\_modules/@types/node/globals.d.ts:55](../node_modules/@types/node/globals.d.ts#L55) + +#### Parameters + +##### err + +`Error` + +##### stackTraces + +`CallSite`[] + +#### Returns + +`any` + +#### See + +https://v8.dev/docs/stack-trace-api#customizing-stack-traces + +#### Inherited from + +`Error.prepareStackTrace` diff --git a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorMigration.md b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorMigration.md new file mode 100644 index 0000000000..02cf84b763 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorMigration.md @@ -0,0 +1,41 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / [DBMigrationError](core.Namespace.DBMigrationError.md) / ErrorMigration + +# Interface: ErrorMigration + +Defined in: [src/core.ts:144](../src/core.ts#L144) + +## Extends + +- `Interface` + +## Properties + +### dbFile + +> **dbFile**: `string` + +Defined in: [src/core.ts:146](../src/core.ts#L146) + +*** + +### migrationError + +> **migrationError**: [`MigrationError`](core.TypeAlias.MigrationError.md) + +Defined in: [src/core.ts:147](../src/core.ts#L147) + +*** + +### type + +> **type**: `"errorMigration"` + +Defined in: [src/core.ts:145](../src/core.ts#L145) + +#### Overrides + +`Interface.type` diff --git a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorNotADatabase.md b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorNotADatabase.md new file mode 100644 index 0000000000..18e2429081 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorNotADatabase.md @@ -0,0 +1,33 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / [DBMigrationError](core.Namespace.DBMigrationError.md) / ErrorNotADatabase + +# Interface: ErrorNotADatabase + +Defined in: [src/core.ts:139](../src/core.ts#L139) + +## Extends + +- `Interface` + +## Properties + +### dbFile + +> **dbFile**: `string` + +Defined in: [src/core.ts:141](../src/core.ts#L141) + +*** + +### type + +> **type**: `"errorNotADatabase"` + +Defined in: [src/core.ts:140](../src/core.ts#L140) + +#### Overrides + +`Interface.type` diff --git a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorSQL.md b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorSQL.md new file mode 100644 index 0000000000..4d85b04197 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.ErrorSQL.md @@ -0,0 +1,41 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / [DBMigrationError](core.Namespace.DBMigrationError.md) / ErrorSQL + +# Interface: ErrorSQL + +Defined in: [src/core.ts:150](../src/core.ts#L150) + +## Extends + +- `Interface` + +## Properties + +### dbFile + +> **dbFile**: `string` + +Defined in: [src/core.ts:152](../src/core.ts#L152) + +*** + +### migrationSQLError + +> **migrationSQLError**: `string` + +Defined in: [src/core.ts:153](../src/core.ts#L153) + +*** + +### type + +> **type**: `"errorSQL"` + +Defined in: [src/core.ts:151](../src/core.ts#L151) + +#### Overrides + +`Interface.type` diff --git a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.InvalidConfirmation.md b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.InvalidConfirmation.md new file mode 100644 index 0000000000..34dea63aed --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.Interface.InvalidConfirmation.md @@ -0,0 +1,25 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / [DBMigrationError](core.Namespace.DBMigrationError.md) / InvalidConfirmation + +# Interface: InvalidConfirmation + +Defined in: [src/core.ts:135](../src/core.ts#L135) + +## Extends + +- `Interface` + +## Properties + +### type + +> **type**: `"invalidConfirmation"` + +Defined in: [src/core.ts:136](../src/core.ts#L136) + +#### Overrides + +`Interface.type` diff --git a/packages/simplex-chat-nodejs/docs/core.DBMigrationError.TypeAlias.Tag.md b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.TypeAlias.Tag.md new file mode 100644 index 0000000000..a3ef341601 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.DBMigrationError.TypeAlias.Tag.md @@ -0,0 +1,11 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / [DBMigrationError](core.Namespace.DBMigrationError.md) / Tag + +# Type Alias: Tag + +> **Tag** = `"invalidConfirmation"` \| `"errorNotADatabase"` \| `"errorMigration"` \| `"errorSQL"` + +Defined in: [src/core.ts:129](../src/core.ts#L129) diff --git a/packages/simplex-chat-nodejs/docs/core.Enumeration.MigrationConfirmation.md b/packages/simplex-chat-nodejs/docs/core.Enumeration.MigrationConfirmation.md new file mode 100644 index 0000000000..7dfd4991bf --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Enumeration.MigrationConfirmation.md @@ -0,0 +1,43 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / MigrationConfirmation + +# Enumeration: MigrationConfirmation + +Defined in: [src/core.ts:101](../src/core.ts#L101) + +Migration confirmation mode + +## Enumeration Members + +### Console + +> **Console**: `"console"` + +Defined in: [src/core.ts:104](../src/core.ts#L104) + +*** + +### Error + +> **Error**: `"error"` + +Defined in: [src/core.ts:105](../src/core.ts#L105) + +*** + +### YesUp + +> **YesUp**: `"yesUp"` + +Defined in: [src/core.ts:102](../src/core.ts#L102) + +*** + +### YesUpDown + +> **YesUpDown**: `"yesUpDown"` + +Defined in: [src/core.ts:103](../src/core.ts#L103) diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatCloseStore.md b/packages/simplex-chat-nodejs/docs/core.Function.chatCloseStore.md new file mode 100644 index 0000000000..deeb3213fd --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatCloseStore.md @@ -0,0 +1,23 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / chatCloseStore + +# Function: chatCloseStore() + +> **chatCloseStore**(`ctrl`): `Promise`\<`void`\> + +Defined in: [src/core.ts:17](../src/core.ts#L17) + +Close chat store + +## Parameters + +### ctrl + +`bigint` + +## Returns + +`Promise`\<`void`\> diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatDecryptFile.md b/packages/simplex-chat-nodejs/docs/core.Function.chatDecryptFile.md new file mode 100644 index 0000000000..434aeeaae8 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatDecryptFile.md @@ -0,0 +1,31 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / chatDecryptFile + +# Function: chatDecryptFile() + +> **chatDecryptFile**(`fromPath`, `__namedParameters`, `toPath`): `Promise`\<`void`\> + +Defined in: [src/core.ts:73](../src/core.ts#L73) + +Decrypt file + +## Parameters + +### fromPath + +`string` + +### \_\_namedParameters + +[`CryptoArgs`](core.Interface.CryptoArgs.md) + +### toPath + +`string` + +## Returns + +`Promise`\<`void`\> diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatEncryptFile.md b/packages/simplex-chat-nodejs/docs/core.Function.chatEncryptFile.md new file mode 100644 index 0000000000..6aa0ad2923 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatEncryptFile.md @@ -0,0 +1,31 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / chatEncryptFile + +# Function: chatEncryptFile() + +> **chatEncryptFile**(`ctrl`, `fromPath`, `toPath`): `Promise`\<[`CryptoArgs`](core.Interface.CryptoArgs.md)\> + +Defined in: [src/core.ts:65](../src/core.ts#L65) + +Encrypt file + +## Parameters + +### ctrl + +`bigint` + +### fromPath + +`string` + +### toPath + +`string` + +## Returns + +`Promise`\<[`CryptoArgs`](core.Interface.CryptoArgs.md)\> diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatMigrateInit.md b/packages/simplex-chat-nodejs/docs/core.Function.chatMigrateInit.md new file mode 100644 index 0000000000..9116026f56 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatMigrateInit.md @@ -0,0 +1,31 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / chatMigrateInit + +# Function: chatMigrateInit() + +> **chatMigrateInit**(`dbPath`, `dbKey`, `confirm`): `Promise`\<`bigint`\> + +Defined in: [src/core.ts:7](../src/core.ts#L7) + +Initialize chat controller + +## Parameters + +### dbPath + +`string` + +### dbKey + +`string` + +### confirm + +[`MigrationConfirmation`](core.Enumeration.MigrationConfirmation.md) + +## Returns + +`Promise`\<`bigint`\> diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatReadFile.md b/packages/simplex-chat-nodejs/docs/core.Function.chatReadFile.md new file mode 100644 index 0000000000..27de43e63c --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatReadFile.md @@ -0,0 +1,27 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / chatReadFile + +# Function: chatReadFile() + +> **chatReadFile**(`path`, `__namedParameters`): `Promise`\<`ArrayBuffer`\> + +Defined in: [src/core.ts:58](../src/core.ts#L58) + +Read buffer from encrypted file + +## Parameters + +### path + +`string` + +### \_\_namedParameters + +[`CryptoArgs`](core.Interface.CryptoArgs.md) + +## Returns + +`Promise`\<`ArrayBuffer`\> diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatRecvMsgWait.md b/packages/simplex-chat-nodejs/docs/core.Function.chatRecvMsgWait.md new file mode 100644 index 0000000000..9bf44d6523 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatRecvMsgWait.md @@ -0,0 +1,27 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / chatRecvMsgWait + +# Function: chatRecvMsgWait() + +> **chatRecvMsgWait**(`ctrl`, `wait`): `Promise`\<`ChatEvent` \| `undefined`\> + +Defined in: [src/core.ts:37](../src/core.ts#L37) + +Receive chat event + +## Parameters + +### ctrl + +`bigint` + +### wait + +`number` + +## Returns + +`Promise`\<`ChatEvent` \| `undefined`\> diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatSendCmd.md b/packages/simplex-chat-nodejs/docs/core.Function.chatSendCmd.md new file mode 100644 index 0000000000..2dfcba45b4 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatSendCmd.md @@ -0,0 +1,27 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / chatSendCmd + +# Function: chatSendCmd() + +> **chatSendCmd**(`ctrl`, `cmd`): `Promise`\<`ChatResponse`\> + +Defined in: [src/core.ts:25](../src/core.ts#L25) + +Send chat command as string + +## Parameters + +### ctrl + +`bigint` + +### cmd + +`string` + +## Returns + +`Promise`\<`ChatResponse`\> diff --git a/packages/simplex-chat-nodejs/docs/core.Function.chatWriteFile.md b/packages/simplex-chat-nodejs/docs/core.Function.chatWriteFile.md new file mode 100644 index 0000000000..3b1d770fbb --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Function.chatWriteFile.md @@ -0,0 +1,31 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / chatWriteFile + +# Function: chatWriteFile() + +> **chatWriteFile**(`ctrl`, `path`, `buffer`): `Promise`\<[`CryptoArgs`](core.Interface.CryptoArgs.md)\> + +Defined in: [src/core.ts:50](../src/core.ts#L50) + +Write buffer to encrypted file + +## Parameters + +### ctrl + +`bigint` + +### path + +`string` + +### buffer + +`ArrayBuffer` + +## Returns + +`Promise`\<[`CryptoArgs`](core.Interface.CryptoArgs.md)\> diff --git a/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md b/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md new file mode 100644 index 0000000000..906ef3ec3e --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md @@ -0,0 +1,31 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / APIResult + +# Interface: APIResult\ + +Defined in: [src/core.ts:87](../src/core.ts#L87) + +## Type Parameters + +### R + +`R` + +## Properties + +### error? + +> `optional` **error**: `ChatError` + +Defined in: [src/core.ts:89](../src/core.ts#L89) + +*** + +### result? + +> `optional` **result**: `R` + +Defined in: [src/core.ts:88](../src/core.ts#L88) diff --git a/packages/simplex-chat-nodejs/docs/core.Interface.CryptoArgs.md b/packages/simplex-chat-nodejs/docs/core.Interface.CryptoArgs.md new file mode 100644 index 0000000000..eddcb0bc5a --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Interface.CryptoArgs.md @@ -0,0 +1,27 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / CryptoArgs + +# Interface: CryptoArgs + +Defined in: [src/core.ts:111](../src/core.ts#L111) + +File encryption key and nonce + +## Properties + +### fileKey + +> **fileKey**: `string` + +Defined in: [src/core.ts:112](../src/core.ts#L112) + +*** + +### fileNonce + +> **fileNonce**: `string` + +Defined in: [src/core.ts:113](../src/core.ts#L113) diff --git a/packages/simplex-chat-nodejs/docs/core.Interface.UpMigration.md b/packages/simplex-chat-nodejs/docs/core.Interface.UpMigration.md new file mode 100644 index 0000000000..32f6c267aa --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Interface.UpMigration.md @@ -0,0 +1,25 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / UpMigration + +# Interface: UpMigration + +Defined in: [src/core.ts:185](../src/core.ts#L185) + +## Properties + +### upName + +> **upName**: `string` + +Defined in: [src/core.ts:186](../src/core.ts#L186) + +*** + +### withDown + +> **withDown**: `boolean` + +Defined in: [src/core.ts:187](../src/core.ts#L187) diff --git a/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTREDifferent.md b/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTREDifferent.md new file mode 100644 index 0000000000..8dab81a3a3 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTREDifferent.md @@ -0,0 +1,33 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / [MTRError](core.Namespace.MTRError.md) / MTREDifferent + +# Interface: MTREDifferent + +Defined in: [src/core.ts:206](../src/core.ts#L206) + +## Extends + +- `Interface` + +## Properties + +### downMigrations + +> **downMigrations**: `string`[] + +Defined in: [src/core.ts:208](../src/core.ts#L208) + +*** + +### type + +> **type**: `"different"` + +Defined in: [src/core.ts:207](../src/core.ts#L207) + +#### Overrides + +`Interface.type` diff --git a/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTRENoDown.md b/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTRENoDown.md new file mode 100644 index 0000000000..1de634e40e --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.MTRError.Interface.MTRENoDown.md @@ -0,0 +1,33 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / [MTRError](core.Namespace.MTRError.md) / MTRENoDown + +# Interface: MTRENoDown + +Defined in: [src/core.ts:201](../src/core.ts#L201) + +## Extends + +- `Interface` + +## Properties + +### type + +> **type**: `"noDown"` + +Defined in: [src/core.ts:202](../src/core.ts#L202) + +#### Overrides + +`Interface.type` + +*** + +### upMigrations + +> **upMigrations**: [`UpMigration`](core.Interface.UpMigration.md) + +Defined in: [src/core.ts:203](../src/core.ts#L203) diff --git a/packages/simplex-chat-nodejs/docs/core.MTRError.TypeAlias.Tag.md b/packages/simplex-chat-nodejs/docs/core.MTRError.TypeAlias.Tag.md new file mode 100644 index 0000000000..768baa0068 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.MTRError.TypeAlias.Tag.md @@ -0,0 +1,11 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / [MTRError](core.Namespace.MTRError.md) / Tag + +# Type Alias: Tag + +> **Tag** = `"noDown"` \| `"different"` + +Defined in: [src/core.ts:195](../src/core.ts#L195) diff --git a/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEDowngrade.md b/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEDowngrade.md new file mode 100644 index 0000000000..a2742cbff2 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEDowngrade.md @@ -0,0 +1,33 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / [MigrationError](core.Namespace.MigrationError.md) / MEDowngrade + +# Interface: MEDowngrade + +Defined in: [src/core.ts:174](../src/core.ts#L174) + +## Extends + +- `Interface` + +## Properties + +### downMigrations + +> **downMigrations**: `string`[] + +Defined in: [src/core.ts:176](../src/core.ts#L176) + +*** + +### type + +> **type**: `"downgrade"` + +Defined in: [src/core.ts:175](../src/core.ts#L175) + +#### Overrides + +`Interface.type` diff --git a/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEUpgrade.md b/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEUpgrade.md new file mode 100644 index 0000000000..08fe7d56e3 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MEUpgrade.md @@ -0,0 +1,33 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / [MigrationError](core.Namespace.MigrationError.md) / MEUpgrade + +# Interface: MEUpgrade + +Defined in: [src/core.ts:169](../src/core.ts#L169) + +## Extends + +- `Interface` + +## Properties + +### type + +> **type**: `"upgrade"` + +Defined in: [src/core.ts:170](../src/core.ts#L170) + +#### Overrides + +`Interface.type` + +*** + +### upMigrations + +> **upMigrations**: [`UpMigration`](core.Interface.UpMigration.md) + +Defined in: [src/core.ts:171](../src/core.ts#L171) diff --git a/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MigrationError.md b/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MigrationError.md new file mode 100644 index 0000000000..cd811a5747 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.MigrationError.Interface.MigrationError.md @@ -0,0 +1,33 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / [MigrationError](core.Namespace.MigrationError.md) / MigrationError + +# Interface: MigrationError + +Defined in: [src/core.ts:179](../src/core.ts#L179) + +## Extends + +- `Interface` + +## Properties + +### mtrError + +> **mtrError**: [`MTRError`](core.TypeAlias.MTRError.md) + +Defined in: [src/core.ts:181](../src/core.ts#L181) + +*** + +### type + +> **type**: `"migrationError"` + +Defined in: [src/core.ts:180](../src/core.ts#L180) + +#### Overrides + +`Interface.type` diff --git a/packages/simplex-chat-nodejs/docs/core.MigrationError.TypeAlias.Tag.md b/packages/simplex-chat-nodejs/docs/core.MigrationError.TypeAlias.Tag.md new file mode 100644 index 0000000000..5ef6e70b08 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.MigrationError.TypeAlias.Tag.md @@ -0,0 +1,11 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / [MigrationError](core.Namespace.MigrationError.md) / Tag + +# Type Alias: Tag + +> **Tag** = `"upgrade"` \| `"downgrade"` \| `"migrationError"` + +Defined in: [src/core.ts:163](../src/core.ts#L163) diff --git a/packages/simplex-chat-nodejs/docs/core.Namespace.DBMigrationError.md b/packages/simplex-chat-nodejs/docs/core.Namespace.DBMigrationError.md new file mode 100644 index 0000000000..95ddfa5b24 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Namespace.DBMigrationError.md @@ -0,0 +1,18 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / DBMigrationError + +# DBMigrationError + +## Interfaces + +- [ErrorMigration](core.DBMigrationError.Interface.ErrorMigration.md) +- [ErrorNotADatabase](core.DBMigrationError.Interface.ErrorNotADatabase.md) +- [ErrorSQL](core.DBMigrationError.Interface.ErrorSQL.md) +- [InvalidConfirmation](core.DBMigrationError.Interface.InvalidConfirmation.md) + +## Type Aliases + +- [Tag](core.DBMigrationError.TypeAlias.Tag.md) diff --git a/packages/simplex-chat-nodejs/docs/core.Namespace.MTRError.md b/packages/simplex-chat-nodejs/docs/core.Namespace.MTRError.md new file mode 100644 index 0000000000..70baca917a --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Namespace.MTRError.md @@ -0,0 +1,16 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / MTRError + +# MTRError + +## Interfaces + +- [MTREDifferent](core.MTRError.Interface.MTREDifferent.md) +- [MTRENoDown](core.MTRError.Interface.MTRENoDown.md) + +## Type Aliases + +- [Tag](core.MTRError.TypeAlias.Tag.md) diff --git a/packages/simplex-chat-nodejs/docs/core.Namespace.MigrationError.md b/packages/simplex-chat-nodejs/docs/core.Namespace.MigrationError.md new file mode 100644 index 0000000000..cc66142dbc --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.Namespace.MigrationError.md @@ -0,0 +1,17 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / MigrationError + +# MigrationError + +## Interfaces + +- [MEDowngrade](core.MigrationError.Interface.MEDowngrade.md) +- [MEUpgrade](core.MigrationError.Interface.MEUpgrade.md) +- [MigrationError](core.MigrationError.Interface.MigrationError.md) + +## Type Aliases + +- [Tag](core.MigrationError.TypeAlias.Tag.md) diff --git a/packages/simplex-chat-nodejs/docs/core.TypeAlias.DBMigrationError.md b/packages/simplex-chat-nodejs/docs/core.TypeAlias.DBMigrationError.md new file mode 100644 index 0000000000..6473b3ef60 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.TypeAlias.DBMigrationError.md @@ -0,0 +1,11 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / DBMigrationError + +# Type Alias: DBMigrationError + +> **DBMigrationError** = [`InvalidConfirmation`](core.DBMigrationError.Interface.InvalidConfirmation.md) \| [`ErrorNotADatabase`](core.DBMigrationError.Interface.ErrorNotADatabase.md) \| [`ErrorMigration`](core.DBMigrationError.Interface.ErrorMigration.md) \| [`ErrorSQL`](core.DBMigrationError.Interface.ErrorSQL.md) + +Defined in: [src/core.ts:122](../src/core.ts#L122) diff --git a/packages/simplex-chat-nodejs/docs/core.TypeAlias.MTRError.md b/packages/simplex-chat-nodejs/docs/core.TypeAlias.MTRError.md new file mode 100644 index 0000000000..11aa5b7c24 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.TypeAlias.MTRError.md @@ -0,0 +1,11 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / MTRError + +# Type Alias: MTRError + +> **MTRError** = [`MTRENoDown`](core.MTRError.Interface.MTRENoDown.md) \| [`MTREDifferent`](core.MTRError.Interface.MTREDifferent.md) + +Defined in: [src/core.ts:190](../src/core.ts#L190) diff --git a/packages/simplex-chat-nodejs/docs/core.TypeAlias.MigrationError.md b/packages/simplex-chat-nodejs/docs/core.TypeAlias.MigrationError.md new file mode 100644 index 0000000000..c15b679769 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/core.TypeAlias.MigrationError.md @@ -0,0 +1,11 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [core](Namespace.core.md) / MigrationError + +# Type Alias: MigrationError + +> **MigrationError** = [`MEUpgrade`](core.MigrationError.Interface.MEUpgrade.md) \| [`MEDowngrade`](core.MigrationError.Interface.MEDowngrade.md) \| [`MigrationError`](core.MigrationError.Interface.MigrationError.md) + +Defined in: [src/core.ts:157](../src/core.ts#L157) diff --git a/packages/simplex-chat-nodejs/docs/util.Function.botAddressSettings.md b/packages/simplex-chat-nodejs/docs/util.Function.botAddressSettings.md new file mode 100644 index 0000000000..7fa5d81b7a --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/util.Function.botAddressSettings.md @@ -0,0 +1,21 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [util](Namespace.util.md) / botAddressSettings + +# Function: botAddressSettings() + +> **botAddressSettings**(`__namedParameters`): [`BotAddressSettings`](api.Interface.BotAddressSettings.md) + +Defined in: [src/util.ts:48](../src/util.ts#L48) + +## Parameters + +### \_\_namedParameters + +`UserContactLink` + +## Returns + +[`BotAddressSettings`](api.Interface.BotAddressSettings.md) diff --git a/packages/simplex-chat-nodejs/docs/util.Function.chatInfoName.md b/packages/simplex-chat-nodejs/docs/util.Function.chatInfoName.md new file mode 100644 index 0000000000..dd68403c40 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/util.Function.chatInfoName.md @@ -0,0 +1,21 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [util](Namespace.util.md) / chatInfoName + +# Function: chatInfoName() + +> **chatInfoName**(`cInfo`): `string` + +Defined in: [src/util.ts:18](../src/util.ts#L18) + +## Parameters + +### cInfo + +`ChatInfo` + +## Returns + +`string` diff --git a/packages/simplex-chat-nodejs/docs/util.Function.chatInfoRef.md b/packages/simplex-chat-nodejs/docs/util.Function.chatInfoRef.md new file mode 100644 index 0000000000..dccb2a9d29 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/util.Function.chatInfoRef.md @@ -0,0 +1,21 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [util](Namespace.util.md) / chatInfoRef + +# Function: chatInfoRef() + +> **chatInfoRef**(`cInfo`): `ChatRef` \| `undefined` + +Defined in: [src/util.ts:4](../src/util.ts#L4) + +## Parameters + +### cInfo + +`ChatInfo` + +## Returns + +`ChatRef` \| `undefined` diff --git a/packages/simplex-chat-nodejs/docs/util.Function.ciBotCommand.md b/packages/simplex-chat-nodejs/docs/util.Function.ciBotCommand.md new file mode 100644 index 0000000000..bc7a66f163 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/util.Function.ciBotCommand.md @@ -0,0 +1,21 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [util](Namespace.util.md) / ciBotCommand + +# Function: ciBotCommand() + +> **ciBotCommand**(`chatItem`): [`BotCommand`](util.Interface.BotCommand.md) \| `undefined` + +Defined in: [src/util.ts:78](../src/util.ts#L78) + +## Parameters + +### chatItem + +`ChatItem` + +## Returns + +[`BotCommand`](util.Interface.BotCommand.md) \| `undefined` diff --git a/packages/simplex-chat-nodejs/docs/util.Function.ciContentText.md b/packages/simplex-chat-nodejs/docs/util.Function.ciContentText.md new file mode 100644 index 0000000000..7ab0bc540c --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/util.Function.ciContentText.md @@ -0,0 +1,21 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [util](Namespace.util.md) / ciContentText + +# Function: ciContentText() + +> **ciContentText**(`__namedParameters`): `string` \| `undefined` + +Defined in: [src/util.ts:64](../src/util.ts#L64) + +## Parameters + +### \_\_namedParameters + +`ChatItem` + +## Returns + +`string` \| `undefined` diff --git a/packages/simplex-chat-nodejs/docs/util.Function.contactAddressStr.md b/packages/simplex-chat-nodejs/docs/util.Function.contactAddressStr.md new file mode 100644 index 0000000000..3f6c7b9562 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/util.Function.contactAddressStr.md @@ -0,0 +1,21 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [util](Namespace.util.md) / contactAddressStr + +# Function: contactAddressStr() + +> **contactAddressStr**(`link`): `string` + +Defined in: [src/util.ts:44](../src/util.ts#L44) + +## Parameters + +### link + +`CreatedConnLink` + +## Returns + +`string` diff --git a/packages/simplex-chat-nodejs/docs/util.Function.fromLocalProfile.md b/packages/simplex-chat-nodejs/docs/util.Function.fromLocalProfile.md new file mode 100644 index 0000000000..c32081b6cf --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/util.Function.fromLocalProfile.md @@ -0,0 +1,21 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [util](Namespace.util.md) / fromLocalProfile + +# Function: fromLocalProfile() + +> **fromLocalProfile**(`__namedParameters`): `Profile` + +Defined in: [src/util.ts:56](../src/util.ts#L56) + +## Parameters + +### \_\_namedParameters + +`LocalProfile` + +## Returns + +`Profile` diff --git a/packages/simplex-chat-nodejs/docs/util.Function.reactionText.md b/packages/simplex-chat-nodejs/docs/util.Function.reactionText.md new file mode 100644 index 0000000000..896dc74a12 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/util.Function.reactionText.md @@ -0,0 +1,21 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [util](Namespace.util.md) / reactionText + +# Function: reactionText() + +> **reactionText**(`reaction`): `string` + +Defined in: [src/util.ts:89](../src/util.ts#L89) + +## Parameters + +### reaction + +`ACIReaction` + +## Returns + +`string` diff --git a/packages/simplex-chat-nodejs/docs/util.Function.senderName.md b/packages/simplex-chat-nodejs/docs/util.Function.senderName.md new file mode 100644 index 0000000000..6bacad97f6 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/util.Function.senderName.md @@ -0,0 +1,25 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [util](Namespace.util.md) / senderName + +# Function: senderName() + +> **senderName**(`cInfo`, `chatDir`): `string` + +Defined in: [src/util.ts:37](../src/util.ts#L37) + +## Parameters + +### cInfo + +`ChatInfo` + +### chatDir + +`CIDirection` + +## Returns + +`string` diff --git a/packages/simplex-chat-nodejs/docs/util.Interface.BotCommand.md b/packages/simplex-chat-nodejs/docs/util.Interface.BotCommand.md new file mode 100644 index 0000000000..21c3ad72ba --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/util.Interface.BotCommand.md @@ -0,0 +1,25 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [util](Namespace.util.md) / BotCommand + +# Interface: BotCommand + +Defined in: [src/util.ts:72](../src/util.ts#L72) + +## Properties + +### keyword + +> **keyword**: `string` + +Defined in: [src/util.ts:73](../src/util.ts#L73) + +*** + +### params + +> **params**: `string` + +Defined in: [src/util.ts:74](../src/util.ts#L74) diff --git a/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js b/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js new file mode 100644 index 0000000000..f880808bcf --- /dev/null +++ b/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js @@ -0,0 +1,17 @@ +(async () => { + const {bot} = await import("../dist/index.js") + const [chat, _user, _address] = await bot.run({ + profile: {displayName: "Squaring bot example", fullName: ""}, + dbOpts: {dbFilePrefix: "./squaring_bot", dbKey: ""}, + options: { + addressSettings: {welcomeMessage: "If you send me a number, I will calculate its square."}, + }, + onMessage: async (ci, content) => { + const n = +content.text + const reply = typeof n === "number" && !isNaN(n) + ? `${n} * ${n} = ${n * n}` + : `this is not a number` + await chat.apiSendTextReply(ci, reply) + } + }) +})() diff --git a/packages/simplex-chat-nodejs/examples/squaring-bot.ts b/packages/simplex-chat-nodejs/examples/squaring-bot.ts new file mode 100644 index 0000000000..682e7b887a --- /dev/null +++ b/packages/simplex-chat-nodejs/examples/squaring-bot.ts @@ -0,0 +1,42 @@ +import {T} from "@simplex-chat/types" +import {bot, util} from "../dist" + +(async () => { + const welcomeMessage = "Hello! I am a simple squaring bot.\n\nIf you send me a number, I will calculate its square." + const [chat, _user, _address] = await bot.run({ + profile: {displayName: "Squaring bot example", fullName: ""}, + dbOpts: {dbFilePrefix: "./squaring_bot", dbKey: ""}, + options: { + addressSettings: {autoAccept: true, welcomeMessage, businessAddress: false}, + commands: [ // commands to show in client UI + {type: "command", keyword: "help", label: "Send welcome message"}, + {type: "command", keyword: "info", label: "More information (not implemented)"} + ], + logContacts: true, + logNetwork: false + }, + onMessage: async (ci, content) => { + const n = +content.text + const reply = typeof n === "number" && !isNaN(n) + ? `${n} * ${n} = ${n * n}` + : `this is not a number` + await chat.apiSendTextReply(ci, reply) + }, + onCommands: { // command handlers can be different from commands to be shown in client UI + "help": async (ci: T.AChatItem, _cmd: util.BotCommand) => { + await chat.apiSendTextMessage(ci.chatInfo, welcomeMessage) + }, + // fallback handler that will be called for all other commands + "": async (ci: T.AChatItem, _cmd: util.BotCommand) => { + await chat.apiSendTextReply(ci, "This command is not supported") + } + }, + // If you use `onMessage` and subscribe to "newChatItems" event, exclude content messages from processing + // If you use `onCommands` and subscribe to "newChatItems" event, exclude commands from processing + events: { + "chatItemReaction": ({added, reaction}) => { + console.log(`${util.senderName(reaction.chatInfo, reaction.chatReaction.chatDir)} ${added ? "added" : "removed"} reaction ${util.reactionText(reaction)}`) + } + }, + }) +})() diff --git a/packages/simplex-chat-nodejs/jest.config.js b/packages/simplex-chat-nodejs/jest.config.js new file mode 100644 index 0000000000..18c5ddf0e5 --- /dev/null +++ b/packages/simplex-chat-nodejs/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + preset: "ts-jest", + maxWorkers: 1, + testEnvironment: "node", + transform: { + '^.+\\.ts$': ['ts-jest', { + tsconfig: 'tests/tsconfig.json' + }] + } +} diff --git a/packages/simplex-chat-nodejs/package.json b/packages/simplex-chat-nodejs/package.json new file mode 100644 index 0000000000..1ef1429e65 --- /dev/null +++ b/packages/simplex-chat-nodejs/package.json @@ -0,0 +1,58 @@ +{ + "name": "simplex-chat", + "version": "6.5.0-beta.4.2", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "src", + "cpp", + "dist", + "binding.gyp", + "tsconfig.json", + "docs", + "examples" + ], + "scripts": { + "preinstall": "node src/download-libs.js", + "install": "node-gyp configure; node-gyp rebuild --release", + "install-tools": "npm install -g node-gyp", + "configure": "node-gyp configure; mkdir libs 2> /dev/null | true", + "build": "node-gyp rebuild && tsc && cp ./src/simplex.* ./dist", + "run": "node src/index.js", + "build-run": "node-gyp build && node src/index.js", + "test": "jest", + "docs": "typedoc" + }, + "dependencies": { + "@simplex-chat/types": "^0.3.0", + "extract-zip": "^2.0.1", + "fast-deep-equal": "^3.1.3", + "node-addon-api": "^8.5.0" + }, + "devDependencies": { + "@types/jest": "^30.0.0", + "@types/node": "^25.0.5", + "jest": "^30.2.0", + "ts-jest": "^29.4.6", + "typedoc": "^0.28.15", + "typedoc-plugin-markdown": "^4.9.0", + "typescript": "^5.9.3" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/simplex-chat/simplex-chat.git" + }, + "keywords": [ + "messenger", + "chat", + "privacy", + "security" + ], + "author": "SimpleX Chat", + "license": "AGPL-3.0", + "bugs": { + "url": "https://github.com/simplex-chat/simplex-chat/issues" + }, + "homepage": "https://github.com/simplex-chat/simplex-chat/tree/stable/packages/simplex-chat-nodejs#readme", + "description": "SimpleX Chat Node.js library for chat bots" +} diff --git a/packages/simplex-chat-nodejs/src/api.ts b/packages/simplex-chat-nodejs/src/api.ts new file mode 100644 index 0000000000..dc87055f87 --- /dev/null +++ b/packages/simplex-chat-nodejs/src/api.ts @@ -0,0 +1,834 @@ +import {CC, CEvt, ChatEvent, ChatResponse, T} from "@simplex-chat/types" +import * as core from "./core" +import * as util from "./util" + +export class ChatCommandError extends Error { + constructor(public message: string, public response: ChatResponse) { + super(message) + } +} + +/** + * Connection request types. + * @enum {string} + */ +export enum ConnReqType { + Invitation = "invitation", + Contact = "contact", +} + +/** + * Bot address settings. + */ +export interface BotAddressSettings { + /** + * Automatically accept contact requests. + * @default true + */ + autoAccept?: boolean + + /** + * Optional welcome message to show before connection to the users. + * @default undefined (no welcome message) + */ + welcomeMessage?: T.MsgContent | string | undefined + + /** + * Business contact address. + * For all requests business chats will be created where other participants can be added. + * @default false + */ + businessAddress?: boolean +} + +export const defaultBotAddressSettings: BotAddressSettings = { + autoAccept: true, + welcomeMessage: undefined, + businessAddress: false +} + +export type EventSubscriberFunc = (event: ChatEvent & {type: K}) => void | Promise + +export type EventSubscribers = {[K in CEvt.Tag]?: EventSubscriberFunc} + +interface EventSubscriber { + subscriber: EventSubscriberFunc + once: boolean +} + +/** + * Main API class for interacting with the chat core library. + */ +export class ChatApi { + private receiveEvents = false + private eventsLoop: Promise | undefined = undefined + private subscribers: {[K in CEvt.Tag]?: EventSubscriber[]} = {} + private receivers: EventSubscriberFunc[] = [] + + private constructor(protected ctrl_: bigint | undefined) {} + + /** + * Initializes the ChatApi. + * @param {string} dbFilePrefix - File prefix for the database files. + * @param {string} [dbKey=""] - Database encryption key. + * @param {core.MigrationConfirmation} [confirm=core.MigrationConfirmation.YesUp] - Migration confirmation mode. + */ + static async init( + dbFilePrefix: string, + dbKey: string = "", + confirm = core.MigrationConfirmation.YesUp + ): Promise { + const ctrl = await core.chatMigrateInit(dbFilePrefix, dbKey, confirm) + return new ChatApi(ctrl) + } + + /** + * Start chat controller. Must be called with the existing user profile. + */ + async startChat(): Promise { + this.receiveEvents = true + this.eventsLoop = this.runEventsLoop() + const r = await this.sendChatCmd(CC.StartChat.cmdString({mainApp: true, enableSndFiles: true})) + if (r.type !== "chatStarted" && r.type !== "chatRunning") { + throw new ChatCommandError("error starting chat", r) + } + } + + /** + * Stop chat controller. + * Must be called before closing the database. + * Usually doesn't need to be called in chat bots. + */ + async stopChat(): Promise { + const r = await this.sendChatCmd("/_stop") + if (r.type !== "chatStopped") throw new ChatCommandError("error starting chat", r) + this.receiveEvents = false + if (this.eventsLoop) await this.eventsLoop + this.eventsLoop = undefined + } + + /** + * Close chat database. + * Usually doesn't need to be called in chat bots. + */ + async close(): Promise { + this.receiveEvents = false + if (this.eventsLoop) await this.eventsLoop + this.eventsLoop = undefined + await core.chatCloseStore(this.ctrl) + this.ctrl_ = undefined + } + + private async runEventsLoop(): Promise { + while (this.receiveEvents) { + try { + const event = await this.recvChatEvent() + if (!event) continue + const subs = this.subscribers[event.type] + if (subs) { + for (const {subscriber, once} of [...subs]) { + try { + const p = (subscriber as EventSubscriberFunc)(event) + if (p instanceof Promise) await p + } catch(e) { + console.log(`${event.type} event processing error`, e) + } + if (once) this.off(event.type, subscriber as EventSubscriberFunc) + } + } + for (const r of [...this.receivers]) { + try { + const p = r(event) + if (p instanceof Promise) await p + } catch(e) { + console.log(`${event.type} event processing error`, e) + } + } + } catch(err) { + const e = err as core.ChatAPIError + if ("chatError" in e) { + console.log("Chat error", e.chatError) + } else { + console.log("Invalid event", e) + } + } + } + } + + /** + * Subscribe multiple event handlers at once. + * @param subscribers - An object mapping event types (CEvt.Tag) to their subscriber functions. + * @throws {Error} If the same function is subscribed to event. + */ + on(subscribers: EventSubscribers): void + + /** + * Subscribe a handler to a specific event. + * @param {CEvt.Tag} event - The event type to subscribe to. + * @param subscriber - The subscriber function for the event. + * @throws {Error} If the same function is subscribed to event. + */ + on(event: K, subscriber: EventSubscriberFunc): void + on(events: K | EventSubscribers, subscriber?: EventSubscriberFunc): void { + if (typeof events === "string" && subscriber) { + this.on_(events, subscriber) + } else { + const eventEntries = Object.entries(events) as [CEvt.Tag, EventSubscriberFunc | undefined][] + for (const [event, subscriber] of eventEntries) { + if (subscriber) this.on_(event, subscriber) + } + } + } + + private on_(event: K, subscriber: EventSubscriberFunc, once: boolean = false): void { + const subs: EventSubscriber[] = this.subscribers[event] || (this.subscribers[event] = []) + if (subs.some(s => s.subscriber === subscriber)) throw Error(`this function is already subscribed to ${event}`) + subs.push({subscriber, once}) + } + + /** + * Subscribe a handler to any event. + * @param receiver - The receiver function for any event. + * @throws {Error} If the same function is subscribed to event. + */ + onAny(receiver: EventSubscriberFunc): void { + if (this.receivers.some(s => s === receiver)) throw Error("this function is already subscribed") + this.receivers.push(receiver) + } + + /** + * Subscribe a handler to a specific event to be delivered one time. + * @param {CEvt.Tag} event - The event type to subscribe to. + * @param subscriber - The subscriber function for the event. + * @throws {Error} If the same function is subscribed to event. + */ + once(event: K, subscriber: EventSubscriberFunc): void { + this.on_(event, subscriber, true) + } + + /** + * Waits for specific event, with an optional predicate. + * Returns `undefined` on timeout if specified. + */ + wait(event: K): Promise + wait(event: K, predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined): Promise + wait(event: K, timeout: number): Promise + wait(event: K, predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined, timeout: number): Promise + wait( + event: K, + predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined | number = undefined, // number for timeout + timeout: number = 0 // milliseconds, default - indefinite + ): Promise { + if (typeof predicate === "number") { + timeout = predicate + predicate = undefined + } + return new Promise((resolve, reject) => { + let done = false + const cleanup = () => { + done = true + this.off(event, subscriber) + } + const subscriber: EventSubscriberFunc = async (evt: ChatEvent & {type: K}) => { + if (done) return + if (predicate) { + try { if (!predicate(evt)) return } + catch(e) { cleanup(); reject(e); return } + } + cleanup() + resolve(evt) + } + this.on(event, subscriber) + if (timeout > 0) { + setTimeout(() => { if (!done) { cleanup(); resolve(undefined) } }, timeout) + } + }) + } + + /** + * Unsubscribe all or a specific handler from a specific event. + * @param {CEvt.Tag} event - The event type to unsubscribe from. + * @param subscriber - An optional subscriber function for the event. + */ + off(event: K, subscriber: EventSubscriberFunc | undefined = undefined): void { + if (subscriber) { + const subs = this.subscribers[event] + if (subs) { + const i = subs.findIndex(s => s.subscriber === subscriber) + if (i >= 0) subs.splice(i, 1) + } + } else { + delete this.subscribers[event] + } + } + + /** + * Unsubscribe all or a specific handler from any events. + * @param receiver - An optional subscriber function for the event. + */ + offAny(receiver: EventSubscriberFunc | undefined = undefined): void { + if (receiver) { + const i = this.receivers.findIndex(r => r === receiver) + if (i >= 0) this.receivers.splice(i, 1) + } else { + this.receivers = [] + } + } + + /** + * Chat controller is initialized + */ + get initialized(): boolean { + return typeof this.ctrl_ === "bigint" + } + + /** + * Chat controller is started + */ + get started(): boolean { + return this.receiveEvents && this.eventsLoop !== undefined + } + + /** + * Chat controller reference + */ + get ctrl(): bigint { + if (typeof this.ctrl_ === "bigint") return this.ctrl_ + else throw Error("chat api controller not initialized") + } + + async sendChatCmd(cmd: string): Promise { + return await core.chatSendCmd(this.ctrl, cmd) + } + + async recvChatEvent(wait: number = 5_000_000): Promise { + return await core.chatRecvMsgWait(this.ctrl, wait) + } + + /** + * Create bot address. + * Network usage: interactive. + */ + async apiCreateUserAddress(userId: number): Promise { + const r = await this.sendChatCmd(CC.APICreateMyAddress.cmdString({userId})) + if (r.type === "userContactLinkCreated") return r.connLinkContact + throw new ChatCommandError("error creating user address", r) + } + + /** + * Deletes a user address. + * Network usage: background. + */ + async apiDeleteUserAddress(userId: number): Promise { + const r = await this.sendChatCmd(CC.APIDeleteMyAddress.cmdString({userId})) + if (r.type === "userContactLinkDeleted") return + throw new ChatCommandError("error deleting user address", r) + } + + /** + * Get bot address and settings. + * Network usage: no. + */ + async apiGetUserAddress(userId: number): Promise { + try { + const r = await this.sendChatCmd(CC.APIShowMyAddress.cmdString({userId})) + switch (r.type) { + case "userContactLink": return r.contactLink + default: throw new ChatCommandError("error loading user address", r) + } + } catch (err) { + const e = err as any + if (e.chatError?.type === "errorStore" && e.chatError.storeError?.type === "userContactLinkNotFound") return undefined + throw e + } + } + + /** + * Add address to bot profile. + * Network usage: interactive. + */ + async apiSetProfileAddress(userId: number, enable: boolean): Promise { + const r = await this.sendChatCmd(CC.APISetProfileAddress.cmdString({userId, enable})) + switch (r.type) { + case "userProfileUpdated": + return r.updateSummary + default: + throw new ChatCommandError("error loading user address", r) + } + } + + /** + * Set bot address settings. + * Network usage: interactive. + */ + async apiSetAddressSettings(userId: number, {autoAccept, welcomeMessage, businessAddress}: BotAddressSettings): Promise { + const autoReply = welcomeMessage || defaultBotAddressSettings.welcomeMessage + const settings: T.AddressSettings = { + autoAccept: (autoAccept === undefined ? defaultBotAddressSettings.autoAccept : autoAccept) ? {acceptIncognito: false} : undefined, + autoReply: typeof autoReply === "string" ? {type: "text", text: autoReply} : autoReply, + businessAddress: businessAddress || defaultBotAddressSettings.businessAddress || false + } + const r = await this.sendChatCmd(CC.APISetAddressSettings.cmdString({userId, settings})) + if (r.type !== "userContactLinkUpdated") { + throw new ChatCommandError("error changing user contact address settings", r) + } + } + + /** + * Send messages. + * Network usage: background. + */ + async apiSendMessages(chat: [T.ChatType, number] | T.ChatRef | T.ChatInfo, messages: T.ComposedMessage[], liveMessage = false): Promise { + const sendRef = Array.isArray(chat) + ? {chatType: chat[0], chatId: chat[1]} + : "chatType" in chat + ? chat + : util.chatInfoRef(chat) + if (!sendRef) throw Error("apiSendMessages: can't send messages to this chat") + const r = await this.sendChatCmd( + CC.APISendMessages.cmdString({ + sendRef, + composedMessages: messages, + liveMessage + }) + ) + if (r.type === "newChatItems") return r.chatItems + throw new ChatCommandError("unexpected response", r) + } + + /** + * Send text message. + * Network usage: background. + */ + async apiSendTextMessage(chat: [T.ChatType, number] | T.ChatRef | T.ChatInfo, text: string, inReplyTo?: number): Promise { + return this.apiSendMessages(chat, [{msgContent: {type: "text", text}, mentions: {}, quotedItemId: inReplyTo}]) + } + + /** + * Send text message in reply to received message. + * Network usage: background. + */ + async apiSendTextReply(chatItem: T.AChatItem, text: string): Promise { + return this.apiSendTextMessage(chatItem.chatInfo, text, chatItem.chatItem.meta.itemId) + } + + /** + * Update message. + * Network usage: background. + */ + async apiUpdateChatItem(chatType: T.ChatType, chatId: number, chatItemId: number, msgContent: T.MsgContent, liveMessage: false): Promise { + const r = await this.sendChatCmd( + CC.APIUpdateChatItem.cmdString({ + chatRef: {chatType, chatId}, + chatItemId, + liveMessage, + updatedMessage: {msgContent, mentions: {}}, + }) + ) + if (r.type === "chatItemUpdated") return r.chatItem.chatItem + throw new ChatCommandError("error updating chat item", r) + } + + /** + * Delete message. + * Network usage: background. + */ + async apiDeleteChatItems( + chatType: T.ChatType, + chatId: number, + chatItemIds: number[], + deleteMode: T.CIDeleteMode + ): Promise { + const r = await this.sendChatCmd(CC.APIDeleteChatItem.cmdString({chatRef: {chatType, chatId}, chatItemIds, deleteMode})) + if (r.type === "chatItemsDeleted") return r.chatItemDeletions + throw new ChatCommandError("error deleting chat item", r) + } + + /** + * Moderate message. Requires Moderator role (and higher than message author's). + * Network usage: background. + */ + async apiDeleteMemberChatItem(groupId: number, chatItemIds: number[]): Promise { + const r = await this.sendChatCmd(CC.APIDeleteMemberChatItem.cmdString({groupId, chatItemIds})) + if (r.type === "chatItemsDeleted") return r.chatItemDeletions + throw new ChatCommandError("error deleting member chat item", r) + } + + /** + * Add/remove message reaction. + * Network usage: background. + */ + async apiChatItemReaction( + chatType: T.ChatType, + chatId: number, + chatItemId: number, + add: boolean, + reaction: T.MsgReaction + ) { + const r = await this.sendChatCmd(CC.APIChatItemReaction.cmdString({chatRef: {chatType, chatId}, chatItemId, add, reaction})) + if (r.type === "chatItemsDeleted") return r.chatItemDeletions + throw new ChatCommandError("error setting item reaction", r) + } + + /** + * Receive file. + * Network usage: no. + */ + async apiReceiveFile(fileId: number): Promise { + const r = await this.sendChatCmd(CC.ReceiveFile.cmdString({fileId, userApprovedRelays: true})) + if (r.type === "rcvFileAccepted") return r.chatItem + throw new ChatCommandError("error receiving file", r) + } + + /** + * Cancel file. + * Network usage: background. + */ + async apiCancelFile(fileId: number): Promise { + const r = await this.sendChatCmd(CC.CancelFile.cmdString({fileId})) + if (r.type === "sndFileCancelled" || r.type === "rcvFileCancelled") return + throw new ChatCommandError("error canceling file", r) + } + + /** + * Add contact to group. Requires bot to have Admin role. + * Network usage: interactive. + */ + async apiAddMember(groupId: number, contactId: number, memberRole: T.GroupMemberRole): Promise { + const r = await this.sendChatCmd(CC.APIAddMember.cmdString({groupId, contactId, memberRole})) + if (r.type === "sentGroupInvitation") return r.member + throw new ChatCommandError("error adding member", r) + } + + /** + * Join group. + * Network usage: interactive. + */ + async apiJoinGroup(groupId: number): Promise { + const r = await this.sendChatCmd(CC.APIJoinGroup.cmdString({groupId})) + if (r.type === "userAcceptedGroupSent") return r.groupInfo + throw new ChatCommandError("error joining group", r) + } + + /** + * Accept group member. Requires Admin role. + * Network usage: background. + */ + async apiAcceptMember(groupId: number, groupMemberId: number, memberRole: T.GroupMemberRole): Promise { + const r = await this.sendChatCmd(CC.APIAcceptMember.cmdString({groupId, groupMemberId, memberRole})) + if (r.type === "memberAccepted") return r.member + throw new ChatCommandError("error accepting member", r) + } + + /** + * Set members role. Requires Admin role. + * Network usage: background. + */ + async apiSetMembersRole(groupId: number, groupMemberIds: number[], memberRole: T.GroupMemberRole): Promise { + const r = await this.sendChatCmd(CC.APIMembersRole.cmdString({groupId, groupMemberIds, memberRole})) + if (r.type === "membersRoleUser") return + throw new ChatCommandError("error setting members role", r) + } + + /** + * Block members. Requires Moderator role. + * Network usage: background. + */ + async apiBlockMembersForAll(groupId: number, groupMemberIds: number[], blocked: boolean): Promise { + const r = await this.sendChatCmd(CC.APIBlockMembersForAll.cmdString({groupId, groupMemberIds, blocked})) + if (r.type === "membersBlockedForAllUser") return + throw new ChatCommandError("error blocking members", r) + } + + /** + * Remove members. Requires Admin role. + * Network usage: background. + */ + async apiRemoveMembers(groupId: number, memberIds: number[], withMessages = false): Promise { + const r = await this.sendChatCmd(CC.APIRemoveMembers.cmdString({groupId, groupMemberIds: memberIds, withMessages})) + if (r.type === "userDeletedMembers") return r.members + throw new ChatCommandError("error removing member", r) + } + + /** + * Leave group. + * Network usage: background. + */ + async apiLeaveGroup(groupId: number): Promise { + const r = await this.sendChatCmd(CC.APILeaveGroup.cmdString({groupId})) + if (r.type === "leftMemberUser") return r.groupInfo + throw new ChatCommandError("error leaving group", r) + } + + /** + * Get group members. + * Network usage: no. + */ + async apiListMembers(groupId: number): Promise { + const r = await this.sendChatCmd(CC.APIListMembers.cmdString({groupId})) + if (r.type === "groupMembers") return r.group.members + throw new ChatCommandError("error getting group members", r) + } + + /** + * Create group. + * Network usage: no. + */ + async apiNewGroup(userId: number, groupProfile: T.GroupProfile): Promise { + const r = await this.sendChatCmd(CC.APINewGroup.cmdString({userId, groupProfile, incognito: false})) + if (r.type === "groupCreated") return r.groupInfo + throw new ChatCommandError("error creating group", r) + } + + /** + * Update group profile. + * Network usage: background. + */ + async apiUpdateGroupProfile(groupId: number, groupProfile: T.GroupProfile): Promise { + const r = await this.sendChatCmd(CC.APIUpdateGroupProfile.cmdString({groupId, groupProfile})) + if (r.type === "groupUpdated") return r.toGroup + throw new ChatCommandError("error updating group", r) + } + + /** + * Create group link. + * Network usage: interactive. + */ + async apiCreateGroupLink(groupId: number, memberRole: T.GroupMemberRole): Promise { + const r = await this.sendChatCmd(CC.APICreateGroupLink.cmdString({groupId, memberRole})) + if (r.type === "groupLinkCreated") { + const link = r.groupLink.connLinkContact + return link.connShortLink || link.connFullLink + } + throw new ChatCommandError("error creating group link", r) + } + + /** + * Set member role for group link. + * Network usage: no. + */ + async apiSetGroupLinkMemberRole(groupId: number, memberRole: T.GroupMemberRole): Promise { + const r = await this.sendChatCmd(CC.APIGroupLinkMemberRole.cmdString({groupId, memberRole})) + if (r.type !== "groupLink") throw new ChatCommandError("error setting group link member role", r) + } + + /** + * Delete group link. + * Network usage: background. + */ + async apiDeleteGroupLink(groupId: number): Promise { + const r = await this.sendChatCmd(CC.APIDeleteGroupLink.cmdString({groupId})) + if (r.type !== "groupLinkDeleted") throw new ChatCommandError("error deleting group link", r) + } + + /** + * Get group link. + * Network usage: no. + */ + async apiGetGroupLink(groupId: number): Promise { + const r = await this.sendChatCmd(CC.APIGetGroupLink.cmdString({groupId})) + if (r.type === "groupLink") return r.groupLink + throw new ChatCommandError("error getting group link", r) + } + + async apiGetGroupLinkStr(groupId: number): Promise { + const link = (await this.apiGetGroupLink(groupId)).connLinkContact + return link.connShortLink || link.connFullLink + } + + /** + * Create 1-time invitation link. + * Network usage: interactive. + */ + async apiCreateLink(userId: number): Promise { + const r = await this.sendChatCmd(CC.APIAddContact.cmdString({userId, incognito: false})) + if (r.type === "invitation") { + const link = r.connLinkInvitation + return link.connShortLink || link.connFullLink + } + throw new ChatCommandError("error creating link", r) + } + + /** + * Determine SimpleX link type and if the bot is already connected via this link. + * Network usage: interactive. + */ + async apiConnectPlan(userId: number, connectionLink: string): Promise<[T.ConnectionPlan, T.CreatedConnLink]> { + const r = await this.sendChatCmd(CC.APIConnectPlan.cmdString({userId, connectionLink})) + if (r.type === "connectionPlan") return [r.connectionPlan, r.connLink] + throw new ChatCommandError("error getting connect plan", r) + } + + /** + * Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link + * Network usage: interactive. + */ + async apiConnect(userId: number, incognito: boolean, preparedLink?: T.CreatedConnLink): Promise { + const r = await this.sendChatCmd(CC.APIConnect.cmdString({userId, incognito, preparedLink_: preparedLink})) + return this.handleConnectResult(r) + } + + /** + * Connect via SimpleX link as string in the active user profile. + * Network usage: interactive. + */ + async apiConnectActiveUser(connLink: string): Promise { + const r = await this.sendChatCmd(CC.Connect.cmdString({incognito: false, connLink_: connLink})) + return this.handleConnectResult(r) + } + + private handleConnectResult(r: ChatResponse): ConnReqType { + switch (r.type) { + case "sentConfirmation": + return ConnReqType.Invitation + case "sentInvitation": + return ConnReqType.Contact + case "contactAlreadyExists": + throw new ChatCommandError("contact already exists", r) + default: + throw new ChatCommandError("connection error", r) + } + } + + /** + * Accept contact request. + * Network usage: interactive. + */ + async apiAcceptContactRequest(contactReqId: number): Promise { + const r = await this.sendChatCmd(CC.APIAcceptContact.cmdString({contactReqId})) + if (r.type === "acceptingContactRequest") return r.contact + throw new ChatCommandError("error accepting contact request", r) + } + + /** + * Reject contact request. The user who sent the request is **not notified**. + * Network usage: no. + */ + async apiRejectContactRequest(contactReqId: number): Promise { + const r = await this.sendChatCmd(CC.APIRejectContact.cmdString({contactReqId})) + if (r.type === "contactRequestRejected") return + throw new ChatCommandError("error rejecting contact request", r) + } + + /** + * Get contacts. + * Network usage: no. + */ + async apiListContacts(userId: number): Promise { + const r = await this.sendChatCmd(CC.APIListContacts.cmdString({userId})) + if (r.type === "contactsList") return r.contacts + throw new ChatCommandError("error listing contacts", r) + } + + /** + * Get groups. + * Network usage: no. + */ + async apiListGroups(userId: number, contactId?: number, search?: string): Promise { + const r = await this.sendChatCmd(CC.APIListGroups.cmdString({userId, contactId_: contactId, search})) + if (r.type === "groupsList") return r.groups + throw new ChatCommandError("error listing groups", r) + } + + /** + * Delete chat. + * Network usage: background. + */ + async apiDeleteChat(chatType: T.ChatType, chatId: number, deleteMode: T.ChatDeleteMode = {type: "full", notify: true}): Promise { + const r = await this.sendChatCmd(CC.APIDeleteChat.cmdString({chatRef: {chatType, chatId}, chatDeleteMode: deleteMode})) + switch (chatType) { + case T.ChatType.Direct: + if (r.type === "contactDeleted") return + break + case T.ChatType.Group: + if (r.type === "groupDeletedUser") return + break + } + throw new ChatCommandError("error deleting chat", r) + } + + /** + * Get active user profile + * Network usage: no. + */ + async apiGetActiveUser(): Promise { + try { + const r = await this.sendChatCmd(CC.ShowActiveUser.cmdString({})) + switch (r.type) { + case "activeUser": + return r.user + default: + throw new ChatCommandError("unexpected response", r) + } + } catch (err) { + const e = err as core.ChatAPIError + if (e.chatError?.type === "error" && e.chatError.errorType.type === "noActiveUser") return undefined + throw err + } + } + + /** + * Create new user profile + * Network usage: no. + */ + async apiCreateActiveUser(profile?: T.Profile): Promise { + const r = await this.sendChatCmd(CC.CreateActiveUser.cmdString({newUser: {profile, pastTimestamp: false}})) + if (r.type === "activeUser") return r.user + throw new ChatCommandError("unexpected response", r) + } + + /** + * Get all user profiles + * Network usage: no. + */ + async apiListUsers(): Promise { + const r = await this.sendChatCmd(CC.ListUsers.cmdString({})) + if (r.type === "usersList") return r.users + throw new ChatCommandError("error listing users", r) + } + + /** + * Set active user profile + * Network usage: no. + */ + async apiSetActiveUser(userId: number, viewPwd?: string): Promise { + const r = await this.sendChatCmd(CC.APISetActiveUser.cmdString({userId, viewPwd})) + if (r.type === "activeUser") return r.user + throw new ChatCommandError("error setting active user", r) + } + + /** + * Delete user profile. + * Network usage: background. + */ + async apiDeleteUser(userId: number, delSMPQueues: boolean, viewPwd?: string): Promise { + const r = await this.sendChatCmd(CC.APIDeleteUser.cmdString({userId, delSMPQueues, viewPwd})) + if (r.type === "cmdOk") return + throw new ChatCommandError("error deleting user", r) + } + + /** + * Update user profile. + * Network usage: background. + */ + async apiUpdateProfile(userId: number, profile: T.Profile): Promise { + const r = await this.sendChatCmd(CC.APIUpdateProfile.cmdString({userId, profile})) + switch (r.type) { + case "userProfileNoChange": + return undefined + case "userProfileUpdated": + return r.updateSummary + default: + throw new ChatCommandError("error updating profile", r) + } + } + + /** + * Configure chat preference overrides for the contact. + * Network usage: background. + */ + async apiSetContactPrefs(contactId: number, preferences: T.Preferences): Promise { + const r = await this.sendChatCmd(CC.APISetContactPrefs.cmdString({contactId, preferences})) + if (r.type !== "contactPrefsUpdated") throw new ChatCommandError("error setting contact prefs", r) + } +} diff --git a/packages/simplex-chat-nodejs/src/bot.ts b/packages/simplex-chat-nodejs/src/bot.ts new file mode 100644 index 0000000000..95a0c13d96 --- /dev/null +++ b/packages/simplex-chat-nodejs/src/bot.ts @@ -0,0 +1,216 @@ +import {T} from "@simplex-chat/types" +import * as api from "./api" +import * as core from "./core" +import * as util from "./util" +import equal = require("fast-deep-equal") + +export interface BotDbOpts { + dbFilePrefix: string // two schema files will be named _chat.db and _agent.db + dbKey?: string + confirmMigrations?: core.MigrationConfirmation +} + +export interface BotOptions { + createAddress?: boolean + updateAddress?: boolean + updateProfile?: boolean + addressSettings?: api.BotAddressSettings + allowFiles?: boolean + commands?: T.ChatBotCommand[] // commands to show in client UI + useBotProfile?: boolean // create profile not marked as a bot, with default preferences + logContacts?: boolean + logNetwork?: boolean +} + +const defaultOpts: Required = { + createAddress: true, + updateAddress: true, + updateProfile: true, + addressSettings: api.defaultBotAddressSettings, + allowFiles: false, + commands: [], + useBotProfile: true, + logContacts: true, + logNetwork: false +} + +export interface BotConfig { + profile: T.Profile, + dbOpts: BotDbOpts, + options: BotOptions, + onMessage?: (chatItem: T.AChatItem, content: T.MsgContent) => void | Promise, + // command handlers can be different from commands to be shown in client UI + onCommands?: {[K in string]?: ((chatItem: T.AChatItem, command: util.BotCommand) => void | Promise)}, + // If you use `onMessage` and to subscribe "newChatItems" event, exclude content messages from processing + // If you use `onCommands` and to subscribe "newChatItems" event, exclude commands from processing + events?: api.EventSubscribers +} + +export async function run({profile, dbOpts, options = defaultOpts, onMessage, onCommands = {}, events = {}}: BotConfig): Promise<[api.ChatApi, T.User, T.UserContactLink | undefined]> { + const bot = await api.ChatApi.init(dbOpts.dbFilePrefix, dbOpts.dbKey || "", dbOpts.confirmMigrations || core.MigrationConfirmation.YesUp) + const opts = fullOptions(options) + if (onMessage) subscribeMessages(bot, onMessage) + if (Object.keys(onCommands).length > 0) subscribeCommands(bot, onCommands) + if (Object.keys(events).length > 0) bot.on(events) + subscribeLogEvents(bot, opts) + const botProfile = mkBotProfile(profile, opts) + const user = await createBotUser(bot, botProfile) + await bot.startChat() + const address = await createOrUpdateAddress(bot, user, opts) + if (address) { + const addressLink = util.contactAddressStr(address.connLinkContact) + console.log(`Bot address: ${addressLink}`) + if (opts.useBotProfile) botProfile.contactLink = addressLink + } + await updateBotUserProfile(bot, user, botProfile, opts) + return [bot, user, address] +} + +function fullOptions(options: BotOptions): Required { + const opts = { + createAddress: options.createAddress ?? defaultOpts.createAddress, + updateAddress: options.updateAddress ?? defaultOpts.updateAddress, + updateProfile: options.updateProfile ?? defaultOpts.updateProfile, + addressSettings: options.addressSettings ?? defaultOpts.addressSettings, + allowFiles: options.allowFiles ?? defaultOpts.allowFiles, + commands: options.commands ?? defaultOpts.commands, + useBotProfile: options.useBotProfile ?? defaultOpts.useBotProfile, + logContacts: options.logContacts ?? defaultOpts.logContacts, + logNetwork: options.logNetwork ?? defaultOpts.logNetwork + } + const welcomeMessage = opts.addressSettings.welcomeMessage ?? defaultOpts.addressSettings.welcomeMessage + opts.addressSettings = { + autoAccept: opts.addressSettings.autoAccept ?? defaultOpts.addressSettings.autoAccept, + welcomeMessage: typeof welcomeMessage === "string" ? {type: "text", text: welcomeMessage} : welcomeMessage, + businessAddress: opts.addressSettings.businessAddress ?? defaultOpts.addressSettings.businessAddress + } + return opts +} + +function mkBotProfile(profile: T.Profile, opts: Required): T.Profile { + if (opts.useBotProfile) { + const prefs = profile.preferences || {} + if (prefs.files || prefs.calls || prefs.voice || prefs.commands) { + console.log("Option useBotProfile is enabled and profile preferences used for files, calls, voice or commands, exiting") + process.exit() + } + prefs.files = {allow: opts.allowFiles ? T.FeatureAllowed.Yes : T.FeatureAllowed.No} + prefs.calls = {allow: T.FeatureAllowed.No} + prefs.voice = {allow: T.FeatureAllowed.No} + prefs.commands = opts.commands + profile.preferences = prefs + profile.peerType = T.ChatPeerType.Bot + } else if (opts.commands.length > 0) { + console.log("Option useBotProfile is disabled and commands are passed, exiting") + process.exit() + } + return profile +} + +function subscribeMessages(bot: api.ChatApi, onMessage: (chatItem: T.AChatItem, content: T.MsgContent) => void | Promise) { + bot.on("newChatItems", async ({chatItems}) => { + for (const ci of chatItems) { + if (ci.chatItem.content.type === "rcvMsgContent") { + try { + const p = onMessage(ci, ci.chatItem.content.msgContent) + if (p instanceof Promise) await p + } catch (e) { + console.log("message processing error", e) + } + } + } + }) +} + +function subscribeCommands(bot: api.ChatApi, commands: {[K in string]?: ((chatItem: T.AChatItem, command: util.BotCommand) => void | Promise)}) { + bot.on("newChatItems", async (evt) => { + for (const ci of evt.chatItems) { + const cmd = util.ciBotCommand(ci.chatItem) + if (cmd) { + const cmdFunc = commands[cmd.keyword] || commands[""] + if (cmdFunc) { + try { + const p = cmdFunc(ci, cmd) + if (p instanceof Promise) await p + } catch(e) { + console.log(`${cmd} command processing error`, e) + } + } + } + } + }) +} + +function subscribeLogEvents(bot: api.ChatApi, opts: Required) { + if (opts.logContacts) { + bot.on({ + "contactConnected": ({contact}) => console.log(`${contact.profile.displayName} connected`), + "contactDeletedByContact": ({contact}) => console.log(`${contact.profile.displayName} deleted connection with bot`) + }) + } + if (opts.logNetwork) { + bot.on({ + "hostConnected": ({transportHost}) => console.log(`connected server ${transportHost}`), + "hostDisconnected": ({transportHost}) => console.log(`diconnected server ${transportHost}`), + "subscriptionStatus": ({subscriptionStatus, connections}) => console.log(`${connections.length} subscription(s) ${subscriptionStatus.type}`) + }) + } +} + +async function createBotUser(bot: api.ChatApi, profile: T.Profile): Promise { + let user = await bot.apiGetActiveUser() + if (!user) { + console.log("No active user in database, creating...") + user = await bot.apiCreateActiveUser(profile) + } + console.log("Bot user: ", user.profile.displayName) + return user +} + +async function createOrUpdateAddress(bot: api.ChatApi, user: T.User, opts: Required): Promise { + const {userId} = user + let address = await bot.apiGetUserAddress(userId) + if (!address) { + if (opts.createAddress) { + console.log("Bot has no address, creating...") + await bot.apiCreateUserAddress(userId) + address = await bot.apiGetUserAddress(userId) + if (!address) { + console.log("Failed reading created user address, exiting") + process.exit() + } + } else { + console.log("Warning: bot has no address") + return + } + } + + const addressSettings = opts.addressSettings || defaultOpts.addressSettings + if (!equal(util.botAddressSettings(address), addressSettings)) { + if (opts.updateAddress) { + console.log("Bot address settings changed, updating...") + await bot.apiSetAddressSettings(userId, addressSettings) + } else { + console.log("Bot address settings changed") + } + } + + return address +} + +async function updateBotUserProfile(bot: api.ChatApi, user: T.User, profile: T.Profile, opts: Required): Promise { + const {userId} = user + if (!equal(util.fromLocalProfile(user.profile), profile)) { + if (opts.updateProfile) { + console.log("Bot profile changed, updating...") + const summary = await bot.apiUpdateProfile(userId, profile) + console.log( + summary + ? `Bot profile updated: ${summary.updateSuccesses} updated contact(s), ${summary.updateFailures} failed contact update(s).` + : "Unexpected: profile did not change!" + ) + } else { + console.log("Bot profile changed") + } + } +} diff --git a/packages/simplex-chat-nodejs/src/core.ts b/packages/simplex-chat-nodejs/src/core.ts new file mode 100644 index 0000000000..949c2356af --- /dev/null +++ b/packages/simplex-chat-nodejs/src/core.ts @@ -0,0 +1,210 @@ +import {ChatEvent, ChatResponse, T} from "@simplex-chat/types" +import * as simplex from "./simplex" + +/** + * Initialize chat controller + */ +export async function chatMigrateInit(dbPath: string, dbKey: string, confirm: MigrationConfirmation): Promise { + const [ctrl, res] = await simplex.chat_migrate_init(dbPath, dbKey, confirm) + const json = JSON.parse(res) + if (json.type === 'ok') return ctrl + throw new ChatInitError("Database or migration error (see dbMigrationError property)", json as DBMigrationError) +} + +/** + * Close chat store + */ +export async function chatCloseStore(ctrl: bigint): Promise { + const res = await simplex.chat_close_store(ctrl) + if (res !== "") throw new Error(res) +} + +/** + * Send chat command as string + */ +export async function chatSendCmd(ctrl: bigint, cmd: string): Promise { + const res = await simplex.chat_send_cmd(ctrl, cmd) + const json = JSON.parse(res) as APIResult + // console.log(cmd.slice(0, 16), json.result?.type || json.error) + if (typeof json.result === 'object') return json.result + if (typeof json.error === 'object') throw new ChatAPIError("Chat command error (see chatError property)", json.error as T.ChatError) + throw new ChatAPIError("Invalid chat command result") +} + +/** + * Receive chat event + */ +export async function chatRecvMsgWait(ctrl: bigint, wait: number): Promise { + const res = await simplex.chat_recv_msg_wait(ctrl, wait) + if (res === "") return undefined + const json = JSON.parse(res) as APIResult + // if (json.result) console.log("event", json.result.type) + if (typeof json.result === 'object') return json.result + if (typeof json.error === 'object') throw new ChatAPIError("Chat event error (see chatError property)", json.error as T.ChatError) + throw new ChatAPIError("Invalid chat event") +} + +/** + * Write buffer to encrypted file + */ +export async function chatWriteFile(ctrl: bigint, path: string, buffer: ArrayBuffer): Promise { + const res = await simplex.chat_write_file(ctrl, path, buffer) + return cryptoArgsResult(res) +} + +/** + * Read buffer from encrypted file + */ +export async function chatReadFile(path: string, {fileKey, fileNonce}: CryptoArgs): Promise { + return await simplex.chat_read_file(path, fileKey, fileNonce) +} + +/** + * Encrypt file + */ +export async function chatEncryptFile(ctrl: bigint, fromPath: string, toPath: string): Promise { + const res = await simplex.chat_encrypt_file(ctrl, fromPath, toPath) + return cryptoArgsResult(res) +} + +/** + * Decrypt file + */ +export async function chatDecryptFile(fromPath: string, {fileKey, fileNonce}: CryptoArgs, toPath: string): Promise { + const res = await simplex.chat_decrypt_file(fromPath, fileKey, fileNonce, toPath) + if (res !== "") throw new Error(res) +} + +function cryptoArgsResult(res: string): CryptoArgs { + const json = JSON.parse(res) + switch (json.type) { + case "result": return json.cryptoArgs as CryptoArgs + case "error": throw Error(json.writeError) + default: throw Error("unexpected chat_write_file result: " + res) + } +} + +export interface APIResult { + result?: R + error?: T.ChatError +} + +export class ChatAPIError extends Error { + constructor(public message: string, public chatError: T.ChatError | undefined = undefined) { + super(message) + } +} + +/** + * Migration confirmation mode + */ +export enum MigrationConfirmation { + YesUp = "yesUp", + YesUpDown = "yesUpDown", + Console = "console", + Error = "error" +} + +/** + * File encryption key and nonce + */ +export interface CryptoArgs { + fileKey: string + fileNonce: string +} + +export class ChatInitError extends Error { + constructor(public message: string, public dbMigrationError: DBMigrationError) { + super(message) + } +} + +export type DBMigrationError = + | DBMigrationError.InvalidConfirmation + | DBMigrationError.ErrorNotADatabase // invalid/corrupt database file or incorrect encryption key + | DBMigrationError.ErrorMigration + | DBMigrationError.ErrorSQL + +export namespace DBMigrationError { + export type Tag = "invalidConfirmation" | "errorNotADatabase" | "errorMigration" | "errorSQL" + + interface Interface { + type: Tag + } + + export interface InvalidConfirmation extends Interface { + type: "invalidConfirmation" + } + + export interface ErrorNotADatabase extends Interface { + type: "errorNotADatabase" + dbFile: string + } + + export interface ErrorMigration extends Interface { + type: "errorMigration" + dbFile: string + migrationError: MigrationError + } + + export interface ErrorSQL extends Interface { + type: "errorSQL" + dbFile: string + migrationSQLError: string + } +} + +export type MigrationError = + | MigrationError.MEUpgrade + | MigrationError.MEDowngrade + | MigrationError.MigrationError + +export namespace MigrationError { + export type Tag = "upgrade" | "downgrade" | "migrationError" + + interface Interface { + type: Tag + } + + export interface MEUpgrade extends Interface { + type: "upgrade" + upMigrations: UpMigration + } + + export interface MEDowngrade extends Interface { + type: "downgrade" + downMigrations: string[] + } + + export interface MigrationError extends Interface { + type: "migrationError" + mtrError: MTRError + } +} + +export interface UpMigration { + upName: string + withDown: boolean +} + +export type MTRError = + | MTRError.MTRENoDown + | MTRError.MTREDifferent + +export namespace MTRError { + export type Tag = "noDown" | "different" + + interface Interface { + type: Tag + } + + export interface MTRENoDown extends Interface { + type: "noDown" + upMigrations: UpMigration + } + + export interface MTREDifferent extends Interface { + type: "different" + downMigrations: string[] + } +} diff --git a/packages/simplex-chat-nodejs/src/download-libs.js b/packages/simplex-chat-nodejs/src/download-libs.js new file mode 100644 index 0000000000..6b8f583155 --- /dev/null +++ b/packages/simplex-chat-nodejs/src/download-libs.js @@ -0,0 +1,224 @@ +const https = require('https'); +const fs = require('fs'); +const path = require('path'); +const extract = require('extract-zip'); + +const GITHUB_REPO = 'simplex-chat/simplex-chat-libs'; +const RELEASE_TAG = 'v6.5.0-beta.4'; +const ROOT_DIR = process.cwd(); // Root of the package being installed +const LIBS_DIR = path.join(ROOT_DIR, 'libs') +const INSTALLED_FILE = path.join(LIBS_DIR, 'installed.txt'); + +// Detect platform and architecture +function getPlatformInfo() { + const platform = process.platform; + const arch = process.arch; + + let platformName; + let archName; + + if (platform === 'linux') { + platformName = 'linux'; + } else if (platform === 'darwin') { + platformName = 'macos'; + } else if (platform === 'win32') { + platformName = 'windows'; + } else { + throw new Error(`Unsupported platform: ${platform}`); + } + + if (arch === 'x64') { + archName = 'x86_64'; + } else if (arch === 'arm64') { + archName = 'aarch64'; + } else { + throw new Error(`Unsupported architecture: ${arch}`); + } + + return { platformName, archName }; +} + +// Cleanup on libs version mismatch +function cleanLibsDirectory() { + if (fs.existsSync(LIBS_DIR)) { + console.log('Cleaning old libraries...'); + fs.rmSync(LIBS_DIR, { recursive: true, force: true }); + fs.mkdirSync(LIBS_DIR, { recursive: true }); + console.log('✓ Old libraries removed'); + } +} + +// Check if libraries are already installed with the correct version +function isAlreadyInstalled() { + if (!fs.existsSync(INSTALLED_FILE)) { + return false; + } + + try { + const installedVersion = fs.readFileSync(INSTALLED_FILE, 'utf-8').trim(); + if (installedVersion === RELEASE_TAG) { + console.log(`✓ Libraries version ${RELEASE_TAG} already installed`); + return true; + } else { + console.log(`Version mismatch: installed ${installedVersion}, need ${RELEASE_TAG}`); + cleanLibsDirectory(); + return false; + } + } catch (err) { + console.warn(`Could not read installed.txt: ${err.message}`); + return false; + } +} + +async function install() { + try { + // Check if already installed + if (isAlreadyInstalled()) { + return; + } + + const { platformName, archName } = getPlatformInfo(); + const repoName = GITHUB_REPO.split('/')[1]; + const zipFilename = `${repoName}-${platformName}-${archName}.zip`; + const ZIP_URL = `https://github.com/${GITHUB_REPO}/releases/download/${RELEASE_TAG}/${zipFilename}`; + const ZIP_PATH = path.join(ROOT_DIR, zipFilename); + const TEMP_EXTRACT_DIR = path.join(ROOT_DIR, '.temp-extract'); + + console.log(`Detected: ${platformName} ${archName}`); + console.log(`Downloading: ${zipFilename}`); + + // Create libs directory + if (!fs.existsSync(LIBS_DIR)) { + fs.mkdirSync(LIBS_DIR, { recursive: true }); + } + + // Download zip with error handling + await downloadFile(ZIP_URL, ZIP_PATH); + + // Extract to temporary directory + console.log('Extracting to temporary directory...'); + if (!fs.existsSync(TEMP_EXTRACT_DIR)) { + fs.mkdirSync(TEMP_EXTRACT_DIR, { recursive: true }); + } + await extract(ZIP_PATH, { dir: TEMP_EXTRACT_DIR }); + + // Move libs folder contents to final location + console.log('Moving libraries to libs/...'); + const libsSourcePath = path.join(TEMP_EXTRACT_DIR, 'libs'); + + if (fs.existsSync(libsSourcePath)) { + // Copy all files from libs folder to LIBS_DIR + const files = fs.readdirSync(libsSourcePath); + files.forEach(file => { + const src = path.join(libsSourcePath, file); + const dest = path.join(LIBS_DIR, file); + + if (fs.statSync(src).isDirectory()) { + copyDirSync(src, dest); + } else { + fs.copyFileSync(src, dest); + } + }); + } else { + throw new Error('libs folder not found in zip archive'); + } + + // Write installed.txt with version + fs.writeFileSync(INSTALLED_FILE, RELEASE_TAG, 'utf-8'); + console.log(`✓ Wrote version ${RELEASE_TAG} to installed.txt`); + + // Cleanup + fs.rmSync(TEMP_EXTRACT_DIR, { recursive: true, force: true }); + fs.unlinkSync(ZIP_PATH); + console.log('✓ Installation complete'); + } catch (err) { + console.error('✗ Failed:', err.message); + process.exit(1); + } +} + +// Helper function to recursively copy directories +function copyDirSync(src, dest) { + if (!fs.existsSync(dest)) { + fs.mkdirSync(dest, { recursive: true }); + } + const files = fs.readdirSync(src); + files.forEach(file => { + const srcFile = path.join(src, file); + const destFile = path.join(dest, file); + if (fs.statSync(srcFile).isDirectory()) { + copyDirSync(srcFile, destFile); + } else { + fs.copyFileSync(srcFile, destFile); + } + }); +} + +function downloadFile(url, dest) { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(dest); + + https.get(url, { headers: { 'User-Agent': 'Node.js' } }, (response) => { + // Handle redirects + if (response.statusCode === 302 || response.statusCode === 301) { + file.destroy(); + fs.unlink(dest, () => {}); + return downloadFile(response.headers.location, dest) + .then(resolve) + .catch(reject); + } + + // Handle 404 + if (response.statusCode === 404) { + file.destroy(); + fs.unlink(dest, () => {}); + reject(new Error( + `Release artifact not found (404). Check:\n` + + ` - Repository exists: ${url.split('/releases')[0]}\n` + + ` - Release tag exists: ${RELEASE_TAG}\n` + + ` - Artifact filename is correct` + )); + return; + } + + // Handle 403 + if (response.statusCode === 403) { + file.destroy(); + fs.unlink(dest, () => {}); + reject(new Error( + `Access denied (403). The repository may be private.\n` + + `Set GITHUB_TOKEN environment variable for private repos.` + )); + return; + } + + // Handle other HTTP errors + if (response.statusCode < 200 || response.statusCode >= 300) { + file.destroy(); + fs.unlink(dest, () => {}); + reject(new Error( + `HTTP ${response.statusCode}: Failed to download from ${url}` + )); + return; + } + + response.pipe(file); + + file.on('finish', () => { + file.close(); + resolve(); + }); + + file.on('error', (err) => { + fs.unlink(dest, () => {}); + reject(new Error(`File write error: ${err.message}`)); + }); + }).on('error', (err) => { + file.destroy(); + fs.unlink(dest, () => {}); + reject(new Error(`Download error: ${err.message}`)); + }); + }); +} + +install(); diff --git a/packages/simplex-chat-nodejs/src/index.ts b/packages/simplex-chat-nodejs/src/index.ts new file mode 100644 index 0000000000..80180ae282 --- /dev/null +++ b/packages/simplex-chat-nodejs/src/index.ts @@ -0,0 +1,22 @@ +/** + * A simple declarative API to run a chat-bot with a single function call. + * It automates creating and updating of the bot profile, address and bot commands shown in the app UI. + */ +export * as bot from "./bot" + +/** + * An API to send chat commands and receive chat events to/from chat core. + * You need to use it in bot event handlers, and for any other use cases. + */ +export * as api from "./api" + +/** + * A low level API to the core library - the same that is used in desktop clients. + * You are unlikely to ever need to use this module directly. + */ +export * as core from "./core" + +/** + * Useful functions for chat events and types. + */ +export * as util from "./util" diff --git a/packages/simplex-chat-nodejs/src/simplex.d.ts b/packages/simplex-chat-nodejs/src/simplex.d.ts new file mode 100644 index 0000000000..10c2f6608a --- /dev/null +++ b/packages/simplex-chat-nodejs/src/simplex.d.ts @@ -0,0 +1,10 @@ +// These functions are defined in CPP add-on ../cpp/simplex.cc + +export function chat_migrate_init(dbPath: string, dbKey: string, confirm: string): Promise<[bigint, string]> +export function chat_close_store(ctrl: bigint): Promise +export function chat_send_cmd(ctrl: bigint, cmd: string): Promise +export function chat_recv_msg_wait(ctrl: bigint, wait: number): Promise +export function chat_write_file(ctrl: bigint, path: string, buffer: ArrayBuffer): Promise +export function chat_read_file(path: string, key: string, nonce: string): Promise +export function chat_encrypt_file(ctrl: bigint, fromPath: string, toPath: string): Promise +export function chat_decrypt_file(fromPath: string, key: string, nonce: string, toPath: string): Promise diff --git a/packages/simplex-chat-nodejs/src/simplex.js b/packages/simplex-chat-nodejs/src/simplex.js new file mode 100644 index 0000000000..fd26c67438 --- /dev/null +++ b/packages/simplex-chat-nodejs/src/simplex.js @@ -0,0 +1 @@ +module.exports = require("../build/Release/simplex") diff --git a/packages/simplex-chat-nodejs/src/util.ts b/packages/simplex-chat-nodejs/src/util.ts new file mode 100644 index 0000000000..52e7843a95 --- /dev/null +++ b/packages/simplex-chat-nodejs/src/util.ts @@ -0,0 +1,92 @@ +import {T} from "@simplex-chat/types" +import {BotAddressSettings} from "./api" + +export function chatInfoRef(cInfo: T.ChatInfo): T.ChatRef | undefined { + switch (cInfo.type) { + case T.ChatType.Direct: return {chatType: T.ChatType.Direct, chatId: cInfo.contact.contactId} + case T.ChatType.Group: { + const chatScope: T.GroupChatScope | undefined = + cInfo.groupChatScope?.type == "memberSupport" + ? {type: "memberSupport", groupMemberId_: cInfo.groupChatScope.groupMember_?.groupMemberId} + : undefined + return {chatType: T.ChatType.Group, chatId: cInfo.groupInfo.groupId, chatScope} + } + default: return undefined + } +} + +export function chatInfoName(cInfo: T.ChatInfo): string { + switch (cInfo.type) { + case "direct": return `@${cInfo.contact.profile.displayName}` + case "group": { + const scope = cInfo.groupChatScope + const scopeName = scope?.type === "memberSupport" + ? `(support${scope.groupMember_ ? ` ${scope.groupMember_.memberProfile.displayName}` : ""})` + : "" + return `#${cInfo.groupInfo.groupProfile.displayName}${scopeName}` + } + case "local": return "private notes" + case "contactRequest": return `request from @${cInfo.contactRequest.profile.displayName}` + case "contactConnection": { + const alias = cInfo.contactConnection.localAlias + return `pending connection${alias ? ` (@${alias})` : ""}` + } + } +} + +export function senderName(cInfo: T.ChatInfo, chatDir: T.CIDirection) { + const sender = chatDir.type === "groupRcv" + ? ` @${chatDir.groupMember.memberProfile.displayName}` + : "" + return chatInfoName(cInfo) + sender +} + +export function contactAddressStr(link: T.CreatedConnLink): string { + return link.connShortLink || link.connFullLink +} + +export function botAddressSettings({addressSettings}: T.UserContactLink): BotAddressSettings { + return { + autoAccept: addressSettings.autoAccept ? true : false, + welcomeMessage: addressSettings.autoReply, + businessAddress: addressSettings.businessAddress + } +} + +export function fromLocalProfile({displayName, fullName, shortDescr, image, contactLink, preferences, peerType}: T.LocalProfile): T.Profile { + const profile = {displayName, fullName, shortDescr, image, contactLink, preferences, peerType} + for (const key in profile) { + if (typeof (profile as any)[key] === "undefined") delete (profile as any)[key] + } + return profile +} + +export function ciContentText({content}: T.ChatItem): string | undefined { + switch (content.type) { + case "sndMsgContent": return content.msgContent.text; + case "rcvMsgContent": return content.msgContent.text; + default: return undefined; + } +} + +export interface BotCommand { + keyword: string + params: string +} + +// returns command (without /) and trimmed parameters +export function ciBotCommand(chatItem: T.ChatItem): BotCommand | undefined { + const msg = ciContentText(chatItem)?.trim() + if (msg) { + const r = msg.match(/\/([^\s]+)(.*)/) + if (r && r.length >= 3) { + return {keyword: r[1], params: r[2].trim()} + } + } + return undefined +} + +export function reactionText(reaction: T.ACIReaction): string { + const r = reaction.chatReaction + return r.reaction.type === "emoji" ? r.reaction.emoji : r.reaction.tag +} diff --git a/packages/simplex-chat-nodejs/tests/api.test.ts b/packages/simplex-chat-nodejs/tests/api.test.ts new file mode 100644 index 0000000000..52153ecfed --- /dev/null +++ b/packages/simplex-chat-nodejs/tests/api.test.ts @@ -0,0 +1,67 @@ +import * as path from "path" +import * as fs from "fs" +import {CEvt, T} from "@simplex-chat/types" +import {api} from ".." + +const CT = T.ChatType + +describe("API tests (use preset servers)", () => { + const tmpDir = "./tests/tmp" + const alicePath = path.join(tmpDir, "alice") + const bobPath = path.join(tmpDir, "bob") + + beforeEach(() => fs.mkdirSync(tmpDir, {recursive: true})) + afterEach(() => fs.rmSync(tmpDir, {recursive: true, force: true})) + + it("should send/receive message", async () => { + // create users and start chat controllers + const alice = await api.ChatApi.init(alicePath) + const bob = await api.ChatApi.init(bobPath) + const servers: string[] = [] + let eventCount = 0 + alice.on("hostConnected" as CEvt.Tag, async ({transportHost}: any) => { servers.push(transportHost) }) + alice.onAny(async () => { eventCount++ }) + await expect(alice.apiGetActiveUser()).resolves.toBeUndefined() + const aliceUser = await alice.apiCreateActiveUser({displayName: "alice", fullName: ""}) + await expect(alice.apiGetActiveUser()).resolves.toMatchObject(aliceUser) + await bob.apiCreateActiveUser({displayName: "bob", fullName: ""}) + await alice.startChat() + await bob.startChat() + // connect via link + const link = await alice.apiCreateLink(aliceUser.userId) + await expect(bob.apiConnectActiveUser(link)).resolves.toBe(api.ConnReqType.Invitation) + const [bobContact, aliceContact] = await Promise.all([ + (await alice.wait("contactConnected")).contact, + (await bob.wait("contactConnected")).contact + ]) + expect(bobContact).toMatchObject({profile: {displayName: "bob"}}) + expect(aliceContact).toMatchObject({profile: {displayName: "alice"}}) + // exchange messages + const isMessage = ({contactId}: T.Contact, msg: string) => (evt: CEvt.NewChatItems) => + evt.chatItems.some(ci => ci.chatInfo.type === CT.Direct && ci.chatInfo.contact.contactId === contactId && ci.chatItem.meta.itemText === msg) + await alice.apiSendTextMessage([CT.Direct, bobContact.contactId], "hello") + await bob.wait("newChatItems", isMessage(aliceContact, "hello")) + await bob.apiSendTextMessage([CT.Direct, aliceContact.contactId], "hello too") + await alice.wait("newChatItems", isMessage(bobContact, "hello too"), 10000) + await alice.apiSendTextMessage([CT.Direct, bobContact.contactId], "how are you?") + await bob.wait("newChatItems", isMessage(aliceContact, "how are you?")) + await bob.apiSendTextMessage([CT.Direct, aliceContact.contactId], "ok, and you?") + await alice.wait("newChatItems", isMessage(bobContact, "ok, and you?"), 10000) + // no more messages + await expect(alice.wait("newChatItems", 500)).resolves.toBeUndefined() + await expect(bob.wait("newChatItems", 500)).resolves.toBeUndefined() + // delete contacts, stop chat controllers and close databases + await alice.apiDeleteChat(CT.Direct, bobContact.contactId) + await bob.wait("contactDeletedByContact") + await bob.apiDeleteChat(CT.Direct, aliceContact.contactId) + await alice.stopChat() + await bob.stopChat() + await alice.close() + await bob.close() + await expect(alice.startChat).rejects.toThrow() + await expect(bob.startChat).rejects.toThrow() + expect(servers.length).toBe(2) + expect(servers[0] !== servers[1]).toBe(true) + expect(eventCount > 0).toBe(true) + }, 30000) +}) diff --git a/packages/simplex-chat-nodejs/tests/bot.test.ts b/packages/simplex-chat-nodejs/tests/bot.test.ts new file mode 100644 index 0000000000..b1fd9d0186 --- /dev/null +++ b/packages/simplex-chat-nodejs/tests/bot.test.ts @@ -0,0 +1,60 @@ +import * as path from "path" +import * as fs from "fs" +import * as assert from "assert" +import {CEvt, T} from "@simplex-chat/types" +import {api, bot, util} from ".." + +const CT = T.ChatType + +describe("Bot tests (use preset servers)", () => { + const tmpDir = "./tests/tmp" + const botPath = path.join(tmpDir, "bot") + const alicePath = path.join(tmpDir, "alice") + + beforeEach(() => fs.mkdirSync(tmpDir, {recursive: true})) + afterEach(() => fs.rmSync(tmpDir, {recursive: true, force: true})) + + it("should reply to messages", async () => { + // run bot + const [chat, botUser, botAddress] = await bot.run({ + profile: {displayName: "Squaring bot", fullName: ""}, + dbOpts: {dbFilePrefix: botPath, dbKey: ""}, + options: { + addressSettings: {welcomeMessage: "If you send me a number, I will calculate its square."}, + }, + onMessage: async (ci, content) => { + const n = +content.text + const reply = typeof n === "number" && !isNaN(n) ? `${n} * ${n} = ${n * n}` : `this is not a number` + await chat.apiSendTextReply(ci, reply) + } + }) + assert(typeof botAddress === "object") + // create user + const alice = await api.ChatApi.init(alicePath) + const aliceUser = await alice.apiCreateActiveUser({displayName: "alice", fullName: ""}) + await alice.startChat() + // connect to bot + const [plan, link] = await alice.apiConnectPlan(aliceUser.userId, util.contactAddressStr(botAddress.connLinkContact)) + assert(plan.type === "contactAddress") + await expect(alice.apiConnect(aliceUser.userId, false, link)).resolves.toBe(api.ConnReqType.Contact) + const [botContact, aliceContact] = await Promise.all([ + (await alice.wait("contactConnected")).contact, + (await chat.wait("contactConnected")).contact + ]) + expect(botContact.profile.displayName).toBe("Squaring bot") + // send message to bot + const isMessage = ({contactId}: T.Contact, msg: string) => (evt: CEvt.NewChatItems) => + evt.chatItems.some(ci => ci.chatInfo.type === CT.Direct && ci.chatInfo.contact.contactId === contactId && ci.chatItem.meta.itemText === msg) + await alice.apiSendTextMessage([CT.Direct, botContact.contactId], "2") + console.log("after sending message") + await alice.wait("newChatItems", isMessage(botContact, "2 * 2 = 4"), 5000) + // cleanup + await alice.apiDeleteChat(CT.Direct, botContact.contactId) + await chat.wait("contactDeletedByContact", ({contact}) => contact.contactId === aliceContact.contactId) + await chat.apiDeleteUserAddress(botUser.userId) + await chat.stopChat() + await chat.close() + await alice.stopChat() + await alice.close() + }, 30000) +}) diff --git a/packages/simplex-chat-nodejs/tests/core.test.ts b/packages/simplex-chat-nodejs/tests/core.test.ts new file mode 100644 index 0000000000..141f35746d --- /dev/null +++ b/packages/simplex-chat-nodejs/tests/core.test.ts @@ -0,0 +1,85 @@ +import * as fs from "fs"; +import * as path from "path"; +import {core} from "../src/index"; + +describe("Core tests", () => { + const tmpDir = "./tests/tmp"; + const dbPath = path.join(tmpDir, "simplex_v1"); + + beforeEach(() => fs.mkdirSync(tmpDir, {recursive: true})); + afterEach(() => fs.rmSync(tmpDir, {recursive: true, force: true})); + + it("should initialize chat controller", async () => { + const ctrl = await core.chatMigrateInit(dbPath, "key", core.MigrationConfirmation.YesUp); + expect(typeof ctrl).toBe("bigint"); + await expect(core.chatCloseStore(ctrl)).resolves.toBe(undefined); + + await expect(core.chatMigrateInit(dbPath, "wrong_key", core.MigrationConfirmation.YesUp)).rejects.toMatchObject({ + message: "Database or migration error (see dbMigrationError property)", + dbMigrationError: expect.objectContaining({type: "errorNotADatabase"}) + }); + }); + + it("should send command and receive event", async () => { + const ctrl = await core.chatMigrateInit(dbPath, "key", core.MigrationConfirmation.YesUp); + + await expect(core.chatSendCmd(ctrl, "/v")).resolves.toMatchObject({ + type: "versionInfo" + }); + await expect(core.chatSendCmd(ctrl, '/debug event {"type": "chatSuspended"}')).resolves.toMatchObject({ + type: "cmdOk" + }); + + const wait = 500_000; + await expect(core.chatRecvMsgWait(ctrl, wait)).resolves.toMatchObject({ + type: "chatSuspended" + }); + await expect(core.chatRecvMsgWait(ctrl, wait)).resolves.toBe(undefined); + + await expect(core.chatSendCmd(ctrl, "/unknown")).rejects.toMatchObject({ + message: "Chat command error (see chatError property)", + chatError: expect.objectContaining({type: "error"}) + }); + + await core.chatCloseStore(ctrl); + }); + + it("should write/read encrypted file from/to buffer", async () => { + const ctrl = await core.chatMigrateInit(dbPath, "key", core.MigrationConfirmation.YesUp); + + const filePath = path.join(tmpDir, "write_file.txt"); + const buffer = new Uint8Array([0, 1, 2]).buffer; + const cryptoArgs = await core.chatWriteFile(ctrl, filePath, buffer); + expect(typeof cryptoArgs.fileKey).toBe("string"); + expect(typeof cryptoArgs.fileNonce).toBe("string"); + + const buffer2 = await core.chatReadFile(filePath, cryptoArgs); + expect(Buffer.from(buffer2).equals(Buffer.from(buffer))).toBe(true); + + await expect(core.chatWriteFile(ctrl, path.join(tmpDir, "unknown", "unknown.txt"), buffer)).rejects.toThrow(); + await expect(core.chatReadFile(path.join(tmpDir, "unknown.txt"), cryptoArgs)).rejects.toThrow(); + + await core.chatCloseStore(ctrl); + }); + + it("should encrypt/decrypt file", async () => { + const ctrl = await core.chatMigrateInit(dbPath, "key", core.MigrationConfirmation.YesUp); + + const unencryptedPath = path.join(tmpDir, "file_unencrypted.txt"); + fs.writeFileSync(unencryptedPath, "unencrypted\n"); + const encryptedPath = path.join(tmpDir, "file_encrypted.txt"); + const cryptoArgs = await core.chatEncryptFile(ctrl, unencryptedPath, encryptedPath); + expect(typeof cryptoArgs.fileKey).toBe("string"); + expect(typeof cryptoArgs.fileNonce).toBe("string"); + + const decryptedPath: string = path.join(tmpDir, "file_decrypted.txt"); + await expect(core.chatDecryptFile(encryptedPath, cryptoArgs, decryptedPath)).resolves.toBe(undefined); + + expect(fs.readFileSync(decryptedPath, "utf8")).toBe("unencrypted\n"); + + await expect(core.chatEncryptFile(ctrl, path.join(tmpDir, "unknown.txt"), encryptedPath)).rejects.toThrow(); + await expect(core.chatDecryptFile(path.join(tmpDir, "unknown.txt"), cryptoArgs, decryptedPath)).rejects.toThrow(); + + await core.chatCloseStore(ctrl); + }); +}); diff --git a/packages/simplex-chat-nodejs/tests/tsconfig.json b/packages/simplex-chat-nodejs/tests/tsconfig.json new file mode 100644 index 0000000000..47e973f3af --- /dev/null +++ b/packages/simplex-chat-nodejs/tests/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "include": ["**/*", "../src/**/*"] +} diff --git a/packages/simplex-chat-nodejs/tsconfig.json b/packages/simplex-chat-nodejs/tsconfig.json new file mode 100644 index 0000000000..f5dc986431 --- /dev/null +++ b/packages/simplex-chat-nodejs/tsconfig.json @@ -0,0 +1,23 @@ +{ + "include": ["src"], + "compilerOptions": { + "declaration": true, + "forceConsistentCasingInFileNames": true, + "lib": ["ES2018"], + "module": "CommonJS", + "moduleResolution": "Node", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noEmitOnError": true, + "outDir": "dist", + "sourceMap": true, + "strict": true, + "strictNullChecks": true, + "target": "ES2018", + "types": ["node"] + } +} diff --git a/packages/simplex-chat-nodejs/typedoc.json b/packages/simplex-chat-nodejs/typedoc.json new file mode 100644 index 0000000000..3523115f49 --- /dev/null +++ b/packages/simplex-chat-nodejs/typedoc.json @@ -0,0 +1,14 @@ +{ + "name": "simplex-chat", + "plugin": ["typedoc-plugin-markdown"], + "entryPoints": [ + "./src/index.ts", + "../simplex-chat-client/types/typescript/src/index.ts" + ], + "entryPointStrategy": "expand", + "tsconfig": "./tsconfig.json", + "sourceLinkTemplate": "../{path}#L{line}", + "disableGit": true, + "flattenOutputFiles": true, + "out": "./docs" +} \ No newline at end of file