mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-04 10:47:32 +00:00
Merge branch 'master' into nd/ios-icloud-backup
This commit is contained in:
commit
c650381a17
87 files changed
+1768
-290
No files matched your search
+3
-3
@@ -874,10 +874,10 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState
|
||||
} else {
|
||||
val candidate = nameSearchCandidate(it.trim())
|
||||
connectNameCandidate.value = candidate
|
||||
// clear the previous match immediately so the list falls back to text search during the debounce,
|
||||
// instead of showing a stale filtered chat while the new search runs
|
||||
searchChatFilteredBySimplexLink.value = emptySet()
|
||||
if (candidate != null) {
|
||||
// clear the previous match immediately so the list falls back to text search during the debounce,
|
||||
// instead of showing a stale filtered chat while the new search runs
|
||||
searchChatFilteredBySimplexLink.value = emptySet()
|
||||
// resolve the name locally on each keystroke, debounced; collectLatest cancels the in-flight
|
||||
// search when the next keystroke arrives. A bare name can be a contact or a channel, so search
|
||||
// both and filter every known chat found; drop the row only when both types are already known.
|
||||
|
||||
@@ -0,0 +1,472 @@
|
||||
Revision 1, 2026-10-19, draft for comments
|
||||
|
||||
Evgeny Poberezkin
|
||||
|
||||
# Agent protocols over SimpleX
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Abstract](#abstract)
|
||||
- [Conventions](#conventions)
|
||||
- [Overview](#overview)
|
||||
- [Addresses in llms.txt](#addresses-in-llmstxt)
|
||||
- [Profile declarations](#profile-declarations)
|
||||
- [protocols](#protocols)
|
||||
- [Public group audience and update frequency](#public-group-audience-and-update-frequency)
|
||||
- [Contact address peers](#contact-address-peers)
|
||||
- [Protocol names](#protocol-names)
|
||||
- [Chat transport](#chat-transport)
|
||||
- [Commands](#commands)
|
||||
- [Replies](#replies)
|
||||
- [Message size](#message-size)
|
||||
- [Service RPC transport](#service-rpc-transport)
|
||||
- [Exchange](#exchange)
|
||||
- [Request](#request)
|
||||
- [Response](#response)
|
||||
- [Signed requests](#signed-requests)
|
||||
- [MCP binding](#mcp-binding)
|
||||
- [Messages](#messages)
|
||||
- [Versions and capabilities](#versions-and-capabilities)
|
||||
- [Examples](#examples)
|
||||
- [Security considerations](#security-considerations)
|
||||
- [Privacy considerations](#privacy-considerations)
|
||||
- [Implementations](#implementations)
|
||||
- [References](#references)
|
||||
|
||||
## Abstract
|
||||
|
||||
This document defines how software agents find SimpleX addresses and call the tools of other agents and bots over SimpleX:
|
||||
|
||||
- an `llms.txt` section with communication addresses, beside the link relation type and the well-known document of [Publishing SimpleX addresses on websites](./web-discovery.md);
|
||||
- declarations: the protocols a profile serves and their transports, the peer types a channel or a contact address is made for, and the update frequency of a channel;
|
||||
- the chat transport: the command `/_call` in a conversation;
|
||||
- the Service RPC transport: one request and one response, without a conversation, in a generic envelope;
|
||||
- the binding of the Model Context Protocol (MCP) [MCP] to both transports.
|
||||
|
||||
## Conventions
|
||||
|
||||
The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT" and "MAY" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals.
|
||||
|
||||
JSON schemas use JSON Type Definition [RFC8927]. Whitespace in JSON examples is for readability.
|
||||
|
||||
**Bot**: a SimpleX profile operated by software: a bot of the bot library, or an AI agent through `@simplex-chat/mcp`.
|
||||
|
||||
## Overview
|
||||
|
||||
A website owner publishes channels for updates and contact addresses for requests [WEB]. An agent:
|
||||
|
||||
1. reads the addresses from the website: link relations, `/.well-known/simplex.json` or `llms.txt`;
|
||||
2. retrieves the link data with the link, without connecting;
|
||||
3. reads the declarations;
|
||||
4. subscribes to a channel, connects to a contact address, or sends a Service RPC request to it.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant A as Agent
|
||||
participant W as Website
|
||||
participant R as SMP router
|
||||
participant B as Bot
|
||||
|
||||
A->>W: GET /.well-known/simplex.json
|
||||
W-->>A: channels and contacts
|
||||
A->>R: retrieve link data
|
||||
R-->>A: link data with declarations
|
||||
alt chat transport
|
||||
A->>B: connect, then /_call messages
|
||||
B-->>A: replies
|
||||
else Service RPC transport
|
||||
A->>B: request in the connection request
|
||||
B-->>A: response to the reply queue
|
||||
end
|
||||
```
|
||||
|
||||
Two transports carry protocol messages:
|
||||
|
||||
- **chat**: a conversation in a direct chat or a group. The caller connects once and sends commands as messages. The conversation persists between calls.
|
||||
- **rpc**: Service RPC. The request is sent in the connection request to a contact address, and the response to the reply queue of that request. Both sides then delete the connection. A call needs one round trip, and each call uses a new connection.
|
||||
|
||||
## Addresses in llms.txt
|
||||
|
||||
An `llms.txt` file [LLMSTXT] lists communication addresses in a section with the heading `## Communications`. The section is a file list: each item is a Markdown link, then a colon and notes.
|
||||
|
||||
The notes begin with the network and the type of the address, then the audience and, for updates, the update frequency or, for contacts, the protocols. Free text follows:
|
||||
|
||||
```
|
||||
<network> updates for <audience>[, <frequency>]. <info>
|
||||
<network> group for <audience>. <info>
|
||||
<network> [business ]contact for <audience>[, <protocol>]*. <info>
|
||||
```
|
||||
|
||||
- `<network>`: the name of the network, `SimpleX` for SimpleX links.
|
||||
- `updates`: an address that publishes updates. `group`: an address of a group, where members converse. `contact`: an address that accepts conversations and requests. `business contact`: a business address [BUSINESS].
|
||||
- `<audience>`: `people`, `agents`, or `people and agents`.
|
||||
- `<frequency>`: a `changefreq` value of the Sitemaps protocol [SITEMAPS].
|
||||
- `<protocol>`: the name of a protocol the address serves ([protocol names](#protocol-names)), in any letter case.
|
||||
- `<info>`: what the address is about.
|
||||
|
||||
The network is named in the notes because the host in a SimpleX link is the router that hosts the address, and that router can have any domain name.
|
||||
|
||||
For SimpleX entries, the type `updates` requires a channel link, the type `group` a group link, and the types `contact` and `business contact` a contact address link [WEB]. The link text is the title of the address.
|
||||
|
||||
```
|
||||
## Communications
|
||||
|
||||
- [Acme news](https://smp12.simplex.im/c#…): SimpleX updates for people and agents, weekly. Product news and offers.
|
||||
- [Acme customers](https://smp12.simplex.im/g#…): SimpleX group for people. Questions and tips from customers.
|
||||
- [Ask Acme](https://smp12.simplex.im/a#…): SimpleX business contact for agents, MCP. Orders and availability.
|
||||
- [Acme support](mailto:support@acme.example): Email contact for people.
|
||||
```
|
||||
|
||||
## Profile declarations
|
||||
|
||||
A profile or a group declares the protocols it serves in its preferences. A channel or a public group declares its audience in its public group record, and a channel also its update frequency. A contact address declares the peers it serves in its link data.
|
||||
|
||||
### protocols
|
||||
|
||||
`protocols` is a key of the preferences: `preferences` of a user or contact profile, and `groupPreferences` of a group profile [CHAT]. Clients store the preferences of other profiles as received, so a client that does not know the key decodes it after an upgrade.
|
||||
|
||||
JSON Type Definition schema [RFC8927] of the `protocols` key:
|
||||
|
||||
```json
|
||||
{
|
||||
"values": {
|
||||
"properties": {
|
||||
"versions": {"elements": {"type": "string"}},
|
||||
"transports": {
|
||||
"optionalProperties": {
|
||||
"chat": {"type": "boolean"},
|
||||
"rpc": {"type": "boolean"}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Keys: protocol names.
|
||||
- `versions`: required. The protocol versions the bot serves, in the bot's order of preference.
|
||||
- `transports`: required. The transports on which the bot serves the protocol.
|
||||
- `transports.chat`: the bot answers `/_call` for the protocol in a conversation.
|
||||
- `transports.rpc`: the bot answers Service RPC requests for the protocol on its contact address. It is valid in `preferences` only.
|
||||
- An absent transport is false.
|
||||
- Clients MUST ignore unknown keys.
|
||||
|
||||
A declaration in `preferences` applies to direct chats with the profile and to Service RPC requests to its contact address. A declaration in `groupPreferences` states that the group's owners serve the protocol in the group chat. Other members can serve a protocol in a group without a declaration, because the core removes preferences from member profiles in groups. In a business chat, `protocols` of the business profile MUST be copied into the chat's `groupPreferences`, as `commands` are.
|
||||
|
||||
`commands` remains the command menu for people. `/_call` is not added to it. The underscore marks a command for software, as in the API of the core, and leaves `/call` free for bots' commands for people.
|
||||
|
||||
Example, the `preferences` of a contact profile:
|
||||
|
||||
```json
|
||||
{
|
||||
"protocols": {
|
||||
"mcp": {
|
||||
"versions": ["2026-07-28"],
|
||||
"transports": {"chat": true, "rpc": true}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Public group audience and update frequency
|
||||
|
||||
Two fields of the public group record, `publicGroup` of the group profile of a channel or a public group [CHAT]. JSON Type Definition schema [RFC8927]:
|
||||
|
||||
```json
|
||||
{
|
||||
"optionalProperties": {
|
||||
"audience": {"elements": {"type": "string"}},
|
||||
"changeFrequency": {"enum": ["always", "hourly", "daily", "weekly", "monthly", "yearly", "never"]}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `audience`: the peer types the channel or the group is made for. The values are the values of a profile's `peerType`: `human`, `bot`, `business`, `agent`. An absent `audience` is `["human"]`. Agents also read content made for people, so a channel or a group declares `agent` when it is made for agents, or has dedicated support for them.
|
||||
- `changeFrequency`: in a channel only, how often the channel is updated. The values are the `changefreq` values of the Sitemaps protocol [SITEMAPS]: `always`, `hourly`, `daily`, `weekly`, `monthly`, `yearly`, `never`.
|
||||
|
||||
### Contact address peers
|
||||
|
||||
One field of the data published with a contact address link, beside `business`, which marks a business address [BUSINESS]. JSON Type Definition schema [RFC8927]:
|
||||
|
||||
```json
|
||||
{
|
||||
"optionalProperties": {
|
||||
"serves": {"elements": {"type": "string"}}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `serves`: the peer types the address is made for, with the values and the default of `audience`.
|
||||
|
||||
Clients MUST ignore unknown values of `audience` and `serves`. The values in the public group record and in the address link data take precedence over the values on a website [WEB].
|
||||
|
||||
### Protocol names
|
||||
|
||||
A protocol name consists of lowercase ASCII letters, digits and hyphens.
|
||||
|
||||
A binding defines the protocol's request and response messages, including how, and whether, a peer discovers the protocol's capabilities. Both transports carry these messages unchanged.
|
||||
|
||||
| Name | Binding |
|
||||
|---|---|
|
||||
| `mcp` | [MCP binding](#mcp-binding) |
|
||||
|
||||
## Chat transport
|
||||
|
||||
### Commands
|
||||
|
||||
```
|
||||
/_call <protocol> <request>
|
||||
/_call @<member> <protocol> <request>
|
||||
```
|
||||
|
||||
- `<protocol>`: the name of a protocol the recipient serves.
|
||||
- `<request>`: the protocol's request message, a JSON object.
|
||||
- `@<member>`: optional, in a group: a mention of the called member [CHAT].
|
||||
- In a group, a command with a mention is answered by the mentioned member. A command without a mention is a broadcast: each member that serves the protocol answers it.
|
||||
- A command is the text of a text message. One space separates its parts.
|
||||
- A bot that declares `transports.chat` for a protocol MUST answer each `/_call` for it that mentions the bot or mentions nobody.
|
||||
|
||||
### Replies
|
||||
|
||||
- A bot sends one reply to each command it answers. The reply quotes the command, with a truncated quote. Its text is the protocol's response message, a JSON object.
|
||||
- A caller matches each reply to its command by the quoted message. Replies can arrive in any order.
|
||||
- In a group, a broadcast can receive several replies, one from each member that serves the protocol. A caller collects them all, each with its sender.
|
||||
- In a direct chat, a caller accepts replies only from the recipient. In a group, a caller of a command with a mention accepts only the mentioned member's reply.
|
||||
- Commands and replies are ordinary messages: people see them in the conversation.
|
||||
|
||||
### Message size
|
||||
|
||||
A command and a reply are one message each. The core refuses a message whose encoded JSON exceeds 15,602 bytes [CHAT]. The command or the reply is a string inside that JSON, so its escape characters count towards the limit. A bot whose reply exceeds the limit replies with the binding's error instead.
|
||||
|
||||
## Service RPC transport
|
||||
|
||||
### Exchange
|
||||
|
||||
The requester's core sends the request with:
|
||||
|
||||
```
|
||||
/_service_request <userId> <target> [timeout=<seconds>] [sign_key=<key>] <json>
|
||||
```
|
||||
|
||||
- `<target>`: a contact address link, or the SimpleX name of a contact address [NAMES].
|
||||
- `timeout`: how long the requester waits for the response. The default is 30 seconds.
|
||||
- `sign_key`: the requester's Ed25519 private key, for a [signed request](#signed-requests).
|
||||
- `<json>`: the [request](#request), a JSON object.
|
||||
- The command returns `serviceResponse` with `responseData`, the [response](#response).
|
||||
|
||||
The responder's core reports the request as the event `serviceRequest`, with `requestId`, `signerKey` and `requestData`. The responder sends the response with:
|
||||
|
||||
```
|
||||
/_service_response <userId> <requestId> <json>
|
||||
```
|
||||
|
||||
Requirements:
|
||||
|
||||
- The responder's core MUST run with service requests enabled, the option `service_requests=on` of `/_start`. Otherwise the core drops each request, and the requester waits until its timeout.
|
||||
- The responder's contact address MUST have ratchet keys. The core adds them to an address created or updated with the option `pq_ratchet=on` or `pq_ratchet=off` (`/_address`, `/_short_link_address`, `/_address_settings`). A request to an address without ratchet keys fails at once.
|
||||
- The core accepts a response within 180 seconds of receiving the request. A requester that expects a slow response sets `timeout` accordingly.
|
||||
- Before encryption, the agent pads a request or a response, its own framing included, to 11,106 bytes, and a longer one fails. A request and a response SHOULD NOT exceed 10,000 bytes of JSON.
|
||||
|
||||
### Request
|
||||
|
||||
JSON Type Definition schema [RFC8927] of the request:
|
||||
|
||||
```json
|
||||
{
|
||||
"properties": {
|
||||
"protocol": {"type": "string"},
|
||||
"request": {}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"version": {},
|
||||
"requestKey": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
```
|
||||
|
||||
- `protocol`: the protocol name.
|
||||
- `version`: the protocol version, for a protocol whose messages do not include one. Its type is defined by the binding.
|
||||
- `requestKey`: the requester's Ed25519 public key, in the encoding of the core's `signerKey`: base64url of the X.509 DER encoding of the key. A request with `requestKey` MUST be signed with that key.
|
||||
- `request`: the protocol's request message, the same as after `/_call <protocol>` in a conversation. The fields of the envelope are never added to it.
|
||||
|
||||
### Response
|
||||
|
||||
JSON Type Definition schema [RFC8927] of the response:
|
||||
|
||||
```json
|
||||
{
|
||||
"optionalProperties": {
|
||||
"response": {},
|
||||
"error": {
|
||||
"properties": {
|
||||
"code": {"type": "string"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"message": {"type": "string"},
|
||||
"retryAfter": {"type": "uint32"}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
```
|
||||
|
||||
- A response has exactly one of `response` and `error`.
|
||||
- `response`: the protocol's response message, the same as the reply in a conversation.
|
||||
- `error`: the request was not passed to the protocol.
|
||||
- `message`: a description for the requester's logs.
|
||||
- `retryAfter`: the number of seconds after which a repeated request can succeed.
|
||||
|
||||
Error codes:
|
||||
|
||||
- `unsupported_protocol`: the responder does not serve the protocol over Service RPC.
|
||||
- `unsupported_version`: the responder does not serve the requested version.
|
||||
- `bad_request`: the envelope is invalid, or `requestKey` differs from the verified signer key.
|
||||
- `conversation_required`: the request needs a conversation. The requester connects to the address and uses the chat transport.
|
||||
- `too_large`: the response exceeds the size limit.
|
||||
- `rate_limited`: the requester repeats the request after `retryAfter`.
|
||||
- `internal`: the responder failed.
|
||||
|
||||
A requester treats an unknown code as final, unless the error has `retryAfter`.
|
||||
|
||||
### Signed requests
|
||||
|
||||
- A signed request has the requester's Ed25519 signature over the request payload, bound to the request's ratchet session: the agent signs `SHA3-256("SimpleXService" ‖ AD) ‖ payload`, where AD is the ratchet's associated data. The responder's agent verifies the signature, and the core reports the key as `signerKey`. A signature copied into another session fails verification.
|
||||
- A responder MUST answer `bad_request` to a request whose `requestKey` differs from `signerKey`, and to an unsigned request with `requestKey`.
|
||||
- A requester signs when the responder needs a persistent identity, for example an account or a purchase. All requests signed with one key are linkable to each other. A requester SHOULD use a separate key for each responder.
|
||||
- An unsigned request has only the payload: the responder cannot link it to other requests by a key.
|
||||
|
||||
## MCP binding
|
||||
|
||||
Protocol name: `mcp`.
|
||||
|
||||
### Messages
|
||||
|
||||
- The request is one JSON-RPC request of MCP revision 2026-07-28 or later, with its `_meta` envelope [MCP]. In a conversation, it follows `/_call mcp`. Over Service RPC, it is the envelope's `request`.
|
||||
- The response is the JSON-RPC response to the request: a result or a JSON-RPC error. In a conversation, it is the text of the reply. Over Service RPC, it is the envelope's `response`.
|
||||
- In a conversation, a caller matches a reply to its request by the quoted message. The response has the request's `id`, as JSON-RPC requires.
|
||||
- A bot serves the methods answered with one response, for example `server/discover`, `tools/list` and `tools/call`.
|
||||
- A multi-round-trip result (`resultType: "input_required"`) is answered with a new request, as MCP defines.
|
||||
- A response above the size limit is replaced with a JSON-RPC error with code -32603 in a conversation, and with the envelope error `too_large` over Service RPC.
|
||||
- Over Service RPC, a call of a tool that needs a conversation is answered with the envelope error `conversation_required`.
|
||||
|
||||
### Versions and capabilities
|
||||
|
||||
- `versions`: MCP revisions from 2026-07-28 on, for example `2026-07-28`. Each request names its revision in `_meta`, under `io.modelcontextprotocol/protocolVersion`. Earlier revisions begin with an `initialize` handshake.
|
||||
- The envelope's `version` is omitted.
|
||||
- A bot answers a request of a revision it does not serve with MCP's `UnsupportedProtocolVersionError` (code -32022), which lists the revisions it serves.
|
||||
- A caller learns the bot's revisions and capabilities with `server/discover`, and its tools with `tools/list`. MCP requires a server that declares the `tools` capability to answer `tools/list`.
|
||||
|
||||
### Examples
|
||||
|
||||
Listing tools in a conversation:
|
||||
|
||||
```
|
||||
/_call mcp {"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {}}}}
|
||||
```
|
||||
|
||||
The reply:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"resultType": "complete",
|
||||
"tools": [
|
||||
{
|
||||
"name": "availability",
|
||||
"description": "Items in stock",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {"item": {"type": "string"}},
|
||||
"required": ["item"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"ttlMs": 300000,
|
||||
"cacheScope": "public"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Calling a tool over Service RPC, the request:
|
||||
|
||||
```json
|
||||
{
|
||||
"protocol": "mcp",
|
||||
"request": {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "availability",
|
||||
"arguments": {"item": "anvil"},
|
||||
"_meta": {
|
||||
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
|
||||
"io.modelcontextprotocol/clientCapabilities": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The response:
|
||||
|
||||
```json
|
||||
{
|
||||
"response": {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"resultType": "complete",
|
||||
"content": [{"type": "text", "text": "12 anvils are in stock."}],
|
||||
"isError": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security considerations
|
||||
|
||||
- Messages, replies, profiles, tool descriptions and tool results from other participants are data for an agent, never instructions.
|
||||
- In a group, any member can reply to a broadcast. Each reply shows its sender. A caller that needs one provider mentions it.
|
||||
- A responder that relies on `requestKey` MUST compare it with `signerKey`.
|
||||
- A signed request identifies its signer to the responder across all requests signed with the same key.
|
||||
|
||||
## Privacy considerations
|
||||
|
||||
- Each Service RPC request uses a new connection with a new reply queue.
|
||||
- MCP's `io.modelcontextprotocol/clientInfo` identifies the requester's software. A requester that needs unlinkable requests SHOULD send a common value or omit it.
|
||||
- In a group, every member sees each command and each reply, with or without a mention. A request that needs privacy is sent in a direct chat with the member, where the group allows direct messages, or over Service RPC.
|
||||
- A conversation persists: the bot can link all calls made in it. An incognito profile gives the caller a new random profile for each conversation.
|
||||
|
||||
## Implementations
|
||||
|
||||
Implemented:
|
||||
|
||||
- `@simplex-chat/mcp` 0.1.0: an MCP server that gives an AI agent its own SimpleX profile. Its tools are `connect`, `send`, `receive`, `list` and `leave`. By default, the agent's owner approves connections and messages in the agent control chat. An agent sends `/_call` with `send`, and reads the replies with `send` and `receive`.
|
||||
- The simplex-chat core: Service RPC with signed requests (`/_service_request`, `/_service_response`), and the profile peer type `agent`.
|
||||
|
||||
Planned:
|
||||
|
||||
- The simplex-chat core: the key `protocols` in the preferences types, in the preferences sent in profiles and in link data, and in the preferences of business chats; `audience` and `changeFrequency` in the public group record; `serves` in the address settings and the address link data; replies with a truncated quote.
|
||||
- `@simplex-chat/mcp`: the tools `mcp_tools` and `mcp_call`. They use Service RPC when the address declares `transports.rpc`, and the agent's conversation otherwise. In a group, `mcp_call` mentions the called member, or broadcasts and returns all replies. Each call is an action, approved by the agent's owner as a message is.
|
||||
- The bot library: a helper that serves MCP tools over both transports.
|
||||
|
||||
## References
|
||||
|
||||
- [BUSINESS] Using SimpleX Chat in business: [BUSINESS.md](../BUSINESS.md)
|
||||
- [CHAT] SimpleX Chat protocol: [simplex-chat.md](./simplex-chat.md)
|
||||
- [LLMSTXT] The /llms.txt file: https://llmstxt.org
|
||||
- [MCP] Model Context Protocol specification, revision 2026-07-28: https://modelcontextprotocol.io/specification/2026-07-28
|
||||
- [NAMES] SimpleX public names for channels and businesses: [names-overview.md](./names-overview.md)
|
||||
- [RFC2119] Key words for use in RFCs to Indicate Requirement Levels: https://www.rfc-editor.org/rfc/rfc2119
|
||||
- [RFC8174] Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words: https://www.rfc-editor.org/rfc/rfc8174
|
||||
- [RFC8927] JSON Type Definition: https://www.rfc-editor.org/rfc/rfc8927
|
||||
- [SITEMAPS] Sitemaps XML format: https://www.sitemaps.org/protocol.html
|
||||
- [WEB] Publishing SimpleX addresses on websites: [web-discovery.md](./web-discovery.md)
|
||||
@@ -0,0 +1,251 @@
|
||||
Revision 1, 2026-10-19, draft
|
||||
|
||||
Evgeny Poberezkin
|
||||
|
||||
# Publishing SimpleX addresses on websites
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Abstract](#abstract)
|
||||
- [Conventions](#conventions)
|
||||
- [Terms](#terms)
|
||||
- [Link relation type](#link-relation-type)
|
||||
- [simplex](#simplex)
|
||||
- [Owner's and described addresses](#owners-and-described-addresses)
|
||||
- [Target attributes](#target-attributes)
|
||||
- [HTML](#html)
|
||||
- [HTTP](#http)
|
||||
- [Well-known URI](#well-known-uri)
|
||||
- [Retrieval](#retrieval)
|
||||
- [Format](#format)
|
||||
- [Example](#example)
|
||||
- [Processing](#processing)
|
||||
- [Security considerations](#security-considerations)
|
||||
- [Privacy considerations](#privacy-considerations)
|
||||
- [IANA considerations](#iana-considerations)
|
||||
- [Link relation type registration](#link-relation-type-registration)
|
||||
- [Well-known URI registration](#well-known-uri-registration)
|
||||
- [HTML link type extensions](#html-link-type-extensions)
|
||||
- [References](#references)
|
||||
|
||||
## Abstract
|
||||
|
||||
This document defines how a website publishes SimpleX channels, groups and contact addresses:
|
||||
|
||||
- the link relation type `simplex`, for the HTML elements `link`, `a` and `area`, and for the HTTP `Link` header field. Its targets are the addresses of the website's owner, or the addresses a page describes, such as the entries of a directory;
|
||||
- the well-known URI `simplex.json`: a JSON document with the owner's channels, groups and contact addresses, their audience, the update frequency of each channel, and the kind and the protocols of each contact address.
|
||||
|
||||
People and software agents subscribe to a channel to receive updates, join a group to take part in its conversations, and connect to a contact address to send requests. [Agent protocols over SimpleX](./agent-protocols.md) defines the requests an agent sends to a contact address, and an `llms.txt` section with the same addresses.
|
||||
|
||||
## Conventions
|
||||
|
||||
The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT" and "MAY" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals.
|
||||
|
||||
JSON schemas use JSON Type Definition [RFC8927].
|
||||
|
||||
## Terms
|
||||
|
||||
- **SimpleX link**: a short connection link of the SimpleX Agent protocol, with the `https` or the `simplex` scheme [AGENT]. The type in the link is the kind of the address.
|
||||
- **Channel link**: a SimpleX link of type `c`, for example `https://smp12.simplex.im/c#<key>`.
|
||||
- **Group link**: a SimpleX link of type `g`, for example `https://smp12.simplex.im/g#<key>`.
|
||||
- **Contact address link**: a SimpleX link of type `a`, for example `https://smp12.simplex.im/a#<key>`.
|
||||
- **SimpleX name**: a name in the SimpleX name service [NAMES]. `#example.simplex` is the name of a channel, `@example.simplex` the name of a contact address.
|
||||
- **Owner**: the person or organization that publishes the website.
|
||||
|
||||
## Link relation type
|
||||
|
||||
### simplex
|
||||
|
||||
The target of a `simplex` link is a SimpleX channel, group or contact address of the owner of the link context, or described by the link context. The target MUST be a channel link, a group link or a contact address link. The type in the link tells which.
|
||||
|
||||
### Owner's and described addresses
|
||||
|
||||
A page that describes the addresses of others, such as a directory or a catalogue, uses the relation type on its entries. The owner's own addresses are listed in the [well-known document](#well-known-uri).
|
||||
|
||||
### Target attributes
|
||||
|
||||
- `title`: the name of the channel, the group or the address, for display. In HTTP, a title with non-ASCII characters is sent as `title*` [RFC8187].
|
||||
- `hreflang`: the language of the channel's posts, or of the conversations [BCP47].
|
||||
|
||||
A link context MAY have several `simplex` links of one kind. Their order is the owner's order of preference: document order in HTML, and the order of link values in HTTP.
|
||||
|
||||
### HTML
|
||||
|
||||
In the document head:
|
||||
|
||||
```html
|
||||
<link rel="simplex" href="https://smp12.simplex.im/c#…" hreflang="en" title="Acme news">
|
||||
<link rel="simplex" href="https://smp12.simplex.im/a#…" hreflang="en" title="Ask Acme">
|
||||
```
|
||||
|
||||
In the document body, the relation type is used on visible links, for example on the entries of a directory:
|
||||
|
||||
```html
|
||||
<a rel="simplex" href="https://smp12.simplex.im/a#…">Chat with us</a>
|
||||
<a rel="simplex" href="https://smp12.simplex.im/g#…">Acme customers</a>
|
||||
```
|
||||
|
||||
### HTTP
|
||||
|
||||
```
|
||||
Link: <https://smp12.simplex.im/c#…>; rel="simplex"; hreflang="en"; title="Acme news"
|
||||
Link: <https://smp12.simplex.im/a#…>; rel="simplex"; hreflang="en"; title="Ask Acme"
|
||||
```
|
||||
|
||||
## Well-known URI
|
||||
|
||||
### Retrieval
|
||||
|
||||
The document is located at the path `/.well-known/simplex.json` of an origin [RFC8615]. A client retrieves it with an HTTPS `GET` request. A server responds with status 200, and SHOULD send the media type `application/json`. A client parses the response body as JSON, whatever media type the server sends.
|
||||
|
||||
The entries of the document are the channels, groups and contact addresses of the origin's owner. A site that controls only a path of its origin publishes link relations instead.
|
||||
|
||||
### Format
|
||||
|
||||
The document MUST conform to this JSON Type Definition schema [RFC8927]:
|
||||
|
||||
```json
|
||||
{
|
||||
"optionalProperties": {
|
||||
"channels": {"elements": {"ref": "address"}},
|
||||
"groups": {"elements": {"ref": "address"}},
|
||||
"contacts": {"elements": {"ref": "address"}}
|
||||
},
|
||||
"additionalProperties": true,
|
||||
"definitions": {
|
||||
"address": {
|
||||
"properties": {
|
||||
"link": {"type": "string"}
|
||||
},
|
||||
"optionalProperties": {
|
||||
"name": {"type": "string"},
|
||||
"title": {"type": "string"},
|
||||
"lang": {"type": "string"},
|
||||
"audience": {"elements": {"type": "string"}},
|
||||
"changeFrequency": {"enum": ["always", "hourly", "daily", "weekly", "monthly", "yearly", "never"]},
|
||||
"business": {"type": "boolean"},
|
||||
"protocols": {"elements": {"type": "string"}}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `channels`: the owner's channels, in the owner's order of preference.
|
||||
- `groups`: the owner's groups, in the owner's order of preference.
|
||||
- `contacts`: the owner's contact addresses, in the owner's order of preference.
|
||||
- `link`: required. A channel link in `channels`, a group link in `groups`, a contact address link in `contacts`.
|
||||
- `name`: the SimpleX name of the channel, the group or the contact address of `link`.
|
||||
- `title`: the name of the channel, the group or the address, for display.
|
||||
- `lang`: the language of the channel's posts, or of the conversations [BCP47].
|
||||
- `audience`: who the channel, the group or the address serves: `people`, `agents`, or both.
|
||||
- `changeFrequency`: in `channels` only, how often the channel is updated. The values are the `changefreq` values of the Sitemaps protocol [SITEMAPS]: `always`, `hourly`, `daily`, `weekly`, `monthly`, `yearly`, `never`.
|
||||
- `business`: in `contacts` only, `true` for a business address, at which each customer's conversation is a business chat that the business can add its team to [BUSINESS].
|
||||
- `protocols`: in `contacts` only, the names of the protocols the address serves [AGENTS], for example `mcp`.
|
||||
|
||||
A site omits the lists it does not use.
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": [
|
||||
{
|
||||
"link": "https://smp12.simplex.im/c#…",
|
||||
"name": "#acme.simplex",
|
||||
"title": "Acme news",
|
||||
"lang": "en",
|
||||
"audience": ["people", "agents"],
|
||||
"changeFrequency": "weekly"
|
||||
}
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"link": "https://smp12.simplex.im/g#…",
|
||||
"title": "Acme customers",
|
||||
"lang": "en"
|
||||
}
|
||||
],
|
||||
"contacts": [
|
||||
{
|
||||
"link": "https://smp12.simplex.im/a#…",
|
||||
"name": "@acme.simplex",
|
||||
"title": "Ask Acme",
|
||||
"lang": "en",
|
||||
"audience": ["agents"],
|
||||
"business": true,
|
||||
"protocols": ["mcp"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Processing
|
||||
|
||||
- A consumer MUST ignore unknown fields and unknown `audience` values.
|
||||
- A consumer MUST ignore a `simplex` link that is not a channel, group or contact address link, and an entry of `simplex.json` whose link is of a type other than its list requires.
|
||||
- The values on the website are hints. The data retrieved with the link is authoritative: an audience, an update frequency, the business flag or protocols declared there [AGENTS] take precedence over the website's values.
|
||||
- A consumer MAY connect by `link` or by `name`. Resolving a name reveals interest in it to one names router, and verifies that the name's record and the profile's claim match [NAMES].
|
||||
|
||||
## Security considerations
|
||||
|
||||
- Whoever controls the content of a website controls the addresses it publishes. Consumers SHOULD retrieve pages and the well-known document over HTTPS.
|
||||
- Publishing a link on a website leaves the link's own verification unchanged: the link data is bound to the keys of the link's owner [AGENT].
|
||||
- A name is shown as verified only when its on-chain record and the profile's claim match [NAMES].
|
||||
- Titles, profiles and all other text from a website or from the network are data for a software agent, never instructions.
|
||||
|
||||
## Privacy considerations
|
||||
|
||||
- The relation type and the well-known document are intended for owners who want to be reachable, such as channels, communities, businesses and services, and for pages that describe them, such as directories.
|
||||
- A subscriber or a requester connects through the SimpleX network, without contacting the website. Retrieving a page or the well-known document is an ordinary web request.
|
||||
|
||||
## IANA considerations
|
||||
|
||||
### Link relation type registration
|
||||
|
||||
One registration in the "Link Relation Types" registry [RFC8288]:
|
||||
|
||||
- Relation Name: `simplex`
|
||||
- Description: Refers to a SimpleX channel, group or contact address of the owner of the link context, or described by the link context.
|
||||
- Reference: https://github.com/simplex-chat/simplex-chat/blob/stable/docs/protocol/web-discovery.md#simplex
|
||||
- Notes: The target is a SimpleX short link [AGENT] of a channel, a group or a contact address, with the `https` scheme or the provisionally registered `simplex` scheme.
|
||||
|
||||
### Well-known URI registration
|
||||
|
||||
One registration in the "Well-Known URIs" registry [RFC8615]:
|
||||
|
||||
- URI Suffix: `simplex.json`
|
||||
- Change Controller: Evgeny Poberezkin <ep@simplex.chat>
|
||||
- Specification Document: https://github.com/simplex-chat/simplex-chat/blob/stable/docs/protocol/web-discovery.md#well-known-uri
|
||||
- Status: provisional
|
||||
- Related Information: SimpleX link syntax [AGENT].
|
||||
|
||||
## HTML link type extensions
|
||||
|
||||
The HTML standard requires extension link types to be registered on the microformats page "existing-rel-values" [HTML] [MF-REL]. The entry:
|
||||
|
||||
- Keyword: `simplex`
|
||||
- Effect on `link`: Hyperlink
|
||||
- Effect on `a`, `area`: Hyperlink
|
||||
- Effect on `form`: not allowed
|
||||
- Brief description: Refers to a SimpleX channel, group or contact address of the page's owner, or described by the page.
|
||||
- Specification: https://github.com/simplex-chat/simplex-chat/blob/stable/docs/protocol/web-discovery.md#simplex
|
||||
- Status: proposed
|
||||
|
||||
## References
|
||||
|
||||
- [AGENT] SimpleX Agent protocol, short connection links: https://github.com/simplex-chat/simplexmq/blob/stable/protocol/agent-protocol.md#short-connection-link-syntax
|
||||
- [AGENTS] Agent protocols over SimpleX: [agent-protocols.md](./agent-protocols.md)
|
||||
- [BCP47] Tags for Identifying Languages: https://www.rfc-editor.org/info/bcp47
|
||||
- [BUSINESS] Using SimpleX Chat in business: [BUSINESS.md](../BUSINESS.md)
|
||||
- [HTML] HTML Living Standard, other link types: https://html.spec.whatwg.org/multipage/links.html#other-link-types
|
||||
- [MF-REL] Microformats, existing rel values: https://microformats.org/wiki/existing-rel-values
|
||||
- [NAMES] SimpleX public names for channels and businesses: [names-overview.md](./names-overview.md)
|
||||
- [RFC2119] Key words for use in RFCs to Indicate Requirement Levels: https://www.rfc-editor.org/rfc/rfc2119
|
||||
- [RFC8174] Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words: https://www.rfc-editor.org/rfc/rfc8174
|
||||
- [RFC8187] Indicating Character Encoding and Language for HTTP Header Field Parameters: https://www.rfc-editor.org/rfc/rfc8187
|
||||
- [RFC8288] Web Linking: https://www.rfc-editor.org/rfc/rfc8288
|
||||
- [RFC8615] Well-Known Uniform Resource Identifiers (URIs): https://www.rfc-editor.org/rfc/rfc8615
|
||||
- [RFC8927] JSON Type Definition: https://www.rfc-editor.org/rfc/rfc8927
|
||||
- [SITEMAPS] Sitemaps XML format: https://www.sitemaps.org/protocol.html
|
||||
@@ -66,6 +66,33 @@ You can run it with: `npx ts-node ./examples/squaring-bot.ts`
|
||||
|
||||
A larger example, with a business address and commands as keys: [SimpleX Calculator](../../apps/simplex-calculator-bot/).
|
||||
|
||||
## Live messages
|
||||
|
||||
Live messages are shown to recipients while they are written, e.g. a streamed reply of a language model.
|
||||
|
||||
`onMessage` handler for `bot.run` that streams a reply:
|
||||
|
||||
```javascript
|
||||
const {api, util} = require("simplex-chat")
|
||||
|
||||
async function onMessage(ci, content, chat) {
|
||||
const {chatType, chatId} = util.chatInfoRef(ci.chatInfo)
|
||||
const live = new api.LiveMessage(chat, chatType, chatId)
|
||||
let text = ""
|
||||
for await (const delta of streamReply(content.text)) {
|
||||
text += delta
|
||||
live.update(text)
|
||||
}
|
||||
await live.end()
|
||||
}
|
||||
```
|
||||
|
||||
`streamReply` stands for your function that returns text fragments as an async iterable.
|
||||
|
||||
`update` sends the first text at once, and then updates the message at most once per 3 seconds (`interval` parameter). `end` finalizes the message; a message without text is deleted.
|
||||
|
||||
The underlying commands: `apiSendMessages` with `liveMessage = true` sends a live message, `apiUpdateChatItem` with `liveMessage = true` updates it, and with `liveMessage = false` finalizes it.
|
||||
|
||||
## Native library
|
||||
|
||||
`libsimplex` and the prebuilt Node.js add-on are downloaded on first use into the user cache (`$XDG_CACHE_HOME/simplex-chat` or `~/.cache/simplex-chat` on Linux, `~/Library/Caches/simplex-chat` on macOS, `%LOCALAPPDATA%\simplex-chat` on Windows), shared with the Python library. To download them ahead of time, for example in a Dockerfile, run as the user that runs the app:
|
||||
@@ -121,10 +148,27 @@ Library provides types and functions to:
|
||||
- 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.
|
||||
- send live messages, e.g. streamed replies.
|
||||
- create, join and manage group.
|
||||
- send and receive files.
|
||||
- etc.
|
||||
|
||||
## Tests
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm run build
|
||||
npm test
|
||||
```
|
||||
|
||||
To run one test file: `npm test -- tests/core.test.ts`.
|
||||
|
||||
A local SMP server is started for the API and bot tests. On Linux x86-64 and arm64, `smp-server` is downloaded from [simplexmq releases](https://github.com/simplex-chat/simplexmq/releases) to `node_modules/.cache/smp-server`. To use another build, or on other systems, set `SMP_SERVER` to the path of `smp-server`:
|
||||
|
||||
```sh
|
||||
SMP_SERVER=/path/to/smp-server npm test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
[AGPL v3](./LICENSE)
|
||||
@@ -17,6 +17,7 @@ You need to use it in bot event handlers, and for any other use cases.
|
||||
|
||||
- [ChatApi](api.Class.ChatApi.md)
|
||||
- [ChatCommandError](api.Class.ChatCommandError.md)
|
||||
- [LiveMessage](api.Class.LiveMessage.md)
|
||||
|
||||
## Interfaces
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Class: ChatApi
|
||||
|
||||
Defined in: [src/api.ts:95](../src/api.ts#L95)
|
||||
Defined in: [src/api.ts:100](../api.ts#L100)
|
||||
|
||||
Main API class for interacting with the chat core library.
|
||||
|
||||
@@ -16,7 +16,7 @@ Main API class for interacting with the chat core library.
|
||||
|
||||
> `protected` **ctrl\_**: `bigint` \| `undefined`
|
||||
|
||||
Defined in: [src/api.ts:101](../src/api.ts#L101)
|
||||
Defined in: [src/api.ts:106](../api.ts#L106)
|
||||
|
||||
## Accessors
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/api.ts:101](../src/api.ts#L101)
|
||||
|
||||
> **get** **ctrl**(): `bigint`
|
||||
|
||||
Defined in: [src/api.ts:343](../src/api.ts#L343)
|
||||
Defined in: [src/api.ts:345](../api.ts#L345)
|
||||
|
||||
Chat controller reference
|
||||
|
||||
@@ -42,7 +42,7 @@ Chat controller reference
|
||||
|
||||
> **get** **initialized**(): `boolean`
|
||||
|
||||
Defined in: [src/api.ts:329](../src/api.ts#L329)
|
||||
Defined in: [src/api.ts:331](../api.ts#L331)
|
||||
|
||||
Chat controller is initialized
|
||||
|
||||
@@ -58,7 +58,7 @@ Chat controller is initialized
|
||||
|
||||
> **get** **started**(): `boolean`
|
||||
|
||||
Defined in: [src/api.ts:336](../src/api.ts#L336)
|
||||
Defined in: [src/api.ts:338](../api.ts#L338)
|
||||
|
||||
Chat controller is started
|
||||
|
||||
@@ -72,7 +72,7 @@ Chat controller is started
|
||||
|
||||
> **apiAcceptContactRequest**(`contactReqId`): `Promise`\<`Contact`\>
|
||||
|
||||
Defined in: [src/api.ts:770](../src/api.ts#L770)
|
||||
Defined in: [src/api.ts:773](../api.ts#L773)
|
||||
|
||||
Accept contact request.
|
||||
Network usage: interactive.
|
||||
@@ -93,7 +93,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiAcceptMember**(`groupId`, `groupMemberId`, `memberRole`): `Promise`\<`GroupMember`\>
|
||||
|
||||
Defined in: [src/api.ts:590](../src/api.ts#L590)
|
||||
Defined in: [src/api.ts:593](../api.ts#L593)
|
||||
|
||||
Accept group member. Requires Admin role.
|
||||
Network usage: background.
|
||||
@@ -122,7 +122,7 @@ Network usage: background.
|
||||
|
||||
> **apiAddMember**(`groupId`, `contactId`, `memberRole`): `Promise`\<`GroupMember`\>
|
||||
|
||||
Defined in: [src/api.ts:560](../src/api.ts#L560)
|
||||
Defined in: [src/api.ts:563](../api.ts#L563)
|
||||
|
||||
Add contact to group. Requires bot to have Admin role.
|
||||
Network usage: interactive.
|
||||
@@ -151,7 +151,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiAddMemberViaGroup**(`groupId`, `viaGroupId`, `groupMemberId`, `memberRole`): `Promise`\<`GroupMember`\>
|
||||
|
||||
Defined in: [src/api.ts:570](../src/api.ts#L570)
|
||||
Defined in: [src/api.ts:573](../api.ts#L573)
|
||||
|
||||
Add member of another group to group. Requires bot to have Admin role in the group, and direct messages allowed for its role in another group.
|
||||
Network usage: interactive.
|
||||
@@ -184,7 +184,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiBlockMembersForAll**(`groupId`, `groupMemberIds`, `blocked`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:610](../src/api.ts#L610)
|
||||
Defined in: [src/api.ts:613](../api.ts#L613)
|
||||
|
||||
Block members. Requires Moderator role.
|
||||
Network usage: background.
|
||||
@@ -213,7 +213,7 @@ Network usage: background.
|
||||
|
||||
> **apiCancelFile**(`fileId`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:550](../src/api.ts#L550)
|
||||
Defined in: [src/api.ts:553](../api.ts#L553)
|
||||
|
||||
Cancel file.
|
||||
Network usage: background.
|
||||
@@ -234,7 +234,7 @@ Network usage: background.
|
||||
|
||||
> **apiChatItemReaction**(`chatType`, `chatId`, `chatItemId`, `add`, `reaction`): `Promise`\<`ACIReaction`\>
|
||||
|
||||
Defined in: [src/api.ts:523](../src/api.ts#L523)
|
||||
Defined in: [src/api.ts:526](../api.ts#L526)
|
||||
|
||||
Add/remove message reaction.
|
||||
Network usage: background.
|
||||
@@ -271,7 +271,7 @@ Network usage: background.
|
||||
|
||||
> **apiConnect**(`userId`, `incognito`, `preparedLink`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\>
|
||||
|
||||
Defined in: [src/api.ts:739](../src/api.ts#L739)
|
||||
Defined in: [src/api.ts:742](../api.ts#L742)
|
||||
|
||||
Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link
|
||||
Network usage: interactive.
|
||||
@@ -300,7 +300,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiConnectActiveUser**(`connLink`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\>
|
||||
|
||||
Defined in: [src/api.ts:748](../src/api.ts#L748)
|
||||
Defined in: [src/api.ts:751](../api.ts#L751)
|
||||
|
||||
Connect via SimpleX link as string in the active user profile.
|
||||
Network usage: interactive.
|
||||
@@ -321,7 +321,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiConnectPlan**(`userId`, `connectionLink`): `Promise`\<\[`ConnectionPlan`, `CreatedConnLink`\]\>
|
||||
|
||||
Defined in: [src/api.ts:729](../src/api.ts#L729)
|
||||
Defined in: [src/api.ts:732](../api.ts#L732)
|
||||
|
||||
Determine SimpleX link type and if the bot is already connected via this link.
|
||||
Network usage: interactive.
|
||||
@@ -346,7 +346,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiCreateActiveUser**(`profile?`): `Promise`\<`User`\>
|
||||
|
||||
Defined in: [src/api.ts:907](../src/api.ts#L907)
|
||||
Defined in: [src/api.ts:910](../api.ts#L910)
|
||||
|
||||
Create new user profile
|
||||
Network usage: no.
|
||||
@@ -367,7 +367,7 @@ Network usage: no.
|
||||
|
||||
> **apiCreateGroupLink**(`groupId`, `memberRole`): `Promise`\<`string`\>
|
||||
|
||||
Defined in: [src/api.ts:670](../src/api.ts#L670)
|
||||
Defined in: [src/api.ts:673](../api.ts#L673)
|
||||
|
||||
Create group link.
|
||||
Network usage: interactive.
|
||||
@@ -392,7 +392,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiCreateLink**(`userId`): `Promise`\<`string`\>
|
||||
|
||||
Defined in: [src/api.ts:716](../src/api.ts#L716)
|
||||
Defined in: [src/api.ts:719](../api.ts#L719)
|
||||
|
||||
Create 1-time invitation link.
|
||||
Network usage: interactive.
|
||||
@@ -413,7 +413,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiCreateMemberContact**(`groupId`, `groupMemberId`): `Promise`\<`Contact`\>
|
||||
|
||||
Defined in: [src/api.ts:973](../src/api.ts#L973)
|
||||
Defined in: [src/api.ts:976](../api.ts#L976)
|
||||
|
||||
Create a direct message contact with a group member.
|
||||
Returns the created contact.
|
||||
@@ -439,7 +439,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiCreateUserAddress**(`userId`): `Promise`\<`CreatedConnLink`\>
|
||||
|
||||
Defined in: [src/api.ts:360](../src/api.ts#L360)
|
||||
Defined in: [src/api.ts:362](../api.ts#L362)
|
||||
|
||||
Create bot address.
|
||||
Network usage: interactive.
|
||||
@@ -460,7 +460,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiDeleteChat**(`chatType`, `chatId`, `deleteMode?`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:829](../src/api.ts#L829)
|
||||
Defined in: [src/api.ts:832](../api.ts#L832)
|
||||
|
||||
Delete chat.
|
||||
Network usage: background.
|
||||
@@ -489,7 +489,7 @@ Network usage: background.
|
||||
|
||||
> **apiDeleteChatItems**(`chatType`, `chatId`, `chatItemIds`, `deleteMode`): `Promise`\<`ChatItemDeletion`[]\>
|
||||
|
||||
Defined in: [src/api.ts:498](../src/api.ts#L498)
|
||||
Defined in: [src/api.ts:501](../api.ts#L501)
|
||||
|
||||
Delete message.
|
||||
Network usage: background.
|
||||
@@ -522,7 +522,7 @@ Network usage: background.
|
||||
|
||||
> **apiDeleteGroupLink**(`groupId`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:692](../src/api.ts#L692)
|
||||
Defined in: [src/api.ts:695](../api.ts#L695)
|
||||
|
||||
Delete group link.
|
||||
Network usage: background.
|
||||
@@ -543,7 +543,7 @@ Network usage: background.
|
||||
|
||||
> **apiDeleteMemberChatItem**(`groupId`, `chatItemIds`): `Promise`\<`ChatItemDeletion`[]\>
|
||||
|
||||
Defined in: [src/api.ts:513](../src/api.ts#L513)
|
||||
Defined in: [src/api.ts:516](../api.ts#L516)
|
||||
|
||||
Moderate message. Requires Moderator role (and higher than message author's).
|
||||
Network usage: background.
|
||||
@@ -568,7 +568,7 @@ Network usage: background.
|
||||
|
||||
> **apiDeleteUser**(`userId`, `delSMPQueues`, `viewPwd?`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:937](../src/api.ts#L937)
|
||||
Defined in: [src/api.ts:940](../api.ts#L940)
|
||||
|
||||
Delete user profile.
|
||||
Network usage: background.
|
||||
@@ -597,7 +597,7 @@ Network usage: background.
|
||||
|
||||
> **apiDeleteUserAddress**(`userId`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:370](../src/api.ts#L370)
|
||||
Defined in: [src/api.ts:372](../api.ts#L372)
|
||||
|
||||
Deletes a user address.
|
||||
Network usage: background.
|
||||
@@ -618,7 +618,7 @@ Network usage: background.
|
||||
|
||||
> **apiGetActiveUser**(): `Promise`\<`User` \| `undefined`\>
|
||||
|
||||
Defined in: [src/api.ts:887](../src/api.ts#L887)
|
||||
Defined in: [src/api.ts:890](../api.ts#L890)
|
||||
|
||||
Get active user profile
|
||||
Network usage: no.
|
||||
@@ -633,7 +633,7 @@ Network usage: no.
|
||||
|
||||
> **apiGetChat**(`chatType`, `chatId`, `count`): `Promise`\<`any`\>
|
||||
|
||||
Defined in: [src/api.ts:877](../src/api.ts#L877)
|
||||
Defined in: [src/api.ts:880](../api.ts#L880)
|
||||
|
||||
Get chat items.
|
||||
Network usage: no.
|
||||
@@ -662,7 +662,7 @@ Network usage: no.
|
||||
|
||||
> **apiGetChats**(`userId`, `pagination`, `query?`, `pendingConnections?`): `Promise`\<`AChat`[]\>
|
||||
|
||||
Defined in: [src/api.ts:814](../src/api.ts#L814)
|
||||
Defined in: [src/api.ts:817](../api.ts#L817)
|
||||
|
||||
Get chat previews (paginated).
|
||||
Network usage: no.
|
||||
@@ -699,7 +699,7 @@ on large databases.
|
||||
|
||||
> **apiGetGroupLink**(`groupId`): `Promise`\<`GroupLink`\>
|
||||
|
||||
Defined in: [src/api.ts:701](../src/api.ts#L701)
|
||||
Defined in: [src/api.ts:704](../api.ts#L704)
|
||||
|
||||
Get group link.
|
||||
Network usage: no.
|
||||
@@ -720,7 +720,7 @@ Network usage: no.
|
||||
|
||||
> **apiGetGroupLinkStr**(`groupId`): `Promise`\<`string`\>
|
||||
|
||||
Defined in: [src/api.ts:707](../src/api.ts#L707)
|
||||
Defined in: [src/api.ts:710](../api.ts#L710)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -738,7 +738,7 @@ Defined in: [src/api.ts:707](../src/api.ts#L707)
|
||||
|
||||
> **apiGetUserAddress**(`userId`): `Promise`\<`UserContactLink` \| `undefined`\>
|
||||
|
||||
Defined in: [src/api.ts:380](../src/api.ts#L380)
|
||||
Defined in: [src/api.ts:382](../api.ts#L382)
|
||||
|
||||
Get bot address and settings.
|
||||
Network usage: no.
|
||||
@@ -759,7 +759,7 @@ Network usage: no.
|
||||
|
||||
> **apiJoinGroup**(`groupId`): `Promise`\<`GroupInfo`\>
|
||||
|
||||
Defined in: [src/api.ts:580](../src/api.ts#L580)
|
||||
Defined in: [src/api.ts:583](../api.ts#L583)
|
||||
|
||||
Join group.
|
||||
Network usage: interactive.
|
||||
@@ -780,7 +780,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiLeaveGroup**(`groupId`): `Promise`\<`GroupInfo`\>
|
||||
|
||||
Defined in: [src/api.ts:630](../src/api.ts#L630)
|
||||
Defined in: [src/api.ts:633](../api.ts#L633)
|
||||
|
||||
Leave group.
|
||||
Network usage: background.
|
||||
@@ -801,7 +801,7 @@ Network usage: background.
|
||||
|
||||
> **apiListContacts**(`userId`): `Promise`\<`Contact`[]\>
|
||||
|
||||
Defined in: [src/api.ts:790](../src/api.ts#L790)
|
||||
Defined in: [src/api.ts:793](../api.ts#L793)
|
||||
|
||||
Get contacts.
|
||||
Network usage: no.
|
||||
@@ -822,7 +822,7 @@ Network usage: no.
|
||||
|
||||
> **apiListGroups**(`userId`, `contactId?`, `search?`): `Promise`\<`GroupInfo`[]\>
|
||||
|
||||
Defined in: [src/api.ts:800](../src/api.ts#L800)
|
||||
Defined in: [src/api.ts:803](../api.ts#L803)
|
||||
|
||||
Get groups.
|
||||
Network usage: no.
|
||||
@@ -851,7 +851,7 @@ Network usage: no.
|
||||
|
||||
> **apiListMembers**(`groupId`): `Promise`\<`GroupMember`[]\>
|
||||
|
||||
Defined in: [src/api.ts:640](../src/api.ts#L640)
|
||||
Defined in: [src/api.ts:643](../api.ts#L643)
|
||||
|
||||
Get group members.
|
||||
Network usage: no.
|
||||
@@ -872,7 +872,7 @@ Network usage: no.
|
||||
|
||||
> **apiListUsers**(): `Promise`\<`UserInfo`[]\>
|
||||
|
||||
Defined in: [src/api.ts:917](../src/api.ts#L917)
|
||||
Defined in: [src/api.ts:920](../api.ts#L920)
|
||||
|
||||
Get all user profiles
|
||||
Network usage: no.
|
||||
@@ -887,7 +887,7 @@ Network usage: no.
|
||||
|
||||
> **apiNewGroup**(`userId`, `groupProfile`): `Promise`\<`GroupInfo`\>
|
||||
|
||||
Defined in: [src/api.ts:650](../src/api.ts#L650)
|
||||
Defined in: [src/api.ts:653](../api.ts#L653)
|
||||
|
||||
Create group.
|
||||
Network usage: no.
|
||||
@@ -912,7 +912,7 @@ Network usage: no.
|
||||
|
||||
> **apiReceiveFile**(`fileId`): `Promise`\<`AChatItem`\>
|
||||
|
||||
Defined in: [src/api.ts:539](../src/api.ts#L539)
|
||||
Defined in: [src/api.ts:542](../api.ts#L542)
|
||||
|
||||
Receive file.
|
||||
Network usage: no.
|
||||
@@ -933,7 +933,7 @@ Network usage: no.
|
||||
|
||||
> **apiRejectContactRequest**(`contactReqId`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:780](../src/api.ts#L780)
|
||||
Defined in: [src/api.ts:783](../api.ts#L783)
|
||||
|
||||
Reject contact request. The user who sent the request is **not notified**.
|
||||
Network usage: no.
|
||||
@@ -954,7 +954,7 @@ Network usage: no.
|
||||
|
||||
> **apiRemoveMembers**(`groupId`, `memberIds`, `withMessages?`): `Promise`\<`GroupMember`[]\>
|
||||
|
||||
Defined in: [src/api.ts:620](../src/api.ts#L620)
|
||||
Defined in: [src/api.ts:623](../api.ts#L623)
|
||||
|
||||
Remove members. Requires Admin role.
|
||||
Network usage: background.
|
||||
@@ -983,7 +983,7 @@ Network usage: background.
|
||||
|
||||
> **apiSendMemberContactInvitation**(`contactId`, `message?`): `Promise`\<`Contact`\>
|
||||
|
||||
Defined in: [src/api.ts:984](../src/api.ts#L984)
|
||||
Defined in: [src/api.ts:987](../api.ts#L987)
|
||||
|
||||
Send a direct message invitation to a group member contact.
|
||||
The contact must have been created with [apiCreateMemberContact](#apicreatemembercontact).
|
||||
@@ -1009,7 +1009,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiSendMessages**(`chat`, `messages`, `liveMessage?`): `Promise`\<`AChatItem`[]\>
|
||||
|
||||
Defined in: [src/api.ts:442](../src/api.ts#L442)
|
||||
Defined in: [src/api.ts:444](../api.ts#L444)
|
||||
|
||||
Send messages.
|
||||
Network usage: background.
|
||||
@@ -1038,7 +1038,7 @@ Network usage: background.
|
||||
|
||||
> **apiSendTextMessage**(`chat`, `text`, `inReplyTo?`): `Promise`\<`AChatItem`[]\>
|
||||
|
||||
Defined in: [src/api.ts:465](../src/api.ts#L465)
|
||||
Defined in: [src/api.ts:467](../api.ts#L467)
|
||||
|
||||
Send text message.
|
||||
Network usage: background.
|
||||
@@ -1067,7 +1067,7 @@ Network usage: background.
|
||||
|
||||
> **apiSendTextReply**(`chatItem`, `text`): `Promise`\<`AChatItem`[]\>
|
||||
|
||||
Defined in: [src/api.ts:473](../src/api.ts#L473)
|
||||
Defined in: [src/api.ts:475](../api.ts#L475)
|
||||
|
||||
Send text message in reply to received message.
|
||||
Network usage: background.
|
||||
@@ -1092,7 +1092,7 @@ Network usage: background.
|
||||
|
||||
> **apiSetActiveUser**(`userId`, `viewPwd?`): `Promise`\<`User`\>
|
||||
|
||||
Defined in: [src/api.ts:927](../src/api.ts#L927)
|
||||
Defined in: [src/api.ts:930](../api.ts#L930)
|
||||
|
||||
Set active user profile
|
||||
Network usage: no.
|
||||
@@ -1117,7 +1117,7 @@ Network usage: no.
|
||||
|
||||
> **apiSetAddressSettings**(`userId`, `__namedParameters`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:414](../src/api.ts#L414)
|
||||
Defined in: [src/api.ts:416](../api.ts#L416)
|
||||
|
||||
Set bot address settings.
|
||||
Network usage: interactive.
|
||||
@@ -1142,7 +1142,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiSetAutoAcceptMemberContacts**(`userId`, `onOff`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:866](../src/api.ts#L866)
|
||||
Defined in: [src/api.ts:869](../api.ts#L869)
|
||||
|
||||
Set auto-accept member contacts.
|
||||
Network usage: no.
|
||||
@@ -1167,7 +1167,7 @@ Network usage: no.
|
||||
|
||||
> **apiSetContactCustomData**(`contactId`, `customData?`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:856](../src/api.ts#L856)
|
||||
Defined in: [src/api.ts:859](../api.ts#L859)
|
||||
|
||||
Set contact custom data.
|
||||
Network usage: no.
|
||||
@@ -1192,7 +1192,7 @@ Network usage: no.
|
||||
|
||||
> **apiSetContactPrefs**(`contactId`, `preferences`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:963](../src/api.ts#L963)
|
||||
Defined in: [src/api.ts:966](../api.ts#L966)
|
||||
|
||||
Configure chat preference overrides for the contact.
|
||||
Network usage: background.
|
||||
@@ -1217,7 +1217,7 @@ Network usage: background.
|
||||
|
||||
> **apiSetGroupCustomData**(`groupId`, `customData?`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:846](../src/api.ts#L846)
|
||||
Defined in: [src/api.ts:849](../api.ts#L849)
|
||||
|
||||
Set group custom data.
|
||||
Network usage: no.
|
||||
@@ -1242,7 +1242,7 @@ Network usage: no.
|
||||
|
||||
> **apiSetGroupLinkMemberRole**(`groupId`, `memberRole`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:683](../src/api.ts#L683)
|
||||
Defined in: [src/api.ts:686](../api.ts#L686)
|
||||
|
||||
Set member role for group link.
|
||||
Network usage: no.
|
||||
@@ -1267,7 +1267,7 @@ Network usage: no.
|
||||
|
||||
> **apiSetMembersRole**(`groupId`, `groupMemberIds`, `memberRole`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:600](../src/api.ts#L600)
|
||||
Defined in: [src/api.ts:603](../api.ts#L603)
|
||||
|
||||
Set members role. Requires Admin role.
|
||||
Network usage: background.
|
||||
@@ -1296,7 +1296,7 @@ Network usage: background.
|
||||
|
||||
> **apiSetProfileAddress**(`userId`, `enable`): `Promise`\<`UserProfileUpdateSummary`\>
|
||||
|
||||
Defined in: [src/api.ts:398](../src/api.ts#L398)
|
||||
Defined in: [src/api.ts:400](../api.ts#L400)
|
||||
|
||||
Add address to bot profile.
|
||||
Network usage: interactive.
|
||||
@@ -1321,7 +1321,7 @@ Network usage: interactive.
|
||||
|
||||
> **apiSetUserDomain**(`userId`, `simplexDomain?`): `Promise`\<`User`\>
|
||||
|
||||
Defined in: [src/api.ts:427](../src/api.ts#L427)
|
||||
Defined in: [src/api.ts:429](../api.ts#L429)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -1341,11 +1341,12 @@ Defined in: [src/api.ts:427](../src/api.ts#L427)
|
||||
|
||||
### apiUpdateChatItem()
|
||||
|
||||
> **apiUpdateChatItem**(`chatType`, `chatId`, `chatItemId`, `msgContent`, `liveMessage`): `Promise`\<`ChatItem`\>
|
||||
> **apiUpdateChatItem**(`chatType`, `chatId`, `chatItemId`, `msgContent`, `liveMessage?`): `Promise`\<`ChatItem`\>
|
||||
|
||||
Defined in: [src/api.ts:481](../src/api.ts#L481)
|
||||
Defined in: [src/api.ts:484](../api.ts#L484)
|
||||
|
||||
Update message.
|
||||
A live message stays live with `liveMessage = true` and is finalized with `liveMessage = false`.
|
||||
Network usage: background.
|
||||
|
||||
#### Parameters
|
||||
@@ -1366,9 +1367,9 @@ Network usage: background.
|
||||
|
||||
`MsgContent`
|
||||
|
||||
##### liveMessage
|
||||
##### liveMessage?
|
||||
|
||||
`false`
|
||||
`boolean` = `false`
|
||||
|
||||
#### Returns
|
||||
|
||||
@@ -1380,7 +1381,7 @@ Network usage: background.
|
||||
|
||||
> **apiUpdateGroupProfile**(`groupId`, `groupProfile`): `Promise`\<`GroupInfo`\>
|
||||
|
||||
Defined in: [src/api.ts:660](../src/api.ts#L660)
|
||||
Defined in: [src/api.ts:663](../api.ts#L663)
|
||||
|
||||
Update group profile.
|
||||
Network usage: background.
|
||||
@@ -1405,7 +1406,7 @@ Network usage: background.
|
||||
|
||||
> **apiUpdateProfile**(`userId`, `profile`): `Promise`\<`UserProfileUpdateSummary` \| `undefined`\>
|
||||
|
||||
Defined in: [src/api.ts:947](../src/api.ts#L947)
|
||||
Defined in: [src/api.ts:950](../api.ts#L950)
|
||||
|
||||
Update user profile.
|
||||
Network usage: background.
|
||||
@@ -1430,7 +1431,7 @@ Network usage: background.
|
||||
|
||||
> **close**(): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:157](../src/api.ts#L157)
|
||||
Defined in: [src/api.ts:162](../api.ts#L162)
|
||||
|
||||
Stop chat controller and close chat database.
|
||||
The database is not closed if stopping fails.
|
||||
@@ -1446,7 +1447,7 @@ Usually doesn't need to be called in chat bots.
|
||||
|
||||
> **off**\<`K`\>(`event`, `subscriber?`): `void`
|
||||
|
||||
Defined in: [src/api.ts:301](../src/api.ts#L301)
|
||||
Defined in: [src/api.ts:303](../api.ts#L303)
|
||||
|
||||
Unsubscribe all or a specific handler from a specific event.
|
||||
|
||||
@@ -1466,7 +1467,7 @@ The event type to unsubscribe from.
|
||||
|
||||
##### subscriber?
|
||||
|
||||
[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`K`\> \| `undefined`
|
||||
[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`K`\> \| `undefined` = `undefined`
|
||||
|
||||
An optional subscriber function for the event.
|
||||
|
||||
@@ -1480,7 +1481,7 @@ An optional subscriber function for the event.
|
||||
|
||||
> **offAny**(`receiver?`): `void`
|
||||
|
||||
Defined in: [src/api.ts:317](../src/api.ts#L317)
|
||||
Defined in: [src/api.ts:319](../api.ts#L319)
|
||||
|
||||
Unsubscribe all or a specific handler from any events.
|
||||
|
||||
@@ -1488,7 +1489,7 @@ Unsubscribe all or a specific handler from any events.
|
||||
|
||||
##### receiver?
|
||||
|
||||
[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`Tag`\> \| `undefined`
|
||||
[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`Tag`\> \| `undefined` = `undefined`
|
||||
|
||||
An optional subscriber function for the event.
|
||||
|
||||
@@ -1504,7 +1505,7 @@ An optional subscriber function for the event.
|
||||
|
||||
> **on**\<`K`\>(`subscribers`): `void`
|
||||
|
||||
Defined in: [src/api.ts:211](../src/api.ts#L211)
|
||||
Defined in: [src/api.ts:211](../api.ts#L211)
|
||||
|
||||
Subscribe multiple event handlers at once.
|
||||
|
||||
@@ -1534,7 +1535,7 @@ If the same function is subscribed to event.
|
||||
|
||||
> **on**\<`K`\>(`event`, `subscriber`): `void`
|
||||
|
||||
Defined in: [src/api.ts:219](../src/api.ts#L219)
|
||||
Defined in: [src/api.ts:219](../api.ts#L219)
|
||||
|
||||
Subscribe a handler to a specific event.
|
||||
|
||||
@@ -1572,7 +1573,7 @@ If the same function is subscribed to event.
|
||||
|
||||
> **onAny**(`receiver`): `void`
|
||||
|
||||
Defined in: [src/api.ts:242](../src/api.ts#L242)
|
||||
Defined in: [src/api.ts:242](../api.ts#L242)
|
||||
|
||||
Subscribe a handler to any event.
|
||||
|
||||
@@ -1598,7 +1599,7 @@ If the same function is subscribed to event.
|
||||
|
||||
> **once**\<`K`\>(`event`, `subscriber`): `void`
|
||||
|
||||
Defined in: [src/api.ts:253](../src/api.ts#L253)
|
||||
Defined in: [src/api.ts:253](../api.ts#L253)
|
||||
|
||||
Subscribe a handler to a specific event to be delivered one time.
|
||||
|
||||
@@ -1636,7 +1637,7 @@ If the same function is subscribed to event.
|
||||
|
||||
> **recvChatEvent**(`wait?`): `Promise`\<`ChatEvent` \| `undefined`\>
|
||||
|
||||
Defined in: [src/api.ts:352](../src/api.ts#L352)
|
||||
Defined in: [src/api.ts:354](../api.ts#L354)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -1654,7 +1655,7 @@ Defined in: [src/api.ts:352](../src/api.ts#L352)
|
||||
|
||||
> **sendChatCmd**(`cmd`): `Promise`\<`ChatResponse`\>
|
||||
|
||||
Defined in: [src/api.ts:348](../src/api.ts#L348)
|
||||
Defined in: [src/api.ts:350](../api.ts#L350)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -1672,7 +1673,7 @@ Defined in: [src/api.ts:348](../src/api.ts#L348)
|
||||
|
||||
> **startChat**(): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:123](../src/api.ts#L123)
|
||||
Defined in: [src/api.ts:128](../api.ts#L128)
|
||||
|
||||
Start chat controller. Must be called with the existing user profile.
|
||||
|
||||
@@ -1686,7 +1687,7 @@ Start chat controller. Must be called with the existing user profile.
|
||||
|
||||
> **stopChat**(): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:146](../src/api.ts#L146)
|
||||
Defined in: [src/api.ts:151](../api.ts#L151)
|
||||
|
||||
Stop chat controller.
|
||||
`close` calls it before closing the database.
|
||||
@@ -1704,7 +1705,7 @@ Usually doesn't need to be called in chat bots.
|
||||
|
||||
> **wait**\<`K`\>(`event`): `Promise`\<`ChatEvent` & `object`\>
|
||||
|
||||
Defined in: [src/api.ts:261](../src/api.ts#L261)
|
||||
Defined in: [src/api.ts:261](../api.ts#L261)
|
||||
|
||||
Waits for specific event, with an optional predicate.
|
||||
Returns `undefined` on timeout if specified.
|
||||
@@ -1729,7 +1730,7 @@ Returns `undefined` on timeout if specified.
|
||||
|
||||
> **wait**\<`K`\>(`event`, `predicate`): `Promise`\<`ChatEvent` & `object`\>
|
||||
|
||||
Defined in: [src/api.ts:262](../src/api.ts#L262)
|
||||
Defined in: [src/api.ts:262](../api.ts#L262)
|
||||
|
||||
Waits for specific event, with an optional predicate.
|
||||
Returns `undefined` on timeout if specified.
|
||||
@@ -1758,7 +1759,7 @@ Returns `undefined` on timeout if specified.
|
||||
|
||||
> **wait**\<`K`\>(`event`, `timeout`): `Promise`\<ChatEvent & \{ type: K; \} \| `undefined`\>
|
||||
|
||||
Defined in: [src/api.ts:263](../src/api.ts#L263)
|
||||
Defined in: [src/api.ts:263](../api.ts#L263)
|
||||
|
||||
Waits for specific event, with an optional predicate.
|
||||
Returns `undefined` on timeout if specified.
|
||||
@@ -1787,7 +1788,7 @@ Returns `undefined` on timeout if specified.
|
||||
|
||||
> **wait**\<`K`\>(`event`, `predicate`, `timeout`): `Promise`\<ChatEvent & \{ type: K; \} \| `undefined`\>
|
||||
|
||||
Defined in: [src/api.ts:264](../src/api.ts#L264)
|
||||
Defined in: [src/api.ts:264](../api.ts#L264)
|
||||
|
||||
Waits for specific event, with an optional predicate.
|
||||
Returns `undefined` on timeout if specified.
|
||||
@@ -1822,7 +1823,7 @@ Returns `undefined` on timeout if specified.
|
||||
|
||||
> `static` **init**(`db`, `confirm?`, `queueSize?`): `Promise`\<`ChatApi`\>
|
||||
|
||||
Defined in: [src/api.ts:109](../src/api.ts#L109)
|
||||
Defined in: [src/api.ts:114](../api.ts#L114)
|
||||
|
||||
Initializes the ChatApi, loading libsimplex for `db.type` (downloaded on first use).
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Class: ChatCommandError
|
||||
|
||||
Defined in: [src/api.ts:5](../src/api.ts#L5)
|
||||
Defined in: [src/api.ts:5](../api.ts#L5)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/api.ts:5](../src/api.ts#L5)
|
||||
|
||||
> **new ChatCommandError**(`message`, `response`): `ChatCommandError`
|
||||
|
||||
Defined in: [src/api.ts:6](../src/api.ts#L6)
|
||||
Defined in: [src/api.ts:6](../api.ts#L6)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -44,7 +44,7 @@ Defined in: [src/api.ts:6](../src/api.ts#L6)
|
||||
|
||||
> **message**: `string`
|
||||
|
||||
Defined in: [src/api.ts:6](../src/api.ts#L6)
|
||||
Defined in: [src/api.ts:6](../api.ts#L6)
|
||||
|
||||
#### Inherited from
|
||||
|
||||
@@ -56,7 +56,7 @@ Defined in: [src/api.ts:6](../src/api.ts#L6)
|
||||
|
||||
> **name**: `string`
|
||||
|
||||
Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typescript/lib/lib.es5.d.ts#L1076)
|
||||
Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../../node_modules/typescript/lib/lib.es5.d.ts#L1076)
|
||||
|
||||
#### Inherited from
|
||||
|
||||
@@ -68,7 +68,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typ
|
||||
|
||||
> **response**: `ChatResponse`
|
||||
|
||||
Defined in: [src/api.ts:6](../src/api.ts#L6)
|
||||
Defined in: [src/api.ts:6](../api.ts#L6)
|
||||
|
||||
***
|
||||
|
||||
@@ -76,7 +76,7 @@ Defined in: [src/api.ts:6](../src/api.ts#L6)
|
||||
|
||||
> `optional` **stack?**: `string`
|
||||
|
||||
Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078)
|
||||
Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../../node_modules/typescript/lib/lib.es5.d.ts#L1078)
|
||||
|
||||
#### Inherited from
|
||||
|
||||
@@ -88,7 +88,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typ
|
||||
|
||||
> `static` **stackTraceLimit**: `number`
|
||||
|
||||
Defined in: [node\_modules/@types/node/globals.d.ts:67](../node_modules/@types/node/globals.d.ts#L67)
|
||||
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
|
||||
@@ -110,7 +110,7 @@ not capture any frames.
|
||||
|
||||
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
|
||||
|
||||
Defined in: [node\_modules/@types/node/globals.d.ts:51](../node_modules/@types/node/globals.d.ts#L51)
|
||||
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
|
||||
@@ -180,7 +180,7 @@ a();
|
||||
|
||||
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
|
||||
|
||||
Defined in: [node\_modules/@types/node/globals.d.ts:55](../node_modules/@types/node/globals.d.ts#L55)
|
||||
Defined in: [node\_modules/@types/node/globals.d.ts:55](../../node_modules/@types/node/globals.d.ts#L55)
|
||||
|
||||
#### Parameters
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
[**simplex-chat**](README.md)
|
||||
|
||||
***
|
||||
|
||||
[simplex-chat](README.md) / [api](Namespace.api.md) / LiveMessage
|
||||
|
||||
# Class: LiveMessage
|
||||
|
||||
Defined in: [src/api.ts:1005](../api.ts#L1005)
|
||||
|
||||
Text shown as a live message while it is written, e.g. a streamed reply.
|
||||
|
||||
## Constructors
|
||||
|
||||
### Constructor
|
||||
|
||||
> **new LiveMessage**(`chat`, `chatType`, `chatId`, `interval?`, `onError?`): `LiveMessage`
|
||||
|
||||
Defined in: [src/api.ts:1019](../api.ts#L1019)
|
||||
|
||||
#### Parameters
|
||||
|
||||
##### chat
|
||||
|
||||
`Pick`\<[`ChatApi`](api.Class.ChatApi.md), `"apiSendMessages"` \| `"apiUpdateChatItem"` \| `"apiDeleteChatItems"`\>
|
||||
|
||||
Chat API, or an object with its methods to send, update and delete messages.
|
||||
|
||||
##### chatType
|
||||
|
||||
`ChatType`
|
||||
|
||||
Chat type.
|
||||
|
||||
##### chatId
|
||||
|
||||
`number`
|
||||
|
||||
Contact or group ID.
|
||||
|
||||
##### interval?
|
||||
|
||||
`number` = `3000`
|
||||
|
||||
Minimum interval between updates of the live message, in milliseconds.
|
||||
|
||||
##### onError?
|
||||
|
||||
(`error`) => `void`
|
||||
|
||||
Error handler for sending, updating and deleting messages. Errors are logged when it is omitted.
|
||||
|
||||
#### Returns
|
||||
|
||||
`LiveMessage`
|
||||
|
||||
## Methods
|
||||
|
||||
### end()
|
||||
|
||||
> **end**(`text?`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:1049](../api.ts#L1049)
|
||||
|
||||
Finalize the live message with the text, or with the last text when omitted.
|
||||
A message without text is deleted. The next update starts a new live message.
|
||||
The returned promise resolves after all queued commands complete.
|
||||
|
||||
#### Parameters
|
||||
|
||||
##### text?
|
||||
|
||||
`string` = `...`
|
||||
|
||||
#### Returns
|
||||
|
||||
`Promise`\<`void`\>
|
||||
|
||||
***
|
||||
|
||||
### update()
|
||||
|
||||
> **update**(`text`): `void`
|
||||
|
||||
Defined in: [src/api.ts:1031](../api.ts#L1031)
|
||||
|
||||
Set the text of the live message.
|
||||
The first text is sent at once; later texts update the message at most once per interval.
|
||||
|
||||
#### Parameters
|
||||
|
||||
##### text
|
||||
|
||||
`string`
|
||||
|
||||
#### Returns
|
||||
|
||||
`void`
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Enumeration: ConnReqType
|
||||
|
||||
Defined in: [src/api.ts:15](../src/api.ts#L15)
|
||||
Defined in: [src/api.ts:15](../api.ts#L15)
|
||||
|
||||
Connection request types.
|
||||
|
||||
@@ -16,7 +16,7 @@ Connection request types.
|
||||
|
||||
> **Contact**: `"contact"`
|
||||
|
||||
Defined in: [src/api.ts:17](../src/api.ts#L17)
|
||||
Defined in: [src/api.ts:17](../api.ts#L17)
|
||||
|
||||
***
|
||||
|
||||
@@ -24,4 +24,4 @@ Defined in: [src/api.ts:17](../src/api.ts#L17)
|
||||
|
||||
> **Invitation**: `"invitation"`
|
||||
|
||||
Defined in: [src/api.ts:16](../src/api.ts#L16)
|
||||
Defined in: [src/api.ts:16](../api.ts#L16)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: BotAddressSettings
|
||||
|
||||
Defined in: [src/api.ts:23](../src/api.ts#L23)
|
||||
Defined in: [src/api.ts:23](../api.ts#L23)
|
||||
|
||||
Bot address settings.
|
||||
|
||||
@@ -16,7 +16,7 @@ Bot address settings.
|
||||
|
||||
> `optional` **autoAccept?**: `boolean`
|
||||
|
||||
Defined in: [src/api.ts:28](../src/api.ts#L28)
|
||||
Defined in: [src/api.ts:28](../api.ts#L28)
|
||||
|
||||
Automatically accept contact requests.
|
||||
|
||||
@@ -32,7 +32,7 @@ true
|
||||
|
||||
> `optional` **businessAddress?**: `boolean`
|
||||
|
||||
Defined in: [src/api.ts:41](../src/api.ts#L41)
|
||||
Defined in: [src/api.ts:41](../api.ts#L41)
|
||||
|
||||
Business contact address.
|
||||
For all requests business chats will be created where other participants can be added.
|
||||
@@ -49,7 +49,7 @@ false
|
||||
|
||||
> `optional` **welcomeMessage?**: `string` \| `MsgContent`
|
||||
|
||||
Defined in: [src/api.ts:34](../src/api.ts#L34)
|
||||
Defined in: [src/api.ts:34](../api.ts#L34)
|
||||
|
||||
Optional welcome message to show before connection to the users.
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **DbConfig** = \{ `encryptionKey?`: `string`; `filePrefix`: `string`; `type`: `"sqlite"`; \} \| \{ `connectionString`: `string`; `schemaPrefix?`: `string`; `type`: `"postgres"`; \}
|
||||
|
||||
Defined in: [src/api.ts:63](../src/api.ts#L63)
|
||||
Defined in: [src/api.ts:63](../api.ts#L63)
|
||||
|
||||
Database configuration. `type` selects the libsimplex backend loaded by
|
||||
`ChatApi.init`; one backend per process.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **EventSubscriberFunc**\<`K`\> = (`event`, `chat`) => `void` \| `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/api.ts:50](../src/api.ts#L50)
|
||||
Defined in: [src/api.ts:50](../api.ts#L50)
|
||||
|
||||
## Type Parameters
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **EventSubscribers** = `{ [K in CEvt.Tag]?: EventSubscriberFunc<K> }`
|
||||
|
||||
Defined in: [src/api.ts:52](../src/api.ts#L52)
|
||||
Defined in: [src/api.ts:52](../api.ts#L52)
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> `const` **defaultBotAddressSettings**: [`BotAddressSettings`](api.Interface.BotAddressSettings.md)
|
||||
|
||||
Defined in: [src/api.ts:44](../src/api.ts#L44)
|
||||
Defined in: [src/api.ts:44](../api.ts#L44)
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **run**(`__namedParameters`): `Promise`\<\[[`ChatApi`](api.Class.ChatApi.md), `User`, `UserContactLink` \| `undefined`\]\>
|
||||
|
||||
Defined in: [src/bot.ts:49](../src/bot.ts#L49)
|
||||
Defined in: [src/bot.ts:49](../bot.ts#L49)
|
||||
|
||||
## Parameters
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **subscribeChatItems**(`bot`, `onMessage`, `commands`): `void`
|
||||
|
||||
Defined in: [src/bot.ts:110](../src/bot.ts#L110)
|
||||
Defined in: [src/bot.ts:110](../bot.ts#L110)
|
||||
|
||||
## Parameters
|
||||
|
||||
@@ -22,6 +22,8 @@ Defined in: [src/bot.ts:110](../src/bot.ts#L110)
|
||||
|
||||
### commands
|
||||
|
||||
\{\[`key`: `string`\]: ((`chatItem`, `command`, `chat`) => `void` \| `Promise`\<`void`\>) \| `undefined`; \}
|
||||
|
||||
## Returns
|
||||
|
||||
`void`
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: BotConfig
|
||||
|
||||
Defined in: [src/bot.ts:36](../src/bot.ts#L36)
|
||||
Defined in: [src/bot.ts:36](../bot.ts#L36)
|
||||
|
||||
## Properties
|
||||
|
||||
@@ -14,7 +14,7 @@ Defined in: [src/bot.ts:36](../src/bot.ts#L36)
|
||||
|
||||
> **dbOpts**: [`BotDbOpts`](bot.TypeAlias.BotDbOpts.md)
|
||||
|
||||
Defined in: [src/bot.ts:39](../src/bot.ts#L39)
|
||||
Defined in: [src/bot.ts:39](../bot.ts#L39)
|
||||
|
||||
***
|
||||
|
||||
@@ -22,7 +22,7 @@ Defined in: [src/bot.ts:39](../src/bot.ts#L39)
|
||||
|
||||
> `optional` **events?**: [`EventSubscribers`](api.TypeAlias.EventSubscribers.md)
|
||||
|
||||
Defined in: [src/bot.ts:46](../src/bot.ts#L46)
|
||||
Defined in: [src/bot.ts:46](../bot.ts#L46)
|
||||
|
||||
***
|
||||
|
||||
@@ -30,7 +30,7 @@ Defined in: [src/bot.ts:46](../src/bot.ts#L46)
|
||||
|
||||
> `optional` **onCommands?**: `object`
|
||||
|
||||
Defined in: [src/bot.ts:43](../src/bot.ts#L43)
|
||||
Defined in: [src/bot.ts:43](../bot.ts#L43)
|
||||
|
||||
#### Index Signature
|
||||
|
||||
@@ -42,7 +42,7 @@ Defined in: [src/bot.ts:43](../src/bot.ts#L43)
|
||||
|
||||
> `optional` **onMessage?**: (`chatItem`, `content`, `chat`) => `void` \| `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/bot.ts:41](../src/bot.ts#L41)
|
||||
Defined in: [src/bot.ts:41](../bot.ts#L41)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -68,7 +68,7 @@ Defined in: [src/bot.ts:41](../src/bot.ts#L41)
|
||||
|
||||
> **options**: [`BotOptions`](bot.Interface.BotOptions.md)
|
||||
|
||||
Defined in: [src/bot.ts:40](../src/bot.ts#L40)
|
||||
Defined in: [src/bot.ts:40](../bot.ts#L40)
|
||||
|
||||
***
|
||||
|
||||
@@ -76,7 +76,7 @@ Defined in: [src/bot.ts:40](../src/bot.ts#L40)
|
||||
|
||||
> **profile**: `Profile`
|
||||
|
||||
Defined in: [src/bot.ts:37](../src/bot.ts#L37)
|
||||
Defined in: [src/bot.ts:37](../bot.ts#L37)
|
||||
|
||||
***
|
||||
|
||||
@@ -84,4 +84,4 @@ Defined in: [src/bot.ts:37](../src/bot.ts#L37)
|
||||
|
||||
> `optional` **simplexDomain?**: `string` \| `null`
|
||||
|
||||
Defined in: [src/bot.ts:38](../src/bot.ts#L38)
|
||||
Defined in: [src/bot.ts:38](../bot.ts#L38)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: BotOptions
|
||||
|
||||
Defined in: [src/bot.ts:12](../src/bot.ts#L12)
|
||||
Defined in: [src/bot.ts:12](../bot.ts#L12)
|
||||
|
||||
## Properties
|
||||
|
||||
@@ -14,7 +14,7 @@ Defined in: [src/bot.ts:12](../src/bot.ts#L12)
|
||||
|
||||
> `optional` **addressSettings?**: [`BotAddressSettings`](api.Interface.BotAddressSettings.md)
|
||||
|
||||
Defined in: [src/bot.ts:16](../src/bot.ts#L16)
|
||||
Defined in: [src/bot.ts:16](../bot.ts#L16)
|
||||
|
||||
***
|
||||
|
||||
@@ -22,7 +22,7 @@ Defined in: [src/bot.ts:16](../src/bot.ts#L16)
|
||||
|
||||
> `optional` **allowFiles?**: `boolean`
|
||||
|
||||
Defined in: [src/bot.ts:17](../src/bot.ts#L17)
|
||||
Defined in: [src/bot.ts:17](../bot.ts#L17)
|
||||
|
||||
***
|
||||
|
||||
@@ -30,7 +30,7 @@ Defined in: [src/bot.ts:17](../src/bot.ts#L17)
|
||||
|
||||
> `optional` **commands?**: `ChatBotCommand`[]
|
||||
|
||||
Defined in: [src/bot.ts:18](../src/bot.ts#L18)
|
||||
Defined in: [src/bot.ts:18](../bot.ts#L18)
|
||||
|
||||
***
|
||||
|
||||
@@ -38,7 +38,7 @@ Defined in: [src/bot.ts:18](../src/bot.ts#L18)
|
||||
|
||||
> `optional` **createAddress?**: `boolean`
|
||||
|
||||
Defined in: [src/bot.ts:13](../src/bot.ts#L13)
|
||||
Defined in: [src/bot.ts:13](../bot.ts#L13)
|
||||
|
||||
***
|
||||
|
||||
@@ -46,7 +46,7 @@ Defined in: [src/bot.ts:13](../src/bot.ts#L13)
|
||||
|
||||
> `optional` **logContacts?**: `boolean`
|
||||
|
||||
Defined in: [src/bot.ts:20](../src/bot.ts#L20)
|
||||
Defined in: [src/bot.ts:20](../bot.ts#L20)
|
||||
|
||||
***
|
||||
|
||||
@@ -54,7 +54,7 @@ Defined in: [src/bot.ts:20](../src/bot.ts#L20)
|
||||
|
||||
> `optional` **logNetwork?**: `boolean`
|
||||
|
||||
Defined in: [src/bot.ts:21](../src/bot.ts#L21)
|
||||
Defined in: [src/bot.ts:21](../bot.ts#L21)
|
||||
|
||||
***
|
||||
|
||||
@@ -62,7 +62,7 @@ Defined in: [src/bot.ts:21](../src/bot.ts#L21)
|
||||
|
||||
> `optional` **updateAddress?**: `boolean`
|
||||
|
||||
Defined in: [src/bot.ts:14](../src/bot.ts#L14)
|
||||
Defined in: [src/bot.ts:14](../bot.ts#L14)
|
||||
|
||||
***
|
||||
|
||||
@@ -70,7 +70,7 @@ Defined in: [src/bot.ts:14](../src/bot.ts#L14)
|
||||
|
||||
> `optional` **updateProfile?**: `boolean`
|
||||
|
||||
Defined in: [src/bot.ts:15](../src/bot.ts#L15)
|
||||
Defined in: [src/bot.ts:15](../bot.ts#L15)
|
||||
|
||||
***
|
||||
|
||||
@@ -78,4 +78,4 @@ Defined in: [src/bot.ts:15](../src/bot.ts#L15)
|
||||
|
||||
> `optional` **useBotProfile?**: `boolean`
|
||||
|
||||
Defined in: [src/bot.ts:19](../src/bot.ts#L19)
|
||||
Defined in: [src/bot.ts:19](../bot.ts#L19)
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **BotDbOpts** = [`DbConfig`](api.TypeAlias.DbConfig.md) & `object`
|
||||
|
||||
Defined in: [src/bot.ts:7](../src/bot.ts#L7)
|
||||
Defined in: [src/bot.ts:7](../bot.ts#L7)
|
||||
|
||||
## Type Declaration
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Class: ChatAPIError
|
||||
|
||||
Defined in: [src/core.ts:117](../src/core.ts#L117)
|
||||
Defined in: [src/core.ts:117](../core.ts#L117)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:117](../src/core.ts#L117)
|
||||
|
||||
> **new ChatAPIError**(`message`, `chatError?`): `ChatAPIError`
|
||||
|
||||
Defined in: [src/core.ts:118](../src/core.ts#L118)
|
||||
Defined in: [src/core.ts:118](../core.ts#L118)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -28,7 +28,7 @@ Defined in: [src/core.ts:118](../src/core.ts#L118)
|
||||
|
||||
##### chatError?
|
||||
|
||||
`ChatError` \| `undefined`
|
||||
`ChatError` \| `undefined` = `undefined`
|
||||
|
||||
#### Returns
|
||||
|
||||
@@ -44,7 +44,7 @@ Defined in: [src/core.ts:118](../src/core.ts#L118)
|
||||
|
||||
> **chatError**: `ChatError` \| `undefined` = `undefined`
|
||||
|
||||
Defined in: [src/core.ts:118](../src/core.ts#L118)
|
||||
Defined in: [src/core.ts:118](../core.ts#L118)
|
||||
|
||||
***
|
||||
|
||||
@@ -52,7 +52,7 @@ Defined in: [src/core.ts:118](../src/core.ts#L118)
|
||||
|
||||
> **message**: `string`
|
||||
|
||||
Defined in: [src/core.ts:118](../src/core.ts#L118)
|
||||
Defined in: [src/core.ts:118](../core.ts#L118)
|
||||
|
||||
#### Inherited from
|
||||
|
||||
@@ -64,7 +64,7 @@ Defined in: [src/core.ts:118](../src/core.ts#L118)
|
||||
|
||||
> **name**: `string`
|
||||
|
||||
Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typescript/lib/lib.es5.d.ts#L1076)
|
||||
Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../../node_modules/typescript/lib/lib.es5.d.ts#L1076)
|
||||
|
||||
#### Inherited from
|
||||
|
||||
@@ -76,7 +76,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typ
|
||||
|
||||
> `optional` **stack?**: `string`
|
||||
|
||||
Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078)
|
||||
Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../../node_modules/typescript/lib/lib.es5.d.ts#L1078)
|
||||
|
||||
#### Inherited from
|
||||
|
||||
@@ -88,7 +88,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typ
|
||||
|
||||
> `static` **stackTraceLimit**: `number`
|
||||
|
||||
Defined in: [node\_modules/@types/node/globals.d.ts:67](../node_modules/@types/node/globals.d.ts#L67)
|
||||
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
|
||||
@@ -110,7 +110,7 @@ not capture any frames.
|
||||
|
||||
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
|
||||
|
||||
Defined in: [node\_modules/@types/node/globals.d.ts:51](../node_modules/@types/node/globals.d.ts#L51)
|
||||
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
|
||||
@@ -180,7 +180,7 @@ a();
|
||||
|
||||
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
|
||||
|
||||
Defined in: [node\_modules/@types/node/globals.d.ts:55](../node_modules/@types/node/globals.d.ts#L55)
|
||||
Defined in: [node\_modules/@types/node/globals.d.ts:55](../../node_modules/@types/node/globals.d.ts#L55)
|
||||
|
||||
#### Parameters
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Class: ChatInitError
|
||||
|
||||
Defined in: [src/core.ts:141](../src/core.ts#L141)
|
||||
Defined in: [src/core.ts:141](../core.ts#L141)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:141](../src/core.ts#L141)
|
||||
|
||||
> **new ChatInitError**(`message`, `dbMigrationError`): `ChatInitError`
|
||||
|
||||
Defined in: [src/core.ts:142](../src/core.ts#L142)
|
||||
Defined in: [src/core.ts:142](../core.ts#L142)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -44,7 +44,7 @@ Defined in: [src/core.ts:142](../src/core.ts#L142)
|
||||
|
||||
> **dbMigrationError**: [`DBMigrationError`](core.TypeAlias.DBMigrationError.md)
|
||||
|
||||
Defined in: [src/core.ts:142](../src/core.ts#L142)
|
||||
Defined in: [src/core.ts:142](../core.ts#L142)
|
||||
|
||||
***
|
||||
|
||||
@@ -52,7 +52,7 @@ Defined in: [src/core.ts:142](../src/core.ts#L142)
|
||||
|
||||
> **message**: `string`
|
||||
|
||||
Defined in: [src/core.ts:142](../src/core.ts#L142)
|
||||
Defined in: [src/core.ts:142](../core.ts#L142)
|
||||
|
||||
#### Inherited from
|
||||
|
||||
@@ -64,7 +64,7 @@ Defined in: [src/core.ts:142](../src/core.ts#L142)
|
||||
|
||||
> **name**: `string`
|
||||
|
||||
Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typescript/lib/lib.es5.d.ts#L1076)
|
||||
Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../../node_modules/typescript/lib/lib.es5.d.ts#L1076)
|
||||
|
||||
#### Inherited from
|
||||
|
||||
@@ -76,7 +76,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typ
|
||||
|
||||
> `optional` **stack?**: `string`
|
||||
|
||||
Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078)
|
||||
Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../../node_modules/typescript/lib/lib.es5.d.ts#L1078)
|
||||
|
||||
#### Inherited from
|
||||
|
||||
@@ -88,7 +88,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typ
|
||||
|
||||
> `static` **stackTraceLimit**: `number`
|
||||
|
||||
Defined in: [node\_modules/@types/node/globals.d.ts:67](../node_modules/@types/node/globals.d.ts#L67)
|
||||
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
|
||||
@@ -110,7 +110,7 @@ not capture any frames.
|
||||
|
||||
> `static` **captureStackTrace**(`targetObject`, `constructorOpt?`): `void`
|
||||
|
||||
Defined in: [node\_modules/@types/node/globals.d.ts:51](../node_modules/@types/node/globals.d.ts#L51)
|
||||
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
|
||||
@@ -180,7 +180,7 @@ a();
|
||||
|
||||
> `static` **prepareStackTrace**(`err`, `stackTraces`): `any`
|
||||
|
||||
Defined in: [node\_modules/@types/node/globals.d.ts:55](../node_modules/@types/node/globals.d.ts#L55)
|
||||
Defined in: [node\_modules/@types/node/globals.d.ts:55](../../node_modules/@types/node/globals.d.ts#L55)
|
||||
|
||||
#### Parameters
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: ErrorMigration
|
||||
|
||||
Defined in: [src/core.ts:174](../src/core.ts#L174)
|
||||
Defined in: [src/core.ts:174](../core.ts#L174)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:174](../src/core.ts#L174)
|
||||
|
||||
> **dbFile**: `string`
|
||||
|
||||
Defined in: [src/core.ts:176](../src/core.ts#L176)
|
||||
Defined in: [src/core.ts:176](../core.ts#L176)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:176](../src/core.ts#L176)
|
||||
|
||||
> **migrationError**: [`MigrationError`](core.TypeAlias.MigrationError.md)
|
||||
|
||||
Defined in: [src/core.ts:177](../src/core.ts#L177)
|
||||
Defined in: [src/core.ts:177](../core.ts#L177)
|
||||
|
||||
***
|
||||
|
||||
@@ -34,7 +34,7 @@ Defined in: [src/core.ts:177](../src/core.ts#L177)
|
||||
|
||||
> **type**: `"errorMigration"`
|
||||
|
||||
Defined in: [src/core.ts:175](../src/core.ts#L175)
|
||||
Defined in: [src/core.ts:175](../core.ts#L175)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: ErrorNotADatabase
|
||||
|
||||
Defined in: [src/core.ts:169](../src/core.ts#L169)
|
||||
Defined in: [src/core.ts:169](../core.ts#L169)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:169](../src/core.ts#L169)
|
||||
|
||||
> **dbFile**: `string`
|
||||
|
||||
Defined in: [src/core.ts:171](../src/core.ts#L171)
|
||||
Defined in: [src/core.ts:171](../core.ts#L171)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:171](../src/core.ts#L171)
|
||||
|
||||
> **type**: `"errorNotADatabase"`
|
||||
|
||||
Defined in: [src/core.ts:170](../src/core.ts#L170)
|
||||
Defined in: [src/core.ts:170](../core.ts#L170)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: ErrorSQL
|
||||
|
||||
Defined in: [src/core.ts:180](../src/core.ts#L180)
|
||||
Defined in: [src/core.ts:180](../core.ts#L180)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:180](../src/core.ts#L180)
|
||||
|
||||
> **dbFile**: `string`
|
||||
|
||||
Defined in: [src/core.ts:182](../src/core.ts#L182)
|
||||
Defined in: [src/core.ts:182](../core.ts#L182)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:182](../src/core.ts#L182)
|
||||
|
||||
> **migrationSQLError**: `string`
|
||||
|
||||
Defined in: [src/core.ts:183](../src/core.ts#L183)
|
||||
Defined in: [src/core.ts:183](../core.ts#L183)
|
||||
|
||||
***
|
||||
|
||||
@@ -34,7 +34,7 @@ Defined in: [src/core.ts:183](../src/core.ts#L183)
|
||||
|
||||
> **type**: `"errorSQL"`
|
||||
|
||||
Defined in: [src/core.ts:181](../src/core.ts#L181)
|
||||
Defined in: [src/core.ts:181](../core.ts#L181)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: InvalidConfirmation
|
||||
|
||||
Defined in: [src/core.ts:161](../src/core.ts#L161)
|
||||
Defined in: [src/core.ts:161](../core.ts#L161)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:161](../src/core.ts#L161)
|
||||
|
||||
> **type**: `"invalidConfirmation"`
|
||||
|
||||
Defined in: [src/core.ts:162](../src/core.ts#L162)
|
||||
Defined in: [src/core.ts:162](../core.ts#L162)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: InvalidQueueSize
|
||||
|
||||
Defined in: [src/core.ts:165](../src/core.ts#L165)
|
||||
Defined in: [src/core.ts:165](../core.ts#L165)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:165](../src/core.ts#L165)
|
||||
|
||||
> **type**: `"invalidQueueSize"`
|
||||
|
||||
Defined in: [src/core.ts:166](../src/core.ts#L166)
|
||||
Defined in: [src/core.ts:166](../core.ts#L166)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **Tag** = `"invalidConfirmation"` \| `"invalidQueueSize"` \| `"errorNotADatabase"` \| `"errorMigration"` \| `"errorSQL"`
|
||||
|
||||
Defined in: [src/core.ts:155](../src/core.ts#L155)
|
||||
Defined in: [src/core.ts:155](../core.ts#L155)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Enumeration: MigrationConfirmation
|
||||
|
||||
Defined in: [src/core.ts:126](../src/core.ts#L126)
|
||||
Defined in: [src/core.ts:126](../core.ts#L126)
|
||||
|
||||
Migration confirmation mode
|
||||
|
||||
@@ -16,7 +16,7 @@ Migration confirmation mode
|
||||
|
||||
> **Console**: `"console"`
|
||||
|
||||
Defined in: [src/core.ts:129](../src/core.ts#L129)
|
||||
Defined in: [src/core.ts:129](../core.ts#L129)
|
||||
|
||||
***
|
||||
|
||||
@@ -24,7 +24,7 @@ Defined in: [src/core.ts:129](../src/core.ts#L129)
|
||||
|
||||
> **Error**: `"error"`
|
||||
|
||||
Defined in: [src/core.ts:130](../src/core.ts#L130)
|
||||
Defined in: [src/core.ts:130](../core.ts#L130)
|
||||
|
||||
***
|
||||
|
||||
@@ -32,7 +32,7 @@ Defined in: [src/core.ts:130](../src/core.ts#L130)
|
||||
|
||||
> **YesUp**: `"yesUp"`
|
||||
|
||||
Defined in: [src/core.ts:127](../src/core.ts#L127)
|
||||
Defined in: [src/core.ts:127](../core.ts#L127)
|
||||
|
||||
***
|
||||
|
||||
@@ -40,4 +40,4 @@ Defined in: [src/core.ts:127](../src/core.ts#L127)
|
||||
|
||||
> **YesUpDown**: `"yesUpDown"`
|
||||
|
||||
Defined in: [src/core.ts:128](../src/core.ts#L128)
|
||||
Defined in: [src/core.ts:128](../core.ts#L128)
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatCloseStore**(`ctrl`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/core.ts:42](../src/core.ts#L42)
|
||||
Defined in: [src/core.ts:42](../core.ts#L42)
|
||||
|
||||
Close chat store
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatDecryptFile**(`fromPath`, `__namedParameters`, `toPath`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/core.ts:98](../src/core.ts#L98)
|
||||
Defined in: [src/core.ts:98](../core.ts#L98)
|
||||
|
||||
Decrypt file
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatEncryptFile**(`ctrl`, `fromPath`, `toPath`): `Promise`\<[`CryptoArgs`](core.Interface.CryptoArgs.md)\>
|
||||
|
||||
Defined in: [src/core.ts:90](../src/core.ts#L90)
|
||||
Defined in: [src/core.ts:90](../core.ts#L90)
|
||||
|
||||
Encrypt file
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatMigrateInit**(`dbPath`, `dbKey`, `confirm`, `queueSize?`): `Promise`\<`bigint`\>
|
||||
|
||||
Defined in: [src/core.ts:30](../src/core.ts#L30)
|
||||
Defined in: [src/core.ts:30](../core.ts#L30)
|
||||
|
||||
Initialize chat controller
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatReadFile**(`path`, `__namedParameters`): `Promise`\<`Buffer`\<`ArrayBufferLike`\>\>
|
||||
|
||||
Defined in: [src/core.ts:83](../src/core.ts#L83)
|
||||
Defined in: [src/core.ts:83](../core.ts#L83)
|
||||
|
||||
Read buffer from encrypted file
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatRecvMsgWait**(`ctrl`, `wait`): `Promise`\<`ChatEvent` \| `undefined`\>
|
||||
|
||||
Defined in: [src/core.ts:62](../src/core.ts#L62)
|
||||
Defined in: [src/core.ts:62](../core.ts#L62)
|
||||
|
||||
Receive chat event
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatSendCmd**(`ctrl`, `cmd`): `Promise`\<`ChatResponse`\>
|
||||
|
||||
Defined in: [src/core.ts:50](../src/core.ts#L50)
|
||||
Defined in: [src/core.ts:50](../core.ts#L50)
|
||||
|
||||
Send chat command as string
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatWriteFile**(`ctrl`, `path`, `buffer`): `Promise`\<[`CryptoArgs`](core.Interface.CryptoArgs.md)\>
|
||||
|
||||
Defined in: [src/core.ts:75](../src/core.ts#L75)
|
||||
Defined in: [src/core.ts:75](../core.ts#L75)
|
||||
|
||||
Write buffer to encrypted file
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **loadLibrary**(`backend`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/core.ts:13](../src/core.ts#L13)
|
||||
Defined in: [src/core.ts:13](../core.ts#L13)
|
||||
|
||||
Resolve (downloading on first use) and load libsimplex for the backend.
|
||||
One libsimplex per process: the Haskell runtime is initialized once, and another backend is rejected.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: APIResult\<R\>
|
||||
|
||||
Defined in: [src/core.ts:112](../src/core.ts#L112)
|
||||
Defined in: [src/core.ts:112](../core.ts#L112)
|
||||
|
||||
## Type Parameters
|
||||
|
||||
@@ -20,7 +20,7 @@ Defined in: [src/core.ts:112](../src/core.ts#L112)
|
||||
|
||||
> `optional` **error?**: `ChatError`
|
||||
|
||||
Defined in: [src/core.ts:114](../src/core.ts#L114)
|
||||
Defined in: [src/core.ts:114](../core.ts#L114)
|
||||
|
||||
***
|
||||
|
||||
@@ -28,4 +28,4 @@ Defined in: [src/core.ts:114](../src/core.ts#L114)
|
||||
|
||||
> `optional` **result?**: `R`
|
||||
|
||||
Defined in: [src/core.ts:113](../src/core.ts#L113)
|
||||
Defined in: [src/core.ts:113](../core.ts#L113)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: CryptoArgs
|
||||
|
||||
Defined in: [src/core.ts:136](../src/core.ts#L136)
|
||||
Defined in: [src/core.ts:136](../core.ts#L136)
|
||||
|
||||
File encryption key and nonce
|
||||
|
||||
@@ -16,7 +16,7 @@ File encryption key and nonce
|
||||
|
||||
> **fileKey**: `string`
|
||||
|
||||
Defined in: [src/core.ts:137](../src/core.ts#L137)
|
||||
Defined in: [src/core.ts:137](../core.ts#L137)
|
||||
|
||||
***
|
||||
|
||||
@@ -24,4 +24,4 @@ Defined in: [src/core.ts:137](../src/core.ts#L137)
|
||||
|
||||
> **fileNonce**: `string`
|
||||
|
||||
Defined in: [src/core.ts:138](../src/core.ts#L138)
|
||||
Defined in: [src/core.ts:138](../core.ts#L138)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: UpMigration
|
||||
|
||||
Defined in: [src/core.ts:215](../src/core.ts#L215)
|
||||
Defined in: [src/core.ts:215](../core.ts#L215)
|
||||
|
||||
## Properties
|
||||
|
||||
@@ -14,7 +14,7 @@ Defined in: [src/core.ts:215](../src/core.ts#L215)
|
||||
|
||||
> **upName**: `string`
|
||||
|
||||
Defined in: [src/core.ts:216](../src/core.ts#L216)
|
||||
Defined in: [src/core.ts:216](../core.ts#L216)
|
||||
|
||||
***
|
||||
|
||||
@@ -22,4 +22,4 @@ Defined in: [src/core.ts:216](../src/core.ts#L216)
|
||||
|
||||
> **withDown**: `boolean`
|
||||
|
||||
Defined in: [src/core.ts:217](../src/core.ts#L217)
|
||||
Defined in: [src/core.ts:217](../core.ts#L217)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MTREDifferent
|
||||
|
||||
Defined in: [src/core.ts:236](../src/core.ts#L236)
|
||||
Defined in: [src/core.ts:236](../core.ts#L236)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:236](../src/core.ts#L236)
|
||||
|
||||
> **downMigrations**: `string`[]
|
||||
|
||||
Defined in: [src/core.ts:238](../src/core.ts#L238)
|
||||
Defined in: [src/core.ts:238](../core.ts#L238)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:238](../src/core.ts#L238)
|
||||
|
||||
> **type**: `"different"`
|
||||
|
||||
Defined in: [src/core.ts:237](../src/core.ts#L237)
|
||||
Defined in: [src/core.ts:237](../core.ts#L237)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MTRENoDown
|
||||
|
||||
Defined in: [src/core.ts:231](../src/core.ts#L231)
|
||||
Defined in: [src/core.ts:231](../core.ts#L231)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:231](../src/core.ts#L231)
|
||||
|
||||
> **dbMigrations**: `string`[]
|
||||
|
||||
Defined in: [src/core.ts:233](../src/core.ts#L233)
|
||||
Defined in: [src/core.ts:233](../core.ts#L233)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:233](../src/core.ts#L233)
|
||||
|
||||
> **type**: `"noDown"`
|
||||
|
||||
Defined in: [src/core.ts:232](../src/core.ts#L232)
|
||||
Defined in: [src/core.ts:232](../core.ts#L232)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **Tag** = `"noDown"` \| `"different"`
|
||||
|
||||
Defined in: [src/core.ts:225](../src/core.ts#L225)
|
||||
Defined in: [src/core.ts:225](../core.ts#L225)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MEDowngrade
|
||||
|
||||
Defined in: [src/core.ts:204](../src/core.ts#L204)
|
||||
Defined in: [src/core.ts:204](../core.ts#L204)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:204](../src/core.ts#L204)
|
||||
|
||||
> **downMigrations**: `string`[]
|
||||
|
||||
Defined in: [src/core.ts:206](../src/core.ts#L206)
|
||||
Defined in: [src/core.ts:206](../core.ts#L206)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:206](../src/core.ts#L206)
|
||||
|
||||
> **type**: `"downgrade"`
|
||||
|
||||
Defined in: [src/core.ts:205](../src/core.ts#L205)
|
||||
Defined in: [src/core.ts:205](../core.ts#L205)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MEUpgrade
|
||||
|
||||
Defined in: [src/core.ts:199](../src/core.ts#L199)
|
||||
Defined in: [src/core.ts:199](../core.ts#L199)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:199](../src/core.ts#L199)
|
||||
|
||||
> **type**: `"upgrade"`
|
||||
|
||||
Defined in: [src/core.ts:200](../src/core.ts#L200)
|
||||
Defined in: [src/core.ts:200](../core.ts#L200)
|
||||
|
||||
#### Overrides
|
||||
|
||||
@@ -30,4 +30,4 @@ Defined in: [src/core.ts:200](../src/core.ts#L200)
|
||||
|
||||
> **upMigrations**: [`UpMigration`](core.Interface.UpMigration.md)[]
|
||||
|
||||
Defined in: [src/core.ts:201](../src/core.ts#L201)
|
||||
Defined in: [src/core.ts:201](../core.ts#L201)
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MigrationError
|
||||
|
||||
Defined in: [src/core.ts:209](../src/core.ts#L209)
|
||||
Defined in: [src/core.ts:209](../core.ts#L209)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:209](../src/core.ts#L209)
|
||||
|
||||
> **mtrError**: [`MTRError`](core.TypeAlias.MTRError.md)
|
||||
|
||||
Defined in: [src/core.ts:211](../src/core.ts#L211)
|
||||
Defined in: [src/core.ts:211](../core.ts#L211)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:211](../src/core.ts#L211)
|
||||
|
||||
> **type**: `"migrationError"`
|
||||
|
||||
Defined in: [src/core.ts:210](../src/core.ts#L210)
|
||||
Defined in: [src/core.ts:210](../core.ts#L210)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **Tag** = `"upgrade"` \| `"downgrade"` \| `"migrationError"`
|
||||
|
||||
Defined in: [src/core.ts:193](../src/core.ts#L193)
|
||||
Defined in: [src/core.ts:193](../core.ts#L193)
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **Backend** = `"sqlite"` \| `"postgres"`
|
||||
|
||||
Defined in: [src/core.ts:5](../src/core.ts#L5)
|
||||
Defined in: [src/core.ts:5](../core.ts#L5)
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **DBMigrationError** = [`InvalidConfirmation`](core.DBMigrationError.Interface.InvalidConfirmation.md) \| [`InvalidQueueSize`](core.DBMigrationError.Interface.InvalidQueueSize.md) \| [`ErrorNotADatabase`](core.DBMigrationError.Interface.ErrorNotADatabase.md) \| [`ErrorMigration`](core.DBMigrationError.Interface.ErrorMigration.md) \| [`ErrorSQL`](core.DBMigrationError.Interface.ErrorSQL.md)
|
||||
|
||||
Defined in: [src/core.ts:147](../src/core.ts#L147)
|
||||
Defined in: [src/core.ts:147](../core.ts#L147)
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **MTRError** = [`MTRENoDown`](core.MTRError.Interface.MTRENoDown.md) \| [`MTREDifferent`](core.MTRError.Interface.MTREDifferent.md)
|
||||
|
||||
Defined in: [src/core.ts:220](../src/core.ts#L220)
|
||||
Defined in: [src/core.ts:220](../core.ts#L220)
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **MigrationError** = [`MEUpgrade`](core.MigrationError.Interface.MEUpgrade.md) \| [`MEDowngrade`](core.MigrationError.Interface.MEDowngrade.md) \| [`MigrationError`](core.MigrationError.Interface.MigrationError.md)
|
||||
|
||||
Defined in: [src/core.ts:187](../src/core.ts#L187)
|
||||
Defined in: [src/core.ts:187](../core.ts#L187)
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **botAddressSettings**(`__namedParameters`): [`BotAddressSettings`](api.Interface.BotAddressSettings.md)
|
||||
|
||||
Defined in: [src/util.ts:48](../src/util.ts#L48)
|
||||
Defined in: [src/util.ts:48](../util.ts#L48)
|
||||
|
||||
## Parameters
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatInfoName**(`cInfo`): `string`
|
||||
|
||||
Defined in: [src/util.ts:18](../src/util.ts#L18)
|
||||
Defined in: [src/util.ts:18](../util.ts#L18)
|
||||
|
||||
## Parameters
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatInfoRef**(`cInfo`): `ChatRef` \| `undefined`
|
||||
|
||||
Defined in: [src/util.ts:4](../src/util.ts#L4)
|
||||
Defined in: [src/util.ts:4](../util.ts#L4)
|
||||
|
||||
## Parameters
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **ciBotCommand**(`chatItem`): [`BotCommand`](util.Interface.BotCommand.md) \| `undefined`
|
||||
|
||||
Defined in: [src/util.ts:78](../src/util.ts#L78)
|
||||
Defined in: [src/util.ts:78](../util.ts#L78)
|
||||
|
||||
## Parameters
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **ciContentText**(`__namedParameters`): `string` \| `undefined`
|
||||
|
||||
Defined in: [src/util.ts:64](../src/util.ts#L64)
|
||||
Defined in: [src/util.ts:64](../util.ts#L64)
|
||||
|
||||
## Parameters
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **contactAddressStr**(`link`): `string`
|
||||
|
||||
Defined in: [src/util.ts:44](../src/util.ts#L44)
|
||||
Defined in: [src/util.ts:44](../util.ts#L44)
|
||||
|
||||
## Parameters
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **fromLocalProfile**(`__namedParameters`): `Profile`
|
||||
|
||||
Defined in: [src/util.ts:56](../src/util.ts#L56)
|
||||
Defined in: [src/util.ts:56](../util.ts#L56)
|
||||
|
||||
## Parameters
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **reactionText**(`reaction`): `string`
|
||||
|
||||
Defined in: [src/util.ts:89](../src/util.ts#L89)
|
||||
Defined in: [src/util.ts:89](../util.ts#L89)
|
||||
|
||||
## Parameters
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **senderName**(`cInfo`, `chatDir`): `string`
|
||||
|
||||
Defined in: [src/util.ts:37](../src/util.ts#L37)
|
||||
Defined in: [src/util.ts:37](../util.ts#L37)
|
||||
|
||||
## Parameters
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: BotCommand
|
||||
|
||||
Defined in: [src/util.ts:72](../src/util.ts#L72)
|
||||
Defined in: [src/util.ts:72](../util.ts#L72)
|
||||
|
||||
## Properties
|
||||
|
||||
@@ -14,7 +14,7 @@ Defined in: [src/util.ts:72](../src/util.ts#L72)
|
||||
|
||||
> **keyword**: `string`
|
||||
|
||||
Defined in: [src/util.ts:73](../src/util.ts#L73)
|
||||
Defined in: [src/util.ts:73](../util.ts#L73)
|
||||
|
||||
***
|
||||
|
||||
@@ -22,4 +22,4 @@ Defined in: [src/util.ts:73](../src/util.ts#L73)
|
||||
|
||||
> **params**: `string`
|
||||
|
||||
Defined in: [src/util.ts:74](../src/util.ts#L74)
|
||||
Defined in: [src/util.ts:74](../util.ts#L74)
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simplex-chat",
|
||||
"version": "7.1.0-beta.6.4",
|
||||
"version": "7.1.0-beta.6.6",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"gypfile": false,
|
||||
@@ -24,16 +24,17 @@
|
||||
"docs": "typedoc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@simplex-chat/types": "^7.1.0-beta.6.1",
|
||||
"extract-zip": "^2.0.1",
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
"@simplex-chat/types": "^7.1.0-beta.6.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"yauzl": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^25.0.5",
|
||||
"jest": "^30.2.0",
|
||||
"@types/yauzl": "^3.4.0",
|
||||
"jest": "^30.5.2",
|
||||
"node-addon-api": "^8.5.0",
|
||||
"ts-jest": "^29.4.6",
|
||||
"ts-jest": "^29.4.15",
|
||||
"typedoc": "^0.28.15",
|
||||
"typedoc-plugin-markdown": "^4.9.0",
|
||||
"typescript": "^5.9.3"
|
||||
|
||||
@@ -273,9 +273,11 @@ export class ChatApi {
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let done = false
|
||||
let timer: NodeJS.Timeout | undefined = undefined
|
||||
const cleanup = () => {
|
||||
done = true
|
||||
this.off(event, subscriber)
|
||||
clearTimeout(timer)
|
||||
this.off(event, subscriber)
|
||||
}
|
||||
const subscriber: EventSubscriberFunc<K> = async (evt: ChatEvent & {type: K}) => {
|
||||
if (done) return
|
||||
@@ -288,7 +290,7 @@ export class ChatApi {
|
||||
}
|
||||
this.on(event, subscriber)
|
||||
if (timeout > 0) {
|
||||
setTimeout(() => { if (!done) { cleanup(); resolve(undefined) } }, timeout)
|
||||
timer = setTimeout(() => { if (!done) { cleanup(); resolve(undefined) } }, timeout)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -476,9 +478,10 @@ export class ChatApi {
|
||||
|
||||
/**
|
||||
* Update message.
|
||||
* A live message stays live with `liveMessage = true` and is finalized with `liveMessage = false`.
|
||||
* Network usage: background.
|
||||
*/
|
||||
async apiUpdateChatItem(chatType: T.ChatType, chatId: number, chatItemId: number, msgContent: T.MsgContent, liveMessage: false): Promise<T.ChatItem> {
|
||||
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},
|
||||
@@ -995,3 +998,88 @@ export class ChatApi {
|
||||
throw new ChatCommandError("error sending member contact invitation", r)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Text shown as a live message while it is written, e.g. a streamed reply.
|
||||
*/
|
||||
export class LiveMessage {
|
||||
private tasks: Promise<void> = Promise.resolve()
|
||||
private text = ""
|
||||
private writing = false
|
||||
private timer: NodeJS.Timeout | undefined = undefined
|
||||
private item: {itemId: number, text: string} | undefined = undefined
|
||||
|
||||
/**
|
||||
* @param chat - Chat API, or an object with its methods to send, update and delete messages.
|
||||
* @param {T.ChatType} chatType - Chat type.
|
||||
* @param {number} chatId - Contact or group ID.
|
||||
* @param {number} [interval=3000] - Minimum interval between updates of the live message, in milliseconds.
|
||||
* @param onError - Error handler for sending, updating and deleting messages. Errors are logged when it is omitted.
|
||||
*/
|
||||
constructor(
|
||||
private readonly chat: Pick<ChatApi, "apiSendMessages" | "apiUpdateChatItem" | "apiDeleteChatItems">,
|
||||
private readonly chatType: T.ChatType,
|
||||
private readonly chatId: number,
|
||||
private readonly interval = 3000,
|
||||
private readonly onError: (error: unknown) => void = e => console.log("live message error", e)
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Set the text of the live message.
|
||||
* The first text is sent at once; later texts update the message at most once per interval.
|
||||
*/
|
||||
update(text: string): void {
|
||||
this.text = text
|
||||
if (!this.writing) {
|
||||
this.writing = true
|
||||
this.queueText(text, true)
|
||||
} else if (this.timer === undefined) {
|
||||
this.timer = setTimeout(() => {
|
||||
this.timer = undefined
|
||||
this.queueText(this.text, true)
|
||||
}, this.interval)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalize the live message with the text, or with the last text when omitted.
|
||||
* A message without text is deleted. The next update starts a new live message.
|
||||
* The returned promise resolves after all queued commands complete.
|
||||
*/
|
||||
end(text = this.text): Promise<void> {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = undefined
|
||||
this.text = ""
|
||||
this.writing = false
|
||||
this.queueText(text, false)
|
||||
return this.tasks
|
||||
}
|
||||
|
||||
private queueText(text: string, live: boolean): void {
|
||||
this.tasks = this.tasks.then(() => this.showText(text, live)).catch(this.onError)
|
||||
}
|
||||
|
||||
private async showText(text: string, live: boolean): Promise<void> {
|
||||
try {
|
||||
await this.showMessage(text, live)
|
||||
} finally {
|
||||
if (!live) this.item = undefined
|
||||
}
|
||||
}
|
||||
|
||||
private async showMessage(text: string, live: boolean): Promise<void> {
|
||||
const item = this.item
|
||||
if (item === undefined) {
|
||||
if (live || text !== "") {
|
||||
const [{chatItem}] = await this.chat.apiSendMessages([this.chatType, this.chatId], [{msgContent: {type: "text", text}, mentions: {}}], live)
|
||||
this.item = live ? {itemId: chatItem.meta.itemId, text} : undefined
|
||||
}
|
||||
} else if (!live && text === "") {
|
||||
await this.chat.apiDeleteChatItems(this.chatType, this.chatId, [item.itemId], T.CIDeleteMode.Broadcast)
|
||||
this.item = undefined
|
||||
} else if (!live || text !== item.text) {
|
||||
await this.chat.apiUpdateChatItem(this.chatType, this.chatId, item.itemId, {type: "text", text}, live)
|
||||
this.item = live ? {itemId: item.itemId, text} : undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import * as https from "https"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import {pipeline} from "stream/promises"
|
||||
import extract = require("extract-zip")
|
||||
import * as yauzl from "yauzl"
|
||||
import type {Backend} from "./core"
|
||||
import {ADDON_RELEASE, LIBS_RELEASE, SHA256} from "./release"
|
||||
|
||||
@@ -19,6 +19,8 @@ const PLATFORMS: {[platform: string]: {name: string, lib: string} | undefined} =
|
||||
}
|
||||
const ARCHS: {[arch: string]: string | undefined} = {x64: "x86_64", arm64: "aarch64"}
|
||||
const SUPPORTED = ["linux-x86_64", "linux-aarch64", "macos-x86_64", "macos-aarch64", "windows-x86_64"]
|
||||
const S_IFMT = 0o170000
|
||||
const S_IFLNK = 0o120000
|
||||
|
||||
function unsupported(platform: string, arch: string): Error {
|
||||
return new Error(`Unsupported platform: ${platform}/${arch}; supported: ${SUPPORTED.join(", ")}`)
|
||||
@@ -115,7 +117,7 @@ export function installLibs(url: string, sha256: string, target: string, lib: st
|
||||
const zipPath = path.join(tmp, "libs.zip")
|
||||
await download(url, zipPath, timeoutMs)
|
||||
await verifySha256(zipPath, sha256, url)
|
||||
await extract(zipPath, {dir: tmp})
|
||||
await unzip(zipPath, tmp)
|
||||
const extracted = path.join(tmp, "libs")
|
||||
if (!fs.existsSync(path.join(extracted, lib))) throw new Error(`libs/${lib} missing from ${url}`)
|
||||
await renameInto(extracted, target, path.join(target, lib))
|
||||
@@ -132,6 +134,44 @@ export function installAddon(url: string, sha256: string, target: string, timeou
|
||||
})
|
||||
}
|
||||
|
||||
function unzip(zipPath: string, dir: string): Promise<void> {
|
||||
const root = path.resolve(dir)
|
||||
return new Promise((resolve, reject) => {
|
||||
yauzl.open(zipPath, {lazyEntries: true}, (err, zip) => {
|
||||
if (err || !zip) return reject(err ?? new Error(`cannot open ${zipPath}`))
|
||||
let failed = false
|
||||
const fail = (e: unknown) => {
|
||||
if (failed) return
|
||||
failed = true
|
||||
zip.close()
|
||||
reject(e)
|
||||
}
|
||||
zip.on("error", e => {
|
||||
failed = true
|
||||
reject(e)
|
||||
})
|
||||
zip.on("end", () => resolve())
|
||||
zip.on("entry", (entry: yauzl.Entry) => {
|
||||
const dest = path.resolve(root, entry.fileName)
|
||||
if (!dest.startsWith(root + path.sep)) return fail(new Error(`zip entry outside the target folder: ${entry.fileName}`))
|
||||
const mode = entry.externalFileAttributes >>> 16
|
||||
if ((mode & S_IFMT) === S_IFLNK) return fail(new Error(`zip entry is a symlink: ${entry.fileName}`))
|
||||
if (entry.fileName.endsWith("/")) {
|
||||
fs.promises.mkdir(dest, {recursive: true}).then(() => zip.readEntry(), fail)
|
||||
return
|
||||
}
|
||||
zip.openReadStream(entry, (e, stream) => {
|
||||
if (e || !stream) return fail(e ?? new Error(`cannot read ${entry.fileName}`))
|
||||
fs.promises.mkdir(path.dirname(dest), {recursive: true})
|
||||
.then(() => pipeline(stream, fs.createWriteStream(dest, {mode: (mode & 0o777) || 0o644})))
|
||||
.then(() => zip.readEntry(), fail)
|
||||
})
|
||||
})
|
||||
zip.readEntry()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function verifySha256(file: string, expected: string, url: string): Promise<void> {
|
||||
const hash = crypto.createHash("sha256")
|
||||
for await (const chunk of fs.createReadStream(file)) hash.update(chunk)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// Written by scripts/ci/packages_release.py from the simplex-chat-libs releases.
|
||||
|
||||
export const LIBS_RELEASE = "core-7.1.0.12"
|
||||
export const LIBS_RELEASE = "core-7.1.0.14"
|
||||
|
||||
export const ADDON_RELEASE: string | undefined = "nodejs-addon-308c0960c707"
|
||||
|
||||
export const SHA256: {readonly [asset: string]: string} = {
|
||||
"simplex-chat-libs-linux-aarch64.zip": "924b8e14eaba5e309fa584c4bfe598421cf64983d63790c1c45247f592f60055",
|
||||
"simplex-chat-libs-linux-x86_64-postgres.zip": "323f908b45dde751d6082f8885d07d81fba8a5ab8b2df7d3b8ab3a87e56299e5",
|
||||
"simplex-chat-libs-linux-x86_64.zip": "995409e963db408bcf896361b669944484f845b6744fefe479aebb0ca2e7c5cd",
|
||||
"simplex-chat-libs-macos-aarch64.zip": "64464aa48b996e5a96b4aa651bc57bc69e412d3c3804b589c67bd9dc52faddbf",
|
||||
"simplex-chat-libs-macos-x86_64.zip": "94664f96ff7a2cd5a419096e7e843928faa31cb3addb8869fa1e4b5a0d0b3629",
|
||||
"simplex-chat-libs-windows-x86_64.zip": "2e10844e5c119921139c373ee2971341aceef7dc4198429d3be6b1570a009d15",
|
||||
"simplex-chat-libs-linux-aarch64.zip": "375a6021f0c2e5d79b2cd192e17c6c22c8eae73f46e3758fba24ee5c26a671aa",
|
||||
"simplex-chat-libs-linux-x86_64-postgres.zip": "c3e8a54d37801e685c6361c4e7e53e1f5163d3aa9195dcfe686b6cf90e668ef3",
|
||||
"simplex-chat-libs-linux-x86_64.zip": "cc41ab9049d61be3b386d799fa9394f2870e8ae3b2b9b8745dd3f53ba99f211e",
|
||||
"simplex-chat-libs-macos-aarch64.zip": "ac3a46004f25976f71b3ead8a20b9a01c170fe12f125a0a6799ae97ea990f234",
|
||||
"simplex-chat-libs-macos-x86_64.zip": "0d85ffcccc3a6dfe3e0da18d0b7be9ef36ad27381f047ef66cfde67ee4a7e882",
|
||||
"simplex-chat-libs-windows-x86_64.zip": "f1d1c7efc333a5ec5328a5843c9d3c9ef038fb2886c0b60ac12e87a5c1e85bbd",
|
||||
"simplex-chat-nodejs-linux-aarch64.node": "3636a82c56c48229e0326fa03d6ceb5c16dd3afbcc1e86337783dc53a7328d67",
|
||||
"simplex-chat-nodejs-linux-x86_64.node": "55f3479b03fdb57591179ed730da744f1aa362e7529df55221b60028415746e9",
|
||||
"simplex-chat-nodejs-macos-aarch64.node": "a3472872fb0816f435f52ee5471088e6545ebffb95e89fbf8e1ab82275f2d56e",
|
||||
|
||||
@@ -2,17 +2,25 @@ import * as path from "path"
|
||||
import * as fs from "fs"
|
||||
import {CEvt, T} from "@simplex-chat/types"
|
||||
import {api} from ".."
|
||||
import {SmpServer, describeIfSmpServerAvailable, startSmpServer} from "./smpServer"
|
||||
|
||||
const CT = T.ChatType
|
||||
|
||||
describe("API tests (use preset servers)", () => {
|
||||
describeIfSmpServerAvailable("API tests (use local SMP server)", () => {
|
||||
const tmpDir = "./tests/tmp"
|
||||
const alicePath = path.join(tmpDir, "alice")
|
||||
const bobPath = path.join(tmpDir, "bob")
|
||||
let smpServer: SmpServer
|
||||
|
||||
beforeAll(async () => { smpServer = await startSmpServer() }, 120000)
|
||||
afterAll(() => smpServer?.stop())
|
||||
beforeEach(() => fs.mkdirSync(tmpDir, {recursive: true}))
|
||||
afterEach(() => fs.rmSync(tmpDir, {recursive: true, force: true}))
|
||||
|
||||
async function useSmpServer(chat: api.ChatApi): Promise<void> {
|
||||
expect(await chat.sendChatCmd(`/smp ${smpServer.address}`)).toMatchObject({type: "cmdOk"})
|
||||
}
|
||||
|
||||
it("should send/receive message", async () => {
|
||||
// create users and start chat controllers
|
||||
const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath})
|
||||
@@ -27,32 +35,37 @@ describe("API tests (use preset servers)", () => {
|
||||
await bob.apiCreateActiveUser({displayName: "bob", fullName: ""})
|
||||
await alice.startChat()
|
||||
await bob.startChat()
|
||||
await useSmpServer(alice)
|
||||
await useSmpServer(bob)
|
||||
// connect via link
|
||||
const link = await alice.apiCreateLink(aliceUser.userId)
|
||||
const connected = Promise.all([alice.wait("contactConnected"), bob.wait("contactConnected")])
|
||||
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
|
||||
])
|
||||
const [{contact: bobContact}, {contact: aliceContact}] = await connected
|
||||
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)
|
||||
const hello = bob.wait("newChatItems", isMessage(aliceContact, "hello"))
|
||||
await alice.apiSendTextMessage([CT.Direct, bobContact.contactId], "hello")
|
||||
await bob.wait("newChatItems", isMessage(aliceContact, "hello"))
|
||||
await hello
|
||||
const helloToo = alice.wait("newChatItems", isMessage(bobContact, "hello too"), 10000)
|
||||
await bob.apiSendTextMessage([CT.Direct, aliceContact.contactId], "hello too")
|
||||
await alice.wait("newChatItems", isMessage(bobContact, "hello too"), 10000)
|
||||
await expect(helloToo).resolves.toBeDefined()
|
||||
const howAreYou = bob.wait("newChatItems", isMessage(aliceContact, "how are you?"))
|
||||
await alice.apiSendTextMessage([CT.Direct, bobContact.contactId], "how are you?")
|
||||
await bob.wait("newChatItems", isMessage(aliceContact, "how are you?"))
|
||||
await howAreYou
|
||||
const okAndYou = alice.wait("newChatItems", isMessage(bobContact, "ok, and you?"), 10000)
|
||||
await bob.apiSendTextMessage([CT.Direct, aliceContact.contactId], "ok, and you?")
|
||||
await alice.wait("newChatItems", isMessage(bobContact, "ok, and you?"), 10000)
|
||||
await expect(okAndYou).resolves.toBeDefined()
|
||||
// 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
|
||||
const deleted = bob.wait("contactDeletedByContact")
|
||||
await alice.apiDeleteChat(CT.Direct, bobContact.contactId)
|
||||
await bob.wait("contactDeletedByContact")
|
||||
await deleted
|
||||
await bob.apiDeleteChat(CT.Direct, aliceContact.contactId)
|
||||
await alice.stopChat()
|
||||
await bob.stopChat()
|
||||
@@ -60,8 +73,7 @@ describe("API tests (use preset servers)", () => {
|
||||
await bob.close()
|
||||
await expect(alice.startChat()).rejects.toThrow("chat api controller not initialized")
|
||||
await expect(bob.startChat()).rejects.toThrow("chat api controller not initialized")
|
||||
expect(servers.length).toBe(2)
|
||||
expect(servers[0] !== servers[1]).toBe(true)
|
||||
expect([...new Set(servers)]).toEqual(["localhost"])
|
||||
expect(eventCount > 0).toBe(true)
|
||||
}, 30000)
|
||||
|
||||
@@ -77,20 +89,19 @@ describe("API tests (use preset servers)", () => {
|
||||
await alice.startChat()
|
||||
await bob.startChat()
|
||||
await carol.startChat()
|
||||
await useSmpServer(alice)
|
||||
await useSmpServer(bob)
|
||||
await useSmpServer(carol)
|
||||
// connect alice <-> bob
|
||||
const aliceLink1 = await alice.apiCreateLink(aliceUser.userId)
|
||||
const bobConnected = Promise.all([alice.wait("contactConnected"), bob.wait("contactConnected")])
|
||||
await expect(bob.apiConnectActiveUser(aliceLink1)).resolves.toBe(api.ConnReqType.Invitation)
|
||||
const [bobContact] = await Promise.all([
|
||||
(await alice.wait("contactConnected")).contact,
|
||||
(await bob.wait("contactConnected")).contact
|
||||
])
|
||||
const [{contact: bobContact}] = await bobConnected
|
||||
// connect alice <-> carol
|
||||
const aliceLink2 = await alice.apiCreateLink(aliceUser.userId)
|
||||
const carolConnected = Promise.all([alice.wait("contactConnected"), carol.wait("contactConnected")])
|
||||
await expect(carol.apiConnectActiveUser(aliceLink2)).resolves.toBe(api.ConnReqType.Invitation)
|
||||
const [carolContact] = await Promise.all([
|
||||
(await alice.wait("contactConnected")).contact,
|
||||
(await carol.wait("contactConnected")).contact
|
||||
])
|
||||
const [{contact: carolContact}] = await carolConnected
|
||||
// create group with direct messages enabled
|
||||
const group = await alice.apiNewGroup(aliceUser.userId, {
|
||||
displayName: "test-group",
|
||||
@@ -105,10 +116,10 @@ describe("API tests (use preset servers)", () => {
|
||||
await alice.apiAddMember(groupId, bobContact.contactId, T.GroupMemberRole.Member)
|
||||
const bobInvEvt = await bobInvP
|
||||
expect(bobInvEvt).toBeDefined()
|
||||
const aliceBobConnP = alice.wait("connectedToGroupMember", 15000)
|
||||
const bobAliceConnP = bob.wait("connectedToGroupMember", 15000)
|
||||
const aliceBobConnP = alice.wait("joinedGroupMember", 15000)
|
||||
const bobAliceConnP = bob.wait("userJoinedGroup", 15000)
|
||||
await bob.apiJoinGroup(bobInvEvt!.groupInfo.groupId)
|
||||
await Promise.all([aliceBobConnP, bobAliceConnP])
|
||||
expect(await Promise.all([aliceBobConnP, bobAliceConnP])).not.toContain(undefined)
|
||||
// add carol to the group
|
||||
const carolInvP = carol.wait("receivedGroupInvitation", 30000)
|
||||
await alice.apiAddMember(groupId, carolContact.contactId, T.GroupMemberRole.Member)
|
||||
@@ -117,14 +128,13 @@ describe("API tests (use preset servers)", () => {
|
||||
// wait for carol to connect to both alice and bob (and vice versa)
|
||||
const bobCarolConnP = bob.wait("connectedToGroupMember",
|
||||
(evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "carol", 30000)
|
||||
const carolAliceConnP = carol.wait("connectedToGroupMember",
|
||||
(evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "alice", 30000)
|
||||
const carolAliceConnP = carol.wait("userJoinedGroup", 30000)
|
||||
const carolBobConnP = carol.wait("connectedToGroupMember",
|
||||
(evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "bob", 30000)
|
||||
const aliceCarolConnP = alice.wait("connectedToGroupMember",
|
||||
(evt: CEvt.ConnectedToGroupMember) => evt.member.memberProfile.displayName === "carol", 30000)
|
||||
const aliceCarolConnP = alice.wait("joinedGroupMember",
|
||||
(evt: CEvt.JoinedGroupMember) => evt.member.memberProfile.displayName === "carol", 30000)
|
||||
await carol.apiJoinGroup(carolInvEvt!.groupInfo.groupId)
|
||||
await Promise.all([bobCarolConnP, carolAliceConnP, carolBobConnP, aliceCarolConnP])
|
||||
expect(await Promise.all([bobCarolConnP, carolAliceConnP, carolBobConnP, aliceCarolConnP])).not.toContain(undefined)
|
||||
// find carol's memberId from bob's perspective
|
||||
const members = await bob.apiListMembers(groupId)
|
||||
const carolMember = members.find(m => m.memberProfile.displayName === "carol")
|
||||
|
||||
@@ -26,6 +26,17 @@ describe("documented success responses", () => {
|
||||
await expect(chat.apiUpdateChatItem(T.ChatType.Direct, 1, 2, {type: "text", text: "same"}, false)).resolves.toEqual(chatItem)
|
||||
})
|
||||
|
||||
it("apiUpdateChatItem updates and finalizes a live message", async () => {
|
||||
const chatItem = {meta: {itemId: 2}}
|
||||
const chat = await chatWithResponse({type: "chatItemUpdated", user, chatItem: {chatItem}})
|
||||
await expect(chat.apiUpdateChatItem(T.ChatType.Group, 1, 2, {type: "text", text: "partial"}, true)).resolves.toEqual(chatItem)
|
||||
await expect(chat.apiUpdateChatItem(T.ChatType.Group, 1, 2, {type: "text", text: "final"})).resolves.toEqual(chatItem)
|
||||
expect(jest.mocked(core.chatSendCmd).mock.calls.map(([, cmd]) => cmd)).toEqual([
|
||||
'/_update item #1 2 live=on json {"msgContent":{"type":"text","text":"partial"},"mentions":{}}',
|
||||
'/_update item #1 2 json {"msgContent":{"type":"text","text":"final"},"mentions":{}}',
|
||||
])
|
||||
})
|
||||
|
||||
it("apiSetProfileAddress accepts userProfileNoChange", async () => {
|
||||
const chat = await chatWithResponse({type: "userProfileNoChange", user})
|
||||
await expect(chat.apiSetProfileAddress(1, true)).resolves.toEqual({updateSuccesses: 0, updateFailures: 0, changedContacts: []})
|
||||
@@ -129,6 +140,122 @@ describe("startChat lifecycle", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("LiveMessage", () => {
|
||||
const interval = 50
|
||||
const json = JSON.stringify
|
||||
|
||||
function recordingChat() {
|
||||
const calls: string[] = []
|
||||
let itemId = 0
|
||||
const ref = (chatType: T.ChatType, chatId: number) => T.ChatType.cmdString(chatType) + chatId
|
||||
const chat = {
|
||||
apiSendMessages: async ([chatType, chatId]: [T.ChatType, number], [{msgContent}]: T.ComposedMessage[], live: boolean) => {
|
||||
calls.push(`send ${ref(chatType, chatId)} ${json(msgContent.text)} live=${live}`)
|
||||
return [{chatItem: {meta: {itemId: ++itemId}}}] as unknown as T.AChatItem[]
|
||||
},
|
||||
apiUpdateChatItem: async (chatType: T.ChatType, chatId: number, chatItemId: number, msgContent: T.MsgContent, live: boolean) => {
|
||||
calls.push(`update ${ref(chatType, chatId)} ${chatItemId} ${json(msgContent.text)} live=${live}`)
|
||||
return {} as T.ChatItem
|
||||
},
|
||||
apiDeleteChatItems: async (chatType: T.ChatType, chatId: number, chatItemIds: number[], deleteMode: T.CIDeleteMode) => {
|
||||
calls.push(`delete ${ref(chatType, chatId)} ${chatItemIds} ${deleteMode}`)
|
||||
return [] as T.ChatItemDeletion[]
|
||||
},
|
||||
}
|
||||
return {chat, calls}
|
||||
}
|
||||
|
||||
function recordingLiveMessage(): {live: api.LiveMessage, calls: string[]} {
|
||||
const {chat, calls} = recordingChat()
|
||||
return {live: new api.LiveMessage(chat, T.ChatType.Group, 5, interval), calls}
|
||||
}
|
||||
|
||||
function wait(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
it("sends the first text at once, updates at most once per interval and finalizes", async () => {
|
||||
const {live, calls} = recordingLiveMessage()
|
||||
live.update("")
|
||||
live.update("Hel")
|
||||
live.update("Hello")
|
||||
await wait(interval / 5)
|
||||
expect(calls).toEqual(['send #5 "" live=true'])
|
||||
await wait(interval * 2)
|
||||
expect(calls).toEqual(['send #5 "" live=true', 'update #5 1 "Hello" live=true'])
|
||||
await live.end("Hello world")
|
||||
expect(calls).toEqual(['send #5 "" live=true', 'update #5 1 "Hello" live=true', 'update #5 1 "Hello world" live=false'])
|
||||
})
|
||||
|
||||
it("does not repeat unchanged text", async () => {
|
||||
const {live, calls} = recordingLiveMessage()
|
||||
live.update("Hello")
|
||||
live.update("Hello")
|
||||
await wait(interval * 2)
|
||||
await live.end()
|
||||
expect(calls).toEqual(['send #5 "Hello" live=true', 'update #5 1 "Hello" live=false'])
|
||||
})
|
||||
|
||||
it("deletes a live message without text", async () => {
|
||||
const {live, calls} = recordingLiveMessage()
|
||||
live.update("")
|
||||
await live.end()
|
||||
expect(calls).toEqual(['send #5 "" live=true', "delete #5 1 broadcast"])
|
||||
})
|
||||
|
||||
it("sends the final text without a live message", async () => {
|
||||
const {live, calls} = recordingLiveMessage()
|
||||
await live.end()
|
||||
await live.end("Done")
|
||||
expect(calls).toEqual(['send #5 "Done" live=false'])
|
||||
})
|
||||
|
||||
it("finalizes the last text", async () => {
|
||||
const {live, calls} = recordingLiveMessage()
|
||||
live.update("")
|
||||
live.update("partial")
|
||||
await live.end()
|
||||
expect(calls).toEqual(['send #5 "" live=true', 'update #5 1 "partial" live=false'])
|
||||
})
|
||||
|
||||
it("starts the next live message after the end", async () => {
|
||||
const {live, calls} = recordingLiveMessage()
|
||||
live.update("")
|
||||
void live.end("first")
|
||||
live.update("")
|
||||
await live.end("second")
|
||||
expect(calls).toEqual([
|
||||
'send #5 "" live=true',
|
||||
'update #5 1 "first" live=false',
|
||||
'send #5 "" live=true',
|
||||
'update #5 2 "second" live=false',
|
||||
])
|
||||
})
|
||||
|
||||
it("passes errors to the handler and repeats the change with the next text", async () => {
|
||||
const {chat, calls} = recordingChat()
|
||||
const error = new Error("update failed")
|
||||
chat.apiUpdateChatItem = jest.fn(chat.apiUpdateChatItem).mockRejectedValueOnce(error)
|
||||
const errors: unknown[] = []
|
||||
const live = new api.LiveMessage(chat, T.ChatType.Group, 5, interval, e => errors.push(e))
|
||||
live.update("")
|
||||
live.update("Hello")
|
||||
await wait(interval * 2)
|
||||
await live.end("Hello world")
|
||||
expect(errors).toEqual([error])
|
||||
expect(calls).toEqual(['send #5 "" live=true', 'update #5 1 "Hello world" live=false'])
|
||||
})
|
||||
|
||||
it("logs errors by default", async () => {
|
||||
const {chat} = recordingChat()
|
||||
const error = new Error("send failed")
|
||||
chat.apiSendMessages = jest.fn(chat.apiSendMessages).mockRejectedValueOnce(error)
|
||||
const log = jest.spyOn(console, "log").mockImplementation(() => {})
|
||||
await new api.LiveMessage(chat, T.ChatType.Group, 5).end("Hello")
|
||||
expect(log).toHaveBeenCalledWith("live message error", error)
|
||||
})
|
||||
})
|
||||
|
||||
describe("ChatApi.init", () => {
|
||||
it("loads the library for the configured backend before opening the database", async () => {
|
||||
let loaded!: () => void
|
||||
|
||||
@@ -3,18 +3,35 @@ import * as fs from "fs"
|
||||
import * as assert from "assert"
|
||||
import {CEvt, T} from "@simplex-chat/types"
|
||||
import {api, bot, util} from ".."
|
||||
import {SmpServer, describeIfSmpServerAvailable, startSmpServer} from "./smpServer"
|
||||
|
||||
const CT = T.ChatType
|
||||
|
||||
describe("Bot tests (use preset servers)", () => {
|
||||
describeIfSmpServerAvailable("Bot tests (use local SMP server)", () => {
|
||||
const tmpDir = "./tests/tmp"
|
||||
const botPath = path.join(tmpDir, "bot")
|
||||
const alicePath = path.join(tmpDir, "alice")
|
||||
let smpServer: SmpServer
|
||||
|
||||
beforeAll(async () => { smpServer = await startSmpServer() }, 120000)
|
||||
afterAll(() => smpServer?.stop())
|
||||
beforeEach(() => fs.mkdirSync(tmpDir, {recursive: true}))
|
||||
afterEach(() => fs.rmSync(tmpDir, {recursive: true, force: true}))
|
||||
|
||||
async function useSmpServer(chat: api.ChatApi): Promise<void> {
|
||||
expect(await chat.sendChatCmd(`/smp ${smpServer.address}`)).toMatchObject({type: "cmdOk"})
|
||||
}
|
||||
|
||||
async function prepareBotDatabase(): Promise<void> {
|
||||
const chat = await api.ChatApi.init({type: "sqlite", filePrefix: botPath})
|
||||
await chat.apiCreateActiveUser({displayName: "Squaring bot", fullName: ""})
|
||||
await chat.startChat()
|
||||
await useSmpServer(chat)
|
||||
await chat.close()
|
||||
}
|
||||
|
||||
it("should reply to messages", async () => {
|
||||
await prepareBotDatabase()
|
||||
// run bot
|
||||
const [chat, botUser, botAddress] = await bot.run({
|
||||
profile: {displayName: "Squaring bot", fullName: ""},
|
||||
@@ -33,24 +50,25 @@ describe("Bot tests (use preset servers)", () => {
|
||||
const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath})
|
||||
const aliceUser = await alice.apiCreateActiveUser({displayName: "alice", fullName: ""})
|
||||
await alice.startChat()
|
||||
await useSmpServer(alice)
|
||||
// connect to bot
|
||||
const [plan, link] = await alice.apiConnectPlan(aliceUser.userId, util.contactAddressStr(botAddress.connLinkContact))
|
||||
assert(plan.type === "contactAddress")
|
||||
const connected = Promise.all([alice.wait("contactConnected"), chat.wait("contactConnected")])
|
||||
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
|
||||
])
|
||||
const [{contact: botContact}, {contact: aliceContact}] = await connected
|
||||
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)
|
||||
const reply = alice.wait("newChatItems", isMessage(botContact, "2 * 2 = 4"), 5000)
|
||||
await alice.apiSendTextMessage([CT.Direct, botContact.contactId], "2")
|
||||
console.log("after sending message")
|
||||
await alice.wait("newChatItems", isMessage(botContact, "2 * 2 = 4"), 5000)
|
||||
await expect(reply).resolves.toBeDefined()
|
||||
// cleanup
|
||||
const deleted = chat.wait("contactDeletedByContact", ({contact}) => contact.contactId === aliceContact.contactId)
|
||||
await alice.apiDeleteChat(CT.Direct, botContact.contactId)
|
||||
await chat.wait("contactDeletedByContact", ({contact}) => contact.contactId === aliceContact.contactId)
|
||||
await deleted
|
||||
await chat.apiDeleteUserAddress(botUser.userId)
|
||||
await chat.stopChat()
|
||||
await chat.close()
|
||||
|
||||
@@ -17,8 +17,18 @@ describe("Core tests", () => {
|
||||
}, 300000);
|
||||
const tmpDir = "./tests/tmp";
|
||||
const dbPath = path.join(tmpDir, "simplex_v1");
|
||||
const templateDir = "./tests/template";
|
||||
const templateDbPath = path.join(templateDir, "simplex_v1");
|
||||
|
||||
beforeEach(() => fs.mkdirSync(tmpDir, {recursive: true}));
|
||||
beforeAll(async () => {
|
||||
fs.mkdirSync(templateDir, {recursive: true});
|
||||
await stopAndClose(await core.chatMigrateInit(templateDbPath, "key", core.MigrationConfirmation.YesUp));
|
||||
}, 30000);
|
||||
afterAll(() => fs.rmSync(templateDir, {recursive: true, force: true}));
|
||||
beforeEach(() => {
|
||||
fs.mkdirSync(tmpDir, {recursive: true});
|
||||
copyTemplateDb(dbPath);
|
||||
});
|
||||
afterEach(() => fs.rmSync(tmpDir, {recursive: true, force: true}));
|
||||
|
||||
async function stopAndClose(ctrl: bigint): Promise<void> {
|
||||
@@ -26,6 +36,10 @@ describe("Core tests", () => {
|
||||
await core.chatCloseStore(ctrl);
|
||||
}
|
||||
|
||||
function copyTemplateDb(dbFilePrefix: string): void {
|
||||
for (const suffix of ["_chat.db", "_agent.db"]) fs.copyFileSync(templateDbPath + suffix, dbFilePrefix + suffix);
|
||||
}
|
||||
|
||||
it("should initialize chat controller", async () => {
|
||||
const ctrl = await core.chatMigrateInit(dbPath, "key", core.MigrationConfirmation.YesUp);
|
||||
expect(typeof ctrl).toBe("bigint");
|
||||
@@ -132,7 +146,7 @@ describe("Core tests", () => {
|
||||
|
||||
it("should not block the libuv pool while receiving", async () => {
|
||||
const ctrl = await core.chatMigrateInit(dbPath, "key", core.MigrationConfirmation.YesUp);
|
||||
const receives = [1, 2, 3, 4].map(() => core.chatRecvMsgWait(ctrl, 2_000_000));
|
||||
const receives = [1, 2, 3, 4].map(() => core.chatRecvMsgWait(ctrl, 500_000));
|
||||
const start = Date.now();
|
||||
await fs.promises.stat(tmpDir);
|
||||
expect(Date.now() - start).toBeLessThan(200);
|
||||
@@ -159,8 +173,10 @@ describe("Core tests", () => {
|
||||
}, 30000);
|
||||
|
||||
it("should receive on two controllers concurrently", async () => {
|
||||
const ctrlA = await core.chatMigrateInit(path.join(tmpDir, "simplex_a"), "key", core.MigrationConfirmation.YesUp);
|
||||
const ctrlB = await core.chatMigrateInit(path.join(tmpDir, "simplex_b"), "key", core.MigrationConfirmation.YesUp);
|
||||
const dbPathB = path.join(tmpDir, "simplex_b");
|
||||
copyTemplateDb(dbPathB);
|
||||
const ctrlA = await core.chatMigrateInit(dbPath, "key", core.MigrationConfirmation.YesUp);
|
||||
const ctrlB = await core.chatMigrateInit(dbPathB, "key", core.MigrationConfirmation.YesUp);
|
||||
const start = Date.now();
|
||||
await expect(Promise.all([core.chatRecvMsgWait(ctrlA, 1_000_000), core.chatRecvMsgWait(ctrlB, 1_000_000)]))
|
||||
.resolves.toEqual([undefined, undefined]);
|
||||
@@ -235,7 +251,7 @@ describe("Core tests", () => {
|
||||
}, 35000);
|
||||
|
||||
it("should let the process exit while a receiver is idle", () => {
|
||||
const childDbPath = path.resolve(tmpDir, "simplex_child");
|
||||
const childDbPath = path.resolve(dbPath);
|
||||
const script = `
|
||||
const simplex = require("./build/Release/simplex.node");
|
||||
simplex.load(${JSON.stringify(libPath)});
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBtjCCATagAwIBAgIUe2PryrWo0xXX9vcA3WfbCzcdmgAwBQYDK2VxMCoxFjAU
|
||||
BgNVBAMMDVNNUCBzZXJ2ZXIgQ0ExEDAOBgNVBAoMB1NpbXBsZVgwIBcNMjIwMTEx
|
||||
MTExNjM5WhgPNDc1OTEyMDgxMTE2MzlaMCoxFjAUBgNVBAMMDVNNUCBzZXJ2ZXIg
|
||||
Q0ExEDAOBgNVBAoMB1NpbXBsZVgwQzAFBgMrZXEDOgCAcvFwVicR+RLZpiEWPFNR
|
||||
XYTbf+mFcX1NHIyPQDugFwOCgqJAW1fsjYgFhtQJSMH/lc1N7clfm4CjUzBRMB0G
|
||||
A1UdDgQWBBQcUJvR7mm26yxMQfCsWgbnwMmJVDAfBgNVHSMEGDAWgBQcUJvR7mm2
|
||||
6yxMQfCsWgbnwMmJVDAPBgNVHRMBAf8EBTADAQH/MAUGAytlcQNzAAAP/hMPNxyW
|
||||
fyJi+iJViodU+C/aklnvHtjh5P3AbiVCSUfY6+PEdvkC8Ov0pBAYpYi5ukSNNVXl
|
||||
ABVRlipB+vOcLQStNyaZ7kXzQ2IO/0btmIidh+G6SP8I4aytYIYYcV5pEUZpG1L1
|
||||
57g8P29SDv81AA==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,12 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBvDCCATygAwIBAgIUUgqht3ibEJ9jp5JuroML5Baxa/IwBQYDK2VxMCoxFjAU
|
||||
BgNVBAMMDVNNUCBzZXJ2ZXIgQ0ExEDAOBgNVBAoMB1NpbXBsZVgwIBcNMjIwMTEx
|
||||
MTYzOTQ2WhgPNDc1OTEyMDgxNjM5NDZaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDBD
|
||||
MAUGAytlcQM6AFj+fmjmgOMBFgQ7lXfICSlSaB5sYp1wzhM2IaqgmVhWbDs3Tw7W
|
||||
oUXFJsWpKku1kFjLH2yhqIInAKNvMG0wCQYDVR0TBAIwADALBgNVHQ8EBAMCA8gw
|
||||
EwYDVR0lBAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFMjbHbxESDI8YDPjJ8kXWjkg
|
||||
jHxmMB8GA1UdIwQYMBaAFBxQm9HuabbrLExB8KxaBufAyYlUMAUGAytlcQNzAP98
|
||||
RpFacmjxsc07GwC07uAxUxNgxX4R2nA+0St866uFbdyxApiyn44giG+m+YXCXLjh
|
||||
1ffKEhhqAPdlpzByLarx8EWgmOsisBCRWrmraksOV15FKxjPqm7OYKp4f9GvieAQ
|
||||
o/SKOyZVVdcZX7NcuDUxAA==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,4 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MEcCAQAwBQYDK2VxBDsEOZcXSjKQ3FQ9EBfqHmFIidOu4LecpUgi5NVPMhx7mS2d
|
||||
QNQ0lFasp+EfvLErbs1Sd9xLVxA1hnZJng==
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -249,6 +249,35 @@ describe("installLibs", () => {
|
||||
expect(leftovers()).toEqual([])
|
||||
})
|
||||
|
||||
it("rejects a symlink entry and cleans up", async () => {
|
||||
const zip = storedZip({[`libs/${LIB}`]: "lib", "libs/link": {content: "../../outside", mode: 0o120777}})
|
||||
routes["/symlink.zip"] = res => res.writeHead(200).end(zip)
|
||||
const target = path.join(tmp, "sqlite")
|
||||
await expect(installLibs(`${base}/symlink.zip`, sha256(zip), target, LIB)).rejects.toThrow("zip entry is a symlink: libs/link")
|
||||
expect(leftovers()).toEqual([])
|
||||
expect(fs.existsSync(target)).toBe(false)
|
||||
})
|
||||
|
||||
it("rejects an entry outside the target folder and cleans up", async () => {
|
||||
const zip = storedZip({[`libs/${LIB}`]: "lib", "libs/../../outside": "x"})
|
||||
routes["/outside.zip"] = res => res.writeHead(200).end(zip)
|
||||
await expect(installLibs(`${base}/outside.zip`, sha256(zip), path.join(tmp, "sqlite"), LIB)).rejects.toThrow("libs/../../outside")
|
||||
expect(leftovers()).toEqual([])
|
||||
expect(fs.existsSync(path.join(tmp, "outside"))).toBe(false)
|
||||
expect(fs.existsSync(path.join(path.dirname(tmp), "outside"))).toBe(false)
|
||||
})
|
||||
|
||||
const itOnPosix = process.platform === "win32" ? it.skip : it
|
||||
|
||||
itOnPosix("keeps the file mode of an entry", async () => {
|
||||
const zip = storedZip({[`libs/${LIB}`]: {content: "lib", mode: 0o100755}, "libs/data": {content: "x", mode: 0o100644}})
|
||||
routes["/modes.zip"] = res => res.writeHead(200).end(zip)
|
||||
const target = path.join(tmp, "sqlite")
|
||||
await installLibs(`${base}/modes.zip`, sha256(zip), target, LIB)
|
||||
expect(fs.statSync(path.join(target, LIB)).mode & 0o777).toBe(0o755)
|
||||
expect(fs.statSync(path.join(target, "data")).mode & 0o777).toBe(0o644)
|
||||
})
|
||||
|
||||
it("times out a request without a response", async () => {
|
||||
await expect(installLibs(`${base}/hang.zip`, GOOD_SHA, path.join(tmp, "sqlite"), LIB, 200)).rejects.toThrow("timeout")
|
||||
expect(leftovers()).toEqual([])
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import * as path from "path"
|
||||
import * as fs from "fs"
|
||||
import {T} from "@simplex-chat/types"
|
||||
import {api} from ".."
|
||||
import {SmpServer, describeIfSmpServerAvailable, startSmpServer} from "./smpServer"
|
||||
|
||||
const CT = T.ChatType
|
||||
|
||||
describeIfSmpServerAvailable("Live message tests (use local SMP server)", () => {
|
||||
const tmpDir = "./tests/tmp"
|
||||
const alicePath = path.join(tmpDir, "alice")
|
||||
const bobPath = path.join(tmpDir, "bob")
|
||||
let smpServer: SmpServer
|
||||
|
||||
beforeAll(async () => { smpServer = await startSmpServer() }, 120000)
|
||||
afterAll(() => smpServer?.stop())
|
||||
beforeEach(() => fs.mkdirSync(tmpDir, {recursive: true}))
|
||||
afterEach(() => fs.rmSync(tmpDir, {recursive: true, force: true}))
|
||||
|
||||
async function useSmpServer(chat: api.ChatApi): Promise<void> {
|
||||
expect(await chat.sendChatCmd(`/smp ${smpServer.address}`)).toMatchObject({type: "cmdOk"})
|
||||
}
|
||||
|
||||
it("should show, update, finalize and delete live messages", async () => {
|
||||
const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath})
|
||||
const bob = await api.ChatApi.init({type: "sqlite", filePrefix: bobPath})
|
||||
const aliceUser = await alice.apiCreateActiveUser({displayName: "alice", fullName: ""})
|
||||
await bob.apiCreateActiveUser({displayName: "bob", fullName: ""})
|
||||
await alice.startChat()
|
||||
await bob.startChat()
|
||||
await useSmpServer(alice)
|
||||
await useSmpServer(bob)
|
||||
const aliceConnected = alice.wait("contactConnected", 30000)
|
||||
const bobConnected = bob.wait("contactConnected", 30000)
|
||||
await bob.apiConnectActiveUser(await alice.apiCreateLink(aliceUser.userId))
|
||||
const bobContact = (await aliceConnected)!.contact
|
||||
expect(await bobConnected).toBeDefined()
|
||||
|
||||
const timeout = 10000
|
||||
const received = (check: (meta: T.CIMeta) => boolean) =>
|
||||
bob.wait("newChatItems", ({chatItems}) => chatItems.some(({chatItem: {meta}}) => check(meta)), timeout)
|
||||
const updated = (check: (meta: T.CIMeta) => boolean) =>
|
||||
bob.wait("chatItemUpdated", ({chatItem: {chatItem: {meta}}}) => check(meta), timeout)
|
||||
const live = new api.LiveMessage(alice, CT.Direct, bobContact.contactId, 500)
|
||||
|
||||
const started = received(meta => meta.itemLive === true && meta.itemText === "")
|
||||
live.update("")
|
||||
const itemId = (await started)!.chatItems[0].chatItem.meta.itemId
|
||||
const updatedLive = updated(meta => meta.itemId === itemId && meta.itemLive === true && meta.itemText === "Hello")
|
||||
live.update("Hel")
|
||||
live.update("Hello")
|
||||
expect(await updatedLive).toBeDefined()
|
||||
const finalized = updated(meta => meta.itemId === itemId && meta.itemLive === false && meta.itemText === "Hello world")
|
||||
await live.end("Hello world")
|
||||
expect((await finalized)?.chatItem.chatItem.meta.itemEdited).toBe(false)
|
||||
|
||||
const emptyStarted = received(meta => meta.itemLive === true && meta.itemText === "")
|
||||
live.update("")
|
||||
const emptyItemId = (await emptyStarted)!.chatItems[0].chatItem.meta.itemId
|
||||
const deleted = bob.wait("chatItemsDeleted", ({chatItemDeletions}) =>
|
||||
chatItemDeletions.some(({deletedChatItem}) => deletedChatItem.chatItem.meta.itemId === emptyItemId), timeout)
|
||||
await live.end()
|
||||
expect(await deleted).toBeDefined()
|
||||
|
||||
await alice.close()
|
||||
await bob.close()
|
||||
}, 90000)
|
||||
})
|
||||
@@ -0,0 +1,118 @@
|
||||
import {ChildProcess, spawn} from "child_process"
|
||||
import {createHash} from "crypto"
|
||||
import {once} from "events"
|
||||
import {copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync} from "fs"
|
||||
import {AddressInfo, createConnection, createServer} from "net"
|
||||
import {tmpdir} from "os"
|
||||
import {dirname, join} from "path"
|
||||
|
||||
const tlsFixtures = join(__dirname, "fixtures/tls")
|
||||
const smpServerRelease = "v7.1.0-beta.3"
|
||||
const releaseArch: Record<string, string | undefined> = {x64: "x86-64", arm64: "aarch64"}
|
||||
const downloadArch = process.platform === "linux" ? releaseArch[process.arch] : undefined
|
||||
const downloadAttempts = 4
|
||||
|
||||
export const describeIfSmpServerAvailable = process.env.SMP_SERVER || downloadArch ? describe : describe.skip
|
||||
|
||||
export interface SmpServer {
|
||||
address: string
|
||||
stop: () => Promise<void>
|
||||
}
|
||||
|
||||
export async function startSmpServer(): Promise<SmpServer> {
|
||||
const port = await freePort()
|
||||
const dir = mkdtempSync(join(tmpdir(), "smp-server-"))
|
||||
const configDir = join(dir, "config")
|
||||
mkdirSync(configDir)
|
||||
for (const file of ["ca.crt", "server.crt", "server.key"]) copyFileSync(join(tlsFixtures, file), join(configDir, file))
|
||||
const fingerprint = caFingerprint(join(configDir, "ca.crt"))
|
||||
writeFileSync(join(configDir, "fingerprint"), `${fingerprint}\n`)
|
||||
writeFileSync(join(configDir, "smp-server.ini"), `[STORE_LOG]\nenable: off\n\n[TRANSPORT]\nhost: localhost\nport: ${port}\nwebsockets: off\n`)
|
||||
const server = spawn(await smpServerExecutable(), ["start"], {
|
||||
env: {...process.env, SMP_SERVER_CFG_PATH: configDir, SMP_SERVER_LOG_PATH: join(dir, "logs")},
|
||||
stdio: ["ignore", "ignore", "inherit"],
|
||||
})
|
||||
const stop = async () => {
|
||||
if (server.exitCode === null) {
|
||||
const exited = once(server, "exit")
|
||||
server.kill()
|
||||
await exited
|
||||
}
|
||||
rmSync(dir, {recursive: true, force: true})
|
||||
}
|
||||
try {
|
||||
await once(server, "spawn")
|
||||
await waitForServer(server, port)
|
||||
} catch (e) {
|
||||
await stop()
|
||||
throw e
|
||||
}
|
||||
return {address: `smp://${fingerprint}@localhost:${port}`, stop}
|
||||
}
|
||||
|
||||
async function smpServerExecutable(): Promise<string> {
|
||||
if (process.env.SMP_SERVER) return process.env.SMP_SERVER
|
||||
const path = join(__dirname, "../node_modules/.cache/smp-server", `smp-server-${smpServerRelease}`)
|
||||
if (!existsSync(path)) await downloadSmpServer(path)
|
||||
return path
|
||||
}
|
||||
|
||||
async function downloadSmpServer(path: string): Promise<void> {
|
||||
if (!downloadArch) {
|
||||
throw new Error(`smp-server release binaries are not available for ${process.platform} ${process.arch}, set SMP_SERVER to the smp-server path`)
|
||||
}
|
||||
const url = `https://github.com/simplex-chat/simplexmq/releases/download/${smpServerRelease}/smp-server-ubuntu-22_04-${downloadArch}`
|
||||
const data = await downloadWithRetries(url)
|
||||
mkdirSync(dirname(path), {recursive: true})
|
||||
writeFileSync(`${path}.download`, data, {mode: 0o755})
|
||||
renameSync(`${path}.download`, path)
|
||||
}
|
||||
|
||||
async function downloadWithRetries(url: string): Promise<Buffer> {
|
||||
for (let attempt = 1; attempt < downloadAttempts; attempt++) {
|
||||
const data = await download(url).catch(() => undefined)
|
||||
if (data) return data
|
||||
await new Promise(resolve => setTimeout(resolve, attempt * 5000))
|
||||
}
|
||||
return download(url)
|
||||
}
|
||||
|
||||
async function download(url: string): Promise<Buffer> {
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) throw new Error(`error downloading ${url}: ${response.status}`)
|
||||
return Buffer.from(await response.arrayBuffer())
|
||||
}
|
||||
|
||||
function caFingerprint(caFile: string): string {
|
||||
const der = Buffer.from(readFileSync(caFile, "utf-8").replace(/-----[^-]+-----|\s/g, ""), "base64")
|
||||
return createHash("sha256").update(der).digest("base64").replace(/\+/g, "-").replace(/\//g, "_")
|
||||
}
|
||||
|
||||
function freePort(): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const probe = createServer()
|
||||
probe.on("error", reject)
|
||||
probe.listen(0, "127.0.0.1", () => {
|
||||
const {port} = probe.address() as AddressInfo
|
||||
probe.close(() => resolve(port))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function waitForServer(server: ChildProcess, port: number): Promise<void> {
|
||||
const deadline = Date.now() + 15_000
|
||||
while (!(await canConnect(port))) {
|
||||
if (server.exitCode !== null || Date.now() > deadline) throw new Error(`smp-server did not start on port ${port}`)
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
}
|
||||
}
|
||||
|
||||
function canConnect(port: number): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const socket = createConnection({host: "localhost", port}, () => {
|
||||
socket.destroy()
|
||||
resolve(true)
|
||||
})
|
||||
socket.on("error", () => resolve(false))
|
||||
})
|
||||
}
|
||||
@@ -4,15 +4,17 @@ const LOCAL_FILE_HEADER = 0x04034b50
|
||||
const CENTRAL_DIRECTORY_HEADER = 0x02014b50
|
||||
const END_OF_CENTRAL_DIRECTORY = 0x06054b50
|
||||
const ZIP_VERSION = 20
|
||||
const MADE_BY_UNIX = 3 << 8
|
||||
const LOCAL_HEADER_SIZE = 30
|
||||
const CENTRAL_ENTRY_SIZE = 46
|
||||
const END_RECORD_SIZE = 22
|
||||
|
||||
export function storedZip(files: {[name: string]: string}): Buffer {
|
||||
export function storedZip(files: {[name: string]: string | {content: string, mode: number}}): Buffer {
|
||||
const local: Buffer[] = []
|
||||
const central: Buffer[] = []
|
||||
let offset = 0
|
||||
for (const [name, content] of Object.entries(files)) {
|
||||
for (const [name, file] of Object.entries(files)) {
|
||||
const {content, mode} = typeof file === "string" ? {content: file, mode: 0} : file
|
||||
const nameBuf = Buffer.from(name)
|
||||
const data = Buffer.from(content)
|
||||
const crc = zlib.crc32(data)
|
||||
@@ -26,12 +28,13 @@ export function storedZip(files: {[name: string]: string}): Buffer {
|
||||
local.push(header, nameBuf, data)
|
||||
const entry = Buffer.alloc(CENTRAL_ENTRY_SIZE)
|
||||
entry.writeUInt32LE(CENTRAL_DIRECTORY_HEADER, 0)
|
||||
entry.writeUInt16LE(ZIP_VERSION, 4)
|
||||
entry.writeUInt16LE(mode ? MADE_BY_UNIX | ZIP_VERSION : ZIP_VERSION, 4)
|
||||
entry.writeUInt16LE(ZIP_VERSION, 6)
|
||||
entry.writeUInt32LE(crc, 16)
|
||||
entry.writeUInt32LE(data.length, 20)
|
||||
entry.writeUInt32LE(data.length, 24)
|
||||
entry.writeUInt16LE(nameBuf.length, 28)
|
||||
entry.writeUInt32LE(mode * 0x10000, 38)
|
||||
entry.writeUInt32LE(offset, 42)
|
||||
central.push(entry, nameBuf)
|
||||
offset += header.length + nameBuf.length + data.length
|
||||
|
||||
@@ -4,13 +4,13 @@ Written by scripts/ci/packages_release.py prepare: it bumps the version
|
||||
and pins new library releases with the SHA-256 of their assets.
|
||||
"""
|
||||
|
||||
__version__ = "7.1.0b6.post1" # PEP 440 — read by hatchling for wheel metadata
|
||||
LIBS_RELEASE = "core-7.1.0.12" # simplex-chat-libs release tag
|
||||
__version__ = "7.1.0b6.post2" # PEP 440 — read by hatchling for wheel metadata
|
||||
LIBS_RELEASE = "core-7.1.0.14" # simplex-chat-libs release tag
|
||||
LIBS_SHA256 = {
|
||||
"simplex-chat-libs-linux-aarch64.zip": "924b8e14eaba5e309fa584c4bfe598421cf64983d63790c1c45247f592f60055",
|
||||
"simplex-chat-libs-linux-x86_64-postgres.zip": "323f908b45dde751d6082f8885d07d81fba8a5ab8b2df7d3b8ab3a87e56299e5",
|
||||
"simplex-chat-libs-linux-x86_64.zip": "995409e963db408bcf896361b669944484f845b6744fefe479aebb0ca2e7c5cd",
|
||||
"simplex-chat-libs-macos-aarch64.zip": "64464aa48b996e5a96b4aa651bc57bc69e412d3c3804b589c67bd9dc52faddbf",
|
||||
"simplex-chat-libs-macos-x86_64.zip": "94664f96ff7a2cd5a419096e7e843928faa31cb3addb8869fa1e4b5a0d0b3629",
|
||||
"simplex-chat-libs-windows-x86_64.zip": "2e10844e5c119921139c373ee2971341aceef7dc4198429d3be6b1570a009d15",
|
||||
"simplex-chat-libs-linux-aarch64.zip": "375a6021f0c2e5d79b2cd192e17c6c22c8eae73f46e3758fba24ee5c26a671aa",
|
||||
"simplex-chat-libs-linux-x86_64-postgres.zip": "c3e8a54d37801e685c6361c4e7e53e1f5163d3aa9195dcfe686b6cf90e668ef3",
|
||||
"simplex-chat-libs-linux-x86_64.zip": "cc41ab9049d61be3b386d799fa9394f2870e8ae3b2b9b8745dd3f53ba99f211e",
|
||||
"simplex-chat-libs-macos-aarch64.zip": "ac3a46004f25976f71b3ead8a20b9a01c170fe12f125a0a6799ae97ea990f234",
|
||||
"simplex-chat-libs-macos-x86_64.zip": "0d85ffcccc3a6dfe3e0da18d0b7be9ef36ad27381f047ef66cfde67ee4a7e882",
|
||||
"simplex-chat-libs-windows-x86_64.zip": "f1d1c7efc333a5ec5328a5843c9d3c9ef038fb2886c0b60ac12e87a5c1e85bbd",
|
||||
}
|
||||
@@ -26,7 +26,7 @@ build_package() {
|
||||
}
|
||||
|
||||
test_nodejs() {
|
||||
(cd "$nodejs" && npx jest --forceExit --testNamePattern '^(?!.*use preset servers)')
|
||||
(cd "$nodejs" && npx jest --forceExit)
|
||||
}
|
||||
|
||||
test_python() {
|
||||
|
||||
@@ -38,6 +38,23 @@
|
||||
</description>
|
||||
|
||||
<releases>
|
||||
<release version="7.0.4" date="2026-10-03">
|
||||
<url type="details">https://simplex.chat/blog/20260722-simplex-public-names.html</url>
|
||||
<description>
|
||||
<p>New in v7.0-v7.0.4.</p>
|
||||
<p>SimpleX public names for channels and businesses (BETA).</p>
|
||||
<p>Better channels:</p>
|
||||
<ul>
|
||||
<li>Promote subscribers to contributors.</li>
|
||||
<li>Publish your channel on your website.</li>
|
||||
<li>Verify security code with contributors.</li>
|
||||
<li>Wider messages, easier to read.</li>
|
||||
<li>Host your own chat relays.</li>
|
||||
</ul>
|
||||
<p>Add a longer description to your profile.</p>
|
||||
<p>Simplified app settings.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="7.0.3" date="2026-09-26">
|
||||
<url type="details">https://simplex.chat/blog/20260722-simplex-public-names.html</url>
|
||||
<description>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"channels": [
|
||||
{
|
||||
"link": "https://smp18.simplex.im/c#grcfG3ulVI4Sh6ow33qBsmSk7uEy3gRSl2KkJ5ER6tA",
|
||||
"title": "SimpleX Network News",
|
||||
"changeFrequency": "weekly"
|
||||
},
|
||||
{
|
||||
"link": "https://smp10.simplex.im/c#q09nMBmWFGz1m2TvgfZFaEOG5D2a7Ma9mSkl6pHXEsg",
|
||||
"title": "SimpleX Crowdfunding News",
|
||||
"changeFrequency": "weekly"
|
||||
}
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"link": "https://smp4.simplex.im/g#hr4lvFeBmndWMKTwqiodPz3VBo_6UmdGWocXd1SupsM",
|
||||
"title": "SimpleX users group"
|
||||
}
|
||||
],
|
||||
"contacts": [
|
||||
{
|
||||
"link": "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw",
|
||||
"title": "Ask SimpleX Team",
|
||||
"audience": ["people", "agents"],
|
||||
"business": true
|
||||
},
|
||||
{
|
||||
"link": "https://smp4.simplex.im/a#lXUjJW5vHYQzoLYgmi8GbxkGP41_kjefFvBrdwg-0Ok",
|
||||
"title": "SimpleX Directory",
|
||||
"audience": ["people", "agents"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
{% set markdownAlternateUrl = page | markdownAlternate %}
|
||||
{% if markdownAlternateUrl %}<link rel="alternate" type="text/markdown" href="{{ markdownAlternateUrl }}"/>{% endif %}
|
||||
<link rel="describedby" href="/llms.txt"/>
|
||||
<link rel="simplex" href="https://smp18.simplex.im/c#grcfG3ulVI4Sh6ow33qBsmSk7uEy3gRSl2KkJ5ER6tA" title="SimpleX Network News"/>
|
||||
<link rel="simplex" href="https://smp10.simplex.im/c#q09nMBmWFGz1m2TvgfZFaEOG5D2a7Ma9mSkl6pHXEsg" title="SimpleX Crowdfunding News"/>
|
||||
<link rel="simplex" href="https://smp4.simplex.im/g#hr4lvFeBmndWMKTwqiodPz3VBo_6UmdGWocXd1SupsM" title="SimpleX users group"/>
|
||||
{% if active_directory %}<link rel="simplex" href="https://smp4.simplex.im/a#lXUjJW5vHYQzoLYgmi8GbxkGP41_kjefFvBrdwg-0Ok" title="SimpleX Directory"/>
|
||||
{% endif %}<link rel="simplex" href="https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw" title="Ask SimpleX Team"/>
|
||||
@@ -11,4 +11,4 @@ channel:
|
||||
- relay3.simplex.chat
|
||||
---
|
||||
|
||||
<p>The news about SimpleX Chat equity crowdfunding on Wefunder, published as a SimpleX channel. Join it in the SimpleX Chat app: <a href="{{ channel.link }}">SimpleX Crowdfunding News</a>. The crowdfunding memo: <a href="/wefunder.md">simplex.chat/wefunder.md</a>.</p>
|
||||
<p>The news about SimpleX Chat equity crowdfunding on Wefunder, published as a SimpleX channel. Join it in the SimpleX Chat app: <a href="{{ channel.link }}" rel="simplex">SimpleX Crowdfunding News</a>. The crowdfunding memo: <a href="/wefunder.md">simplex.chat/wefunder.md</a>.</p>
|
||||
@@ -318,7 +318,7 @@ active_directory: true
|
||||
<h1 class="text-[38px] md:text-[45px] text-center font-bold text-active-blue mb-8">SimpleX Directory</h1>
|
||||
<p>Welcome to the selected users' communities that you can join via <a href="/downloads">SimpleX Chat
|
||||
app</a>.</p>
|
||||
<p>SimpleX Directory is also available as a <a href="https://smp4.simplex.im/a#lXUjJW5vHYQzoLYgmi8GbxkGP41_kjefFvBrdwg-0Ok" target="_blank" rel="noopener">SimpleX chat bot</a>.</p>
|
||||
<p>SimpleX Directory is also available as a <a href="https://smp4.simplex.im/a#lXUjJW5vHYQzoLYgmi8GbxkGP41_kjefFvBrdwg-0Ok" target="_blank" rel="noopener simplex">SimpleX chat bot</a>.</p>
|
||||
<p>Read about <a href="/docs/directory.html">how to add</a> your community.</p>
|
||||
<template data-markdown><p>All listed groups and channels in JSON, with names, descriptions, member counts, join links and activity times: <a href="https://directory.simplex.chat/data/listing.json">directory.simplex.chat/data/listing.json</a>.</p></template>
|
||||
<div class="search-container">
|
||||
|
||||
@@ -273,7 +273,7 @@ active_home: true
|
||||
<a href="/directory" class="group-image" target="_blank" rel="noopener"><img src="/img/group.svg"></a>
|
||||
<a href="/directory" class="group-image" target="_blank" rel="noopener"><img src="/img/group.svg"></a>
|
||||
<a href="/directory" class="group-image" target="_blank" rel="noopener"><img src="/img/group.svg"></a>
|
||||
<a href="https://smp4.simplex.im/g#hr4lvFeBmndWMKTwqiodPz3VBo_6UmdGWocXd1SupsM" class="simplex-image" title="{{ 'index-directory-users-group-title' | i18n({}, lang) }}" target="_blank" rel="noopener">
|
||||
<a href="https://smp4.simplex.im/g#hr4lvFeBmndWMKTwqiodPz3VBo_6UmdGWocXd1SupsM" class="simplex-image" title="{{ 'index-directory-users-group-title' | i18n({}, lang) }}" target="_blank" rel="noopener simplex">
|
||||
<img class="block dark:hidden" src="/img/new/logo-symbol-light.svg">
|
||||
<img class="hidden dark:block" src="/img/new/logo-symbol-dark.svg">
|
||||
</a>
|
||||
|
||||
@@ -339,7 +339,7 @@ function displayEntries(entries) {
|
||||
imgLinkElement.href = groupLinkUri;
|
||||
}
|
||||
imgLinkElement.target = "_blank";
|
||||
imgLinkElement.rel = "noopener";
|
||||
imgLinkElement.rel = groupLink.connShortLink ? "noopener simplex" : "noopener";
|
||||
imgLinkElement.title = `Join ${displayName}`;
|
||||
|
||||
const imgElement = document.createElement('img');
|
||||
|
||||
@@ -57,6 +57,14 @@ Every page of simplex.chat is also available as Markdown: replace `.html` with `
|
||||
- [SimpleX for business](https://simplex.chat/docs/business.md)
|
||||
- [Host your own messaging server](https://simplex.chat/docs/server.md) and [file server](https://simplex.chat/docs/xftp-server.md)
|
||||
|
||||
## Communications
|
||||
|
||||
- [SimpleX Network News](https://smp18.simplex.im/c#grcfG3ulVI4Sh6ow33qBsmSk7uEy3gRSl2KkJ5ER6tA): SimpleX updates for people, weekly. Network technology, governance and development.
|
||||
- [SimpleX Crowdfunding News](https://smp10.simplex.im/c#q09nMBmWFGz1m2TvgfZFaEOG5D2a7Ma9mSkl6pHXEsg): SimpleX updates for people, weekly. The equity crowdfunding on Wefunder.
|
||||
- [SimpleX users group](https://smp4.simplex.im/g#hr4lvFeBmndWMKTwqiodPz3VBo_6UmdGWocXd1SupsM): SimpleX group for people. Users of SimpleX Chat.
|
||||
- [Ask SimpleX Team](https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw): SimpleX business contact for people and agents. Questions to the SimpleX Chat team.
|
||||
- [SimpleX Directory](https://smp4.simplex.im/a#lXUjJW5vHYQzoLYgmi8GbxkGP41_kjefFvBrdwg-0Ok): SimpleX contact for people and agents. Search for public groups and channels.
|
||||
|
||||
## For AI agents and developers
|
||||
|
||||
- [SimpleX for AI agents and developers](https://simplex.chat/llms/agents.md): a bot in five minutes, the API, building from source, OpenClaw, asking the team
|
||||
|
||||
@@ -12,4 +12,4 @@ channel:
|
||||
- relay3.simplex.chat
|
||||
---
|
||||
|
||||
<p>The news about SimpleX Network technology, governance and anything related to its development, published as a SimpleX channel. Join it in the SimpleX Chat app: <a href="{{ channel.link }}">SimpleX Network News</a>.</p>
|
||||
<p>The news about SimpleX Network technology, governance and anything related to its development, published as a SimpleX channel. Join it in the SimpleX Chat app: <a href="{{ channel.link }}" rel="simplex">SimpleX Network News</a>.</p>
|
||||
Reference in new issue
Block a user