mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-02 15:41:44 +00:00
Merge branch 'master' into chat-relays
This commit is contained in:
@@ -582,3 +582,74 @@ jobs:
|
||||
bin_hash: ${{ steps.windows_desktop_build.outputs.package_hash }}
|
||||
github_ref: ${{ github.ref }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# =========================
|
||||
# NodeJS libs release
|
||||
# =========================
|
||||
|
||||
# Downloads Desktop builds, extracts and archives libraries for NodeJS addon.
|
||||
# Depends on Linux/MacOS, executes only on release.
|
||||
|
||||
# Secrets:
|
||||
# -------
|
||||
# NODEJS_REPO_TOKEN
|
||||
# Only select repositories: simplex-chat-libs
|
||||
# Permissions:
|
||||
# * Contents (Read and Write)
|
||||
|
||||
release-nodejs-libs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-linux, build-macos]
|
||||
if: startsWith(github.ref, 'refs/tags/v') && (!cancelled())
|
||||
steps:
|
||||
- name: Checkout current repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Build archives
|
||||
run: |
|
||||
INIT_DIR='${{ runner.temp }}/artifacts'
|
||||
RELEASE_DIR='${{ runner.temp }}/release-assets'
|
||||
TAG='${{ github.ref_name }}'
|
||||
URL='https://github.com/${{ github.repository }}/releases/download'
|
||||
PREFIX='${{ github.event.repository.name }}-libs'
|
||||
|
||||
# Setup directories
|
||||
mkdir "$INIT_DIR" "$RELEASE_DIR" && cd "$INIT_DIR"
|
||||
|
||||
# Downlaod desktop release
|
||||
curl --proto '=https' --tlsv1.2 -sSf -L "${URL}/${TAG}/simplex-desktop-ubuntu-22_04-x86_64.deb" -o linux.deb
|
||||
curl --proto '=https' --tlsv1.2 -sSf -L "${URL}/${TAG}/simplex-desktop-macos-aarch64.dmg" -o macos-aarch64.dmg
|
||||
curl --proto '=https' --tlsv1.2 -sSf -L "${URL}/${TAG}/simplex-desktop-macos-x86_64.dmg" -o macos-x86_64.dmg
|
||||
|
||||
# Linux
|
||||
# -----
|
||||
# Extract libraries
|
||||
dpkg-deb -R linux.deb linux-out/ && cd linux-out/opt/simplex/lib/app/resources
|
||||
# Preprare directory
|
||||
mkdir libs && cp *.so libs/
|
||||
# Archive
|
||||
zip -r "${PREFIX}-linux-x86_64.zip" libs
|
||||
# Back to original dir
|
||||
mv "${PREFIX}-linux-x86_64.zip" "$RELEASE_DIR" && cd "$INIT_DIR"
|
||||
|
||||
# MacOS: aarch64
|
||||
# --------------
|
||||
7z x macos-aarch64.dmg -omacos1-out/ && cd macos1-out/SimpleX/SimpleX.app/Contents/app/resources/
|
||||
mkdir libs && cp *.dylib libs/
|
||||
zip -r "${PREFIX}-macos-aarch64.zip" libs
|
||||
mv "${PREFIX}-macos-aarch64.zip" "$RELEASE_DIR" && cd "$INIT_DIR"
|
||||
|
||||
# Macos: x86_64
|
||||
# -------------
|
||||
7z x macos-x86_64.dmg -omacos2-out/ && cd macos2-out/SimpleX/SimpleX.app/Contents/app/resources/
|
||||
mkdir libs && cp *.dylib libs/
|
||||
zip -r "${PREFIX}-macos-x86_64.zip" libs
|
||||
mv "${PREFIX}-macos-x86_64.zip" "$RELEASE_DIR" && cd "$INIT_DIR"
|
||||
|
||||
- name: Create release in libs repo and upload artifacts
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
repository: ${{ github.repository }}-libs
|
||||
tag_name: ${{ github.ref_name }}
|
||||
files: ${{ runner.temp }}/release-assets/*
|
||||
token: ${{ secrets.NODEJS_REPO_TOKEN }}
|
||||
|
||||
@@ -81,3 +81,4 @@ website/.cache
|
||||
website/test/stubs-layout-cache/_includes/*.js
|
||||
apps/android/app/release
|
||||
apps/multiplatform/.kotlin/sessions
|
||||
|
||||
|
||||
@@ -178,8 +178,8 @@
|
||||
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.8-6ZoDNN7rGWGJ2cn2Wz03rA-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.8-6ZoDNN7rGWGJ2cn2Wz03rA-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.8-6ZoDNN7rGWGJ2cn2Wz03rA.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.8-6ZoDNN7rGWGJ2cn2Wz03rA.a */; };
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
|
||||
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
|
||||
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
|
||||
@@ -545,8 +545,8 @@
|
||||
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a"; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.8-6ZoDNN7rGWGJ2cn2Wz03rA-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.8-6ZoDNN7rGWGJ2cn2Wz03rA-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.8-6ZoDNN7rGWGJ2cn2Wz03rA.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.8-6ZoDNN7rGWGJ2cn2Wz03rA.a"; sourceTree = "<group>"; };
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
|
||||
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
|
||||
@@ -708,8 +708,8 @@
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.8-6ZoDNN7rGWGJ2cn2Wz03rA-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.8-6ZoDNN7rGWGJ2cn2Wz03rA.a in Frameworks */,
|
||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -795,8 +795,8 @@
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */,
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */,
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.7-CDRaHJn7uof5tglscSjQL5.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.8-6ZoDNN7rGWGJ2cn2Wz03rA-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.8-6ZoDNN7rGWGJ2cn2Wz03rA.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -2003,7 +2003,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 318;
|
||||
CURRENT_PROJECT_VERSION = 319;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -2053,7 +2053,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 318;
|
||||
CURRENT_PROJECT_VERSION = 319;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -2095,7 +2095,7 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 318;
|
||||
CURRENT_PROJECT_VERSION = 319;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
@@ -2115,7 +2115,7 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 318;
|
||||
CURRENT_PROJECT_VERSION = 319;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
@@ -2140,7 +2140,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 318;
|
||||
CURRENT_PROJECT_VERSION = 319;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
@@ -2177,7 +2177,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 318;
|
||||
CURRENT_PROJECT_VERSION = 319;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
@@ -2214,7 +2214,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 318;
|
||||
CURRENT_PROJECT_VERSION = 319;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2265,7 +2265,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 318;
|
||||
CURRENT_PROJECT_VERSION = 319;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2316,7 +2316,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 318;
|
||||
CURRENT_PROJECT_VERSION = 319;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -2350,7 +2350,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 318;
|
||||
CURRENT_PROJECT_VERSION = 319;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
|
||||
@@ -24,13 +24,13 @@ android.nonTransitiveRClass=true
|
||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||
kotlin.jvm.target=11
|
||||
|
||||
android.version_name=6.5-beta.3
|
||||
android.version_code=331
|
||||
android.version_name=6.5-beta.4
|
||||
android.version_code=332
|
||||
|
||||
android.bundle=false
|
||||
|
||||
desktop.version_name=6.5-beta.3
|
||||
desktop.version_code=128
|
||||
desktop.version_name=6.5-beta.4
|
||||
desktop.version_code=129
|
||||
|
||||
kotlin.version=2.1.20
|
||||
gradle.plugin.version=8.7.0
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -153,6 +153,7 @@ This file is generated automatically.
|
||||
- [SndGroupEvent](#sndgroupevent)
|
||||
- [SrvError](#srverror)
|
||||
- [StoreError](#storeerror)
|
||||
- [SubscriptionStatus](#subscriptionstatus)
|
||||
- [SwitchPhase](#switchphase)
|
||||
- [TimedMessagesGroupPreference](#timedmessagesgrouppreference)
|
||||
- [TimedMessagesPreference](#timedmessagespreference)
|
||||
@@ -3598,6 +3599,26 @@ WorkItemError:
|
||||
- errContext: string
|
||||
|
||||
|
||||
---
|
||||
|
||||
## SubscriptionStatus
|
||||
|
||||
**Discriminated union type**:
|
||||
|
||||
Active:
|
||||
- type: "active"
|
||||
|
||||
Pending:
|
||||
- type: "pending"
|
||||
|
||||
Removed:
|
||||
- type: "removed"
|
||||
- subError: string
|
||||
|
||||
NoSub:
|
||||
- type: "noSub"
|
||||
|
||||
|
||||
---
|
||||
|
||||
## SwitchPhase
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: 6aadcf1f3fc19cbc0c8be457556fbaaffb0bfc46
|
||||
tag: 58212c421aa6abb6ad894b8231d8a380849b704b
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4307,6 +4307,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",
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
package-lock.json
|
||||
.vscode
|
||||
build/
|
||||
libs/
|
||||
dist/
|
||||
coverage/
|
||||
tmp/
|
||||
@@ -0,0 +1,3 @@
|
||||
libs/
|
||||
build/
|
||||
node_modules/
|
||||
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
<https://www.gnu.org/licenses/>.
|
||||
@@ -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)
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "simplex",
|
||||
"sources": [ "cpp/simplex.cc" ],
|
||||
"include_dirs": [
|
||||
"<!@(node -p \"require('node-addon-api').include\")"
|
||||
],
|
||||
"dependencies": [
|
||||
"<!(node -p \"require('node-addon-api').gyp\")"
|
||||
],
|
||||
"libraries": [
|
||||
"-L<(module_root_dir)/libs",
|
||||
"-lsimplex"
|
||||
],
|
||||
"cflags!": [ "-fno-exceptions" ],
|
||||
"cflags_cc!": [ "-fno-exceptions" ],
|
||||
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
|
||||
"conditions": [
|
||||
["OS=='mac'", {
|
||||
"xcode_settings": {
|
||||
"OTHER_LDFLAGS": [
|
||||
"-Wl,-rpath,@loader_path/../../libs"
|
||||
]
|
||||
}
|
||||
}],
|
||||
["OS=='linux'", {
|
||||
"ldflags": [
|
||||
"-Wl,-rpath,'$$ORIGIN'/../../libs"
|
||||
]
|
||||
}]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
#include <napi.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <cstdlib>
|
||||
#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<char **>(argv);
|
||||
hs_init_with_rtsopts(&argc, &pargv);
|
||||
}
|
||||
|
||||
class ResultAsyncWorker : public AsyncWorker {
|
||||
public:
|
||||
using ExecuteFn = std::function<void(ResultAsyncWorker*)>;
|
||||
using ResultProcessor = std::function<void(ResultAsyncWorker*, Napi::Env)>;
|
||||
|
||||
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<void(BinaryAsyncWorker*)>;
|
||||
|
||||
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<char> buffer = Napi::Buffer<char>::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<uint64_t>(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<Napi::BigInt>();
|
||||
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<chat_ctrl>(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<String>().Utf8Value();
|
||||
std::string key = args[1].As<String>().Utf8Value();
|
||||
std::string confirm = args[2].As<String>().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<uintptr_t>(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<String>().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<int>(args[1].As<Number>().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<String>().Utf8Value();
|
||||
ArrayBuffer ab = args[2].As<ArrayBuffer>();
|
||||
char* data = static_cast<char*>(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<int>(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<String>().Utf8Value();
|
||||
std::string key = args[1].As<String>().Utf8Value();
|
||||
std::string nonce = args[2].As<String>().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<String>().Utf8Value();
|
||||
std::string toPath = args[2].As<String>().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<String>().Utf8Value();
|
||||
std::string key = args[1].As<String>().Utf8Value();
|
||||
std::string nonce = args[2].As<String>().Utf8Value();
|
||||
std::string toPath = args[3].As<String>().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)
|
||||
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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`
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
[**simplex-chat**](README.md)
|
||||
|
||||
***
|
||||
|
||||
[simplex-chat](README.md) / [api](Namespace.api.md) / EventSubscriberFunc
|
||||
|
||||
# Type Alias: EventSubscriberFunc()\<K\>
|
||||
|
||||
> **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`\>
|
||||
@@ -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<K> }`
|
||||
|
||||
Defined in: [src/api.ts:52](../src/api.ts#L52)
|
||||
@@ -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)
|
||||
@@ -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`\]\>
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
+33
@@ -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`
|
||||
@@ -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`
|
||||
+25
@@ -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`
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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`\>
|
||||
@@ -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`\>
|
||||
@@ -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)\>
|
||||
@@ -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`\>
|
||||
@@ -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`\>
|
||||
@@ -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`\>
|
||||
@@ -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`\>
|
||||
@@ -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)\>
|
||||
@@ -0,0 +1,31 @@
|
||||
[**simplex-chat**](README.md)
|
||||
|
||||
***
|
||||
|
||||
[simplex-chat](README.md) / [core](Namespace.core.md) / APIResult
|
||||
|
||||
# Interface: APIResult\<R\>
|
||||
|
||||
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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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`
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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`
|
||||
@@ -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)
|
||||
@@ -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`
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
})()
|
||||
@@ -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)}`)
|
||||
}
|
||||
},
|
||||
})
|
||||
})()
|
||||
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
maxWorkers: 1,
|
||||
testEnvironment: "node",
|
||||
transform: {
|
||||
'^.+\\.ts$': ['ts-jest', {
|
||||
tsconfig: 'tests/tsconfig.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"
|
||||
}
|
||||
@@ -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<K extends CEvt.Tag> = (event: ChatEvent & {type: K}) => void | Promise<void>
|
||||
|
||||
export type EventSubscribers = {[K in CEvt.Tag]?: EventSubscriberFunc<K>}
|
||||
|
||||
interface EventSubscriber<K extends CEvt.Tag> {
|
||||
subscriber: EventSubscriberFunc<K>
|
||||
once: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Main API class for interacting with the chat core library.
|
||||
*/
|
||||
export class ChatApi {
|
||||
private receiveEvents = false
|
||||
private eventsLoop: Promise<void> | undefined = undefined
|
||||
private subscribers: {[K in CEvt.Tag]?: EventSubscriber<K>[]} = {}
|
||||
private receivers: EventSubscriberFunc<CEvt.Tag>[] = []
|
||||
|
||||
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<ChatApi> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
this.receiveEvents = false
|
||||
if (this.eventsLoop) await this.eventsLoop
|
||||
this.eventsLoop = undefined
|
||||
await core.chatCloseStore(this.ctrl)
|
||||
this.ctrl_ = undefined
|
||||
}
|
||||
|
||||
private async runEventsLoop(): Promise<void> {
|
||||
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<typeof event.type>)(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<typeof event.type>)
|
||||
}
|
||||
}
|
||||
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<K extends CEvt.Tag>(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<K extends CEvt.Tag>(event: K, subscriber: EventSubscriberFunc<K>): void
|
||||
on<K extends CEvt.Tag>(events: K | EventSubscribers, subscriber?: EventSubscriberFunc<K>): void {
|
||||
if (typeof events === "string" && subscriber) {
|
||||
this.on_(events, subscriber)
|
||||
} else {
|
||||
const eventEntries = Object.entries(events) as [CEvt.Tag, EventSubscriberFunc<CEvt.Tag> | undefined][]
|
||||
for (const [event, subscriber] of eventEntries) {
|
||||
if (subscriber) this.on_(event, subscriber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private on_<K extends CEvt.Tag>(event: K, subscriber: EventSubscriberFunc<K>, once: boolean = false): void {
|
||||
const subs: EventSubscriber<K>[] = 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<CEvt.Tag>): 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<K extends CEvt.Tag>(event: K, subscriber: EventSubscriberFunc<K>): void {
|
||||
this.on_(event, subscriber, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for specific event, with an optional predicate.
|
||||
* Returns `undefined` on timeout if specified.
|
||||
*/
|
||||
wait<K extends CEvt.Tag>(event: K): Promise<ChatEvent & {type: K}>
|
||||
wait<K extends CEvt.Tag>(event: K, predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined): Promise<ChatEvent & {type: K}>
|
||||
wait<K extends CEvt.Tag>(event: K, timeout: number): Promise<ChatEvent & {type: K} | undefined>
|
||||
wait<K extends CEvt.Tag>(event: K, predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined, timeout: number): Promise<ChatEvent & {type: K} | undefined>
|
||||
wait<K extends CEvt.Tag>(
|
||||
event: K,
|
||||
predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined | number = undefined, // number for timeout
|
||||
timeout: number = 0 // milliseconds, default - indefinite
|
||||
): Promise<ChatEvent & {type: K} | undefined> {
|
||||
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<K> = 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<K extends CEvt.Tag>(event: K, subscriber: EventSubscriberFunc<K> | 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<CEvt.Tag> | 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<ChatResponse> {
|
||||
return await core.chatSendCmd(this.ctrl, cmd)
|
||||
}
|
||||
|
||||
async recvChatEvent(wait: number = 5_000_000): Promise<ChatEvent | undefined> {
|
||||
return await core.chatRecvMsgWait(this.ctrl, wait)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create bot address.
|
||||
* Network usage: interactive.
|
||||
*/
|
||||
async apiCreateUserAddress(userId: number): Promise<T.CreatedConnLink> {
|
||||
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<void> {
|
||||
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<T.UserContactLink | undefined> {
|
||||
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<T.UserProfileUpdateSummary> {
|
||||
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<void> {
|
||||
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<T.AChatItem[]> {
|
||||
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<T.AChatItem[]> {
|
||||
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<T.AChatItem[]> {
|
||||
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<T.ChatItem> {
|
||||
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<T.ChatItemDeletion[]> {
|
||||
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<T.ChatItemDeletion[]> {
|
||||
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<T.AChatItem> {
|
||||
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<void> {
|
||||
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<T.GroupMember> {
|
||||
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<T.GroupInfo> {
|
||||
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<T.GroupMember> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<T.GroupMember[]> {
|
||||
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<T.GroupInfo> {
|
||||
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<T.GroupMember[]> {
|
||||
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<T.GroupInfo> {
|
||||
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<T.GroupInfo> {
|
||||
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<string> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<T.GroupLink> {
|
||||
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<string> {
|
||||
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<string> {
|
||||
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<ConnReqType> {
|
||||
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<ConnReqType> {
|
||||
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<T.Contact> {
|
||||
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<void> {
|
||||
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<T.Contact[]> {
|
||||
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<T.GroupInfo[]> {
|
||||
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<void> {
|
||||
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<T.User | undefined> {
|
||||
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<T.User> {
|
||||
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<T.UserInfo[]> {
|
||||
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<T.User> {
|
||||
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<void> {
|
||||
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<T.UserProfileUpdateSummary | undefined> {
|
||||
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<void> {
|
||||
const r = await this.sendChatCmd(CC.APISetContactPrefs.cmdString({contactId, preferences}))
|
||||
if (r.type !== "contactPrefsUpdated") throw new ChatCommandError("error setting contact prefs", r)
|
||||
}
|
||||
}
|
||||
@@ -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 <prefix>_chat.db and <prefix>_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<BotOptions> = {
|
||||
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<void>,
|
||||
// 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<void>)},
|
||||
// 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<BotOptions> {
|
||||
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<BotOptions>): 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<void>) {
|
||||
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<void>)}) {
|
||||
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<BotOptions>) {
|
||||
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<T.User> {
|
||||
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<BotOptions>): Promise<T.UserContactLink | undefined> {
|
||||
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<BotOptions>): Promise<void> {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<bigint> {
|
||||
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<void> {
|
||||
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<ChatResponse> {
|
||||
const res = await simplex.chat_send_cmd(ctrl, cmd)
|
||||
const json = JSON.parse(res) as APIResult<ChatResponse>
|
||||
// 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<ChatEvent | undefined> {
|
||||
const res = await simplex.chat_recv_msg_wait(ctrl, wait)
|
||||
if (res === "") return undefined
|
||||
const json = JSON.parse(res) as APIResult<ChatEvent>
|
||||
// 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<CryptoArgs> {
|
||||
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<ArrayBuffer> {
|
||||
return await simplex.chat_read_file(path, fileKey, fileNonce)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt file
|
||||
*/
|
||||
export async function chatEncryptFile(ctrl: bigint, fromPath: string, toPath: string): Promise<CryptoArgs> {
|
||||
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<void> {
|
||||
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<R> {
|
||||
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[]
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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"
|
||||
+10
@@ -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<string>
|
||||
export function chat_send_cmd(ctrl: bigint, cmd: string): Promise<string>
|
||||
export function chat_recv_msg_wait(ctrl: bigint, wait: number): Promise<string>
|
||||
export function chat_write_file(ctrl: bigint, path: string, buffer: ArrayBuffer): Promise<string>
|
||||
export function chat_read_file(path: string, key: string, nonce: string): Promise<ArrayBuffer>
|
||||
export function chat_encrypt_file(ctrl: bigint, fromPath: string, toPath: string): Promise<string>
|
||||
export function chat_decrypt_file(fromPath: string, key: string, nonce: string, toPath: string): Promise<string>
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require("../build/Release/simplex")
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node", "jest"]
|
||||
},
|
||||
"include": ["**/*", "../src/**/*"]
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user