@shhhum/simplex-chat 0.0.0-stage → 7.1.0-beta.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +129 -2
- package/dist/api.d.ts +439 -0
- package/dist/api.js +911 -0
- package/dist/api.js.map +1 -0
- package/dist/bot.d.ts +34 -0
- package/dist/bot.js +194 -0
- package/dist/bot.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +46 -0
- package/dist/cli.js.map +1 -0
- package/dist/core.d.ts +138 -0
- package/dist/core.js +144 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/libs.d.ts +12 -0
- package/dist/libs.js +210 -0
- package/dist/libs.js.map +1 -0
- package/dist/release.d.ts +5 -0
- package/dist/release.js +20 -0
- package/dist/release.js.map +1 -0
- package/dist/simplex.d.ts +12 -0
- package/dist/simplex.js +22 -0
- package/dist/util.d.ts +15 -0
- package/dist/util.js +91 -0
- package/dist/util.js.map +1 -0
- package/docs/Namespace.api.md +33 -0
- package/docs/Namespace.bot.md +24 -0
- package/docs/Namespace.core.md +50 -0
- package/docs/Namespace.util.md +25 -0
- package/docs/README.md +12 -0
- package/docs/api.Class.ChatApi.md +1851 -0
- package/docs/api.Class.ChatCommandError.md +205 -0
- package/docs/api.Enumeration.ConnReqType.md +27 -0
- package/docs/api.Interface.BotAddressSettings.md +60 -0
- package/docs/api.TypeAlias.DbConfig.md +62 -0
- package/docs/api.TypeAlias.EventSubscriberFunc.md +31 -0
- package/docs/api.TypeAlias.EventSubscribers.md +11 -0
- package/docs/api.Variable.defaultBotAddressSettings.md +11 -0
- package/docs/bot.Function.run.md +21 -0
- package/docs/bot.Function.subscribeChatItems.md +27 -0
- package/docs/bot.Interface.BotConfig.md +87 -0
- package/docs/bot.Interface.BotOptions.md +81 -0
- package/docs/bot.TypeAlias.BotDbOpts.md +21 -0
- package/docs/core.Class.ChatAPIError.md +205 -0
- package/docs/core.Class.ChatInitError.md +205 -0
- package/docs/core.DBMigrationError.Interface.ErrorMigration.md +41 -0
- package/docs/core.DBMigrationError.Interface.ErrorNotADatabase.md +33 -0
- package/docs/core.DBMigrationError.Interface.ErrorSQL.md +41 -0
- package/docs/core.DBMigrationError.Interface.InvalidConfirmation.md +25 -0
- package/docs/core.DBMigrationError.Interface.InvalidQueueSize.md +25 -0
- package/docs/core.DBMigrationError.TypeAlias.Tag.md +11 -0
- package/docs/core.Enumeration.MigrationConfirmation.md +43 -0
- package/docs/core.Function.chatCloseStore.md +23 -0
- package/docs/core.Function.chatDecryptFile.md +31 -0
- package/docs/core.Function.chatEncryptFile.md +31 -0
- package/docs/core.Function.chatMigrateInit.md +37 -0
- package/docs/core.Function.chatReadFile.md +27 -0
- package/docs/core.Function.chatRecvMsgWait.md +27 -0
- package/docs/core.Function.chatSendCmd.md +27 -0
- package/docs/core.Function.chatWriteFile.md +31 -0
- package/docs/core.Function.loadLibrary.md +24 -0
- package/docs/core.Interface.APIResult.md +31 -0
- package/docs/core.Interface.CryptoArgs.md +27 -0
- package/docs/core.Interface.UpMigration.md +25 -0
- package/docs/core.MTRError.Interface.MTREDifferent.md +33 -0
- package/docs/core.MTRError.Interface.MTRENoDown.md +33 -0
- package/docs/core.MTRError.TypeAlias.Tag.md +11 -0
- package/docs/core.MigrationError.Interface.MEDowngrade.md +33 -0
- package/docs/core.MigrationError.Interface.MEUpgrade.md +33 -0
- package/docs/core.MigrationError.Interface.MigrationError.md +33 -0
- package/docs/core.MigrationError.TypeAlias.Tag.md +11 -0
- package/docs/core.Namespace.DBMigrationError.md +19 -0
- package/docs/core.Namespace.MTRError.md +16 -0
- package/docs/core.Namespace.MigrationError.md +17 -0
- package/docs/core.TypeAlias.Backend.md +11 -0
- package/docs/core.TypeAlias.DBMigrationError.md +11 -0
- package/docs/core.TypeAlias.MTRError.md +11 -0
- package/docs/core.TypeAlias.MigrationError.md +11 -0
- package/docs/util.Function.botAddressSettings.md +21 -0
- package/docs/util.Function.chatInfoName.md +21 -0
- package/docs/util.Function.chatInfoRef.md +21 -0
- package/docs/util.Function.ciBotCommand.md +21 -0
- package/docs/util.Function.ciContentText.md +21 -0
- package/docs/util.Function.contactAddressStr.md +21 -0
- package/docs/util.Function.fromLocalProfile.md +21 -0
- package/docs/util.Function.reactionText.md +21 -0
- package/docs/util.Function.senderName.md +25 -0
- package/docs/util.Interface.BotCommand.md +25 -0
- package/examples/squaring-bot-readme.js +17 -0
- package/examples/squaring-bot.ts +42 -0
- package/package.json +55 -4
- package/src/api.ts +997 -0
- package/src/bot.ts +221 -0
- package/src/cli.ts +42 -0
- package/src/core.ts +240 -0
- package/src/index.ts +22 -0
- package/src/libs.ts +202 -0
- package/src/release.ts +19 -0
- package/src/simplex.d.ts +12 -0
- package/src/simplex.js +22 -0
- package/src/util.ts +92 -0
- package/tsconfig.json +23 -0
package/src/bot.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {T} from "@simplex-chat/types"
|
|
2
|
+
import * as api from "./api"
|
|
3
|
+
import * as core from "./core"
|
|
4
|
+
import * as util from "./util"
|
|
5
|
+
import equal = require("fast-deep-equal")
|
|
6
|
+
|
|
7
|
+
export type BotDbOpts = api.DbConfig & {
|
|
8
|
+
confirmMigrations?: core.MigrationConfirmation
|
|
9
|
+
queueSize?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface BotOptions {
|
|
13
|
+
createAddress?: boolean
|
|
14
|
+
updateAddress?: boolean
|
|
15
|
+
updateProfile?: boolean
|
|
16
|
+
addressSettings?: api.BotAddressSettings
|
|
17
|
+
allowFiles?: boolean
|
|
18
|
+
commands?: T.ChatBotCommand[] // commands to show in client UI
|
|
19
|
+
useBotProfile?: boolean // create profile not marked as a bot, with default preferences
|
|
20
|
+
logContacts?: boolean
|
|
21
|
+
logNetwork?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const defaultOpts: Required<BotOptions> = {
|
|
25
|
+
createAddress: true,
|
|
26
|
+
updateAddress: true,
|
|
27
|
+
updateProfile: true,
|
|
28
|
+
addressSettings: api.defaultBotAddressSettings,
|
|
29
|
+
allowFiles: false,
|
|
30
|
+
commands: [],
|
|
31
|
+
useBotProfile: true,
|
|
32
|
+
logContacts: true,
|
|
33
|
+
logNetwork: false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface BotConfig {
|
|
37
|
+
profile: T.Profile,
|
|
38
|
+
simplexDomain?: string | null,
|
|
39
|
+
dbOpts: BotDbOpts,
|
|
40
|
+
options: BotOptions,
|
|
41
|
+
onMessage?: (chatItem: T.AChatItem, content: T.MsgContent, chat: api.ChatApi) => void | Promise<void>,
|
|
42
|
+
// command handlers can be different from commands to be shown in client UI
|
|
43
|
+
onCommands?: {[K in string]?: ((chatItem: T.AChatItem, command: util.BotCommand, chat: api.ChatApi) => void | Promise<void>)},
|
|
44
|
+
// If you use `onMessage` and to subscribe "newChatItems" event, exclude content messages from processing
|
|
45
|
+
// If you use `onCommands` and to subscribe "newChatItems" event, exclude commands from processing
|
|
46
|
+
events?: api.EventSubscribers
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function run({profile, simplexDomain, dbOpts, options = defaultOpts, onMessage, onCommands = {}, events = {}}: BotConfig): Promise<[api.ChatApi, T.User, T.UserContactLink | undefined]> {
|
|
50
|
+
const bot = await api.ChatApi.init(dbOpts, dbOpts.confirmMigrations || core.MigrationConfirmation.YesUp, dbOpts.queueSize)
|
|
51
|
+
const opts = fullOptions(options)
|
|
52
|
+
if (onMessage || Object.keys(onCommands).length > 0) subscribeChatItems(bot, onMessage, onCommands)
|
|
53
|
+
if (Object.keys(events).length > 0) bot.on(events)
|
|
54
|
+
subscribeLogEvents(bot, opts)
|
|
55
|
+
const botProfile = mkBotProfile(profile, opts)
|
|
56
|
+
const user = await createBotUser(bot, botProfile)
|
|
57
|
+
await bot.startChat()
|
|
58
|
+
const address = await createOrUpdateAddress(bot, user, opts)
|
|
59
|
+
if (address) {
|
|
60
|
+
const addressLink = util.contactAddressStr(address.connLinkContact)
|
|
61
|
+
console.log(`Bot address: ${addressLink}`)
|
|
62
|
+
if (opts.useBotProfile) botProfile.contactLink = addressLink
|
|
63
|
+
}
|
|
64
|
+
const namedUser = await updateBotSimplexDomain(bot, user, simplexDomain, opts)
|
|
65
|
+
await updateBotUserProfile(bot, namedUser, botProfile, opts)
|
|
66
|
+
return [bot, user, address]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function fullOptions(options: BotOptions): Required<BotOptions> {
|
|
70
|
+
const opts = {
|
|
71
|
+
createAddress: options.createAddress ?? defaultOpts.createAddress,
|
|
72
|
+
updateAddress: options.updateAddress ?? defaultOpts.updateAddress,
|
|
73
|
+
updateProfile: options.updateProfile ?? defaultOpts.updateProfile,
|
|
74
|
+
addressSettings: options.addressSettings ?? defaultOpts.addressSettings,
|
|
75
|
+
allowFiles: options.allowFiles ?? defaultOpts.allowFiles,
|
|
76
|
+
commands: options.commands ?? defaultOpts.commands,
|
|
77
|
+
useBotProfile: options.useBotProfile ?? defaultOpts.useBotProfile,
|
|
78
|
+
logContacts: options.logContacts ?? defaultOpts.logContacts,
|
|
79
|
+
logNetwork: options.logNetwork ?? defaultOpts.logNetwork
|
|
80
|
+
}
|
|
81
|
+
const welcomeMessage = opts.addressSettings.welcomeMessage ?? defaultOpts.addressSettings.welcomeMessage
|
|
82
|
+
opts.addressSettings = {
|
|
83
|
+
autoAccept: opts.addressSettings.autoAccept ?? defaultOpts.addressSettings.autoAccept,
|
|
84
|
+
welcomeMessage: typeof welcomeMessage === "string" ? {type: "text", text: welcomeMessage} : welcomeMessage,
|
|
85
|
+
businessAddress: opts.addressSettings.businessAddress ?? defaultOpts.addressSettings.businessAddress
|
|
86
|
+
}
|
|
87
|
+
return opts
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function mkBotProfile(profile: T.Profile, opts: Required<BotOptions>): T.Profile {
|
|
91
|
+
if (opts.useBotProfile) {
|
|
92
|
+
const prefs = profile.preferences || {}
|
|
93
|
+
if (prefs.files || prefs.calls || prefs.voice || prefs.commands) {
|
|
94
|
+
console.log("Option useBotProfile is enabled and profile preferences used for files, calls, voice or commands, exiting")
|
|
95
|
+
process.exit()
|
|
96
|
+
}
|
|
97
|
+
prefs.files = {allow: opts.allowFiles ? T.FeatureAllowed.Yes : T.FeatureAllowed.No}
|
|
98
|
+
prefs.calls = {allow: T.FeatureAllowed.No}
|
|
99
|
+
prefs.voice = {allow: T.FeatureAllowed.No}
|
|
100
|
+
prefs.commands = opts.commands
|
|
101
|
+
profile.preferences = prefs
|
|
102
|
+
profile.peerType = T.ChatPeerType.Bot
|
|
103
|
+
} else if (opts.commands.length > 0) {
|
|
104
|
+
console.log("Option useBotProfile is disabled and commands are passed, exiting")
|
|
105
|
+
process.exit()
|
|
106
|
+
}
|
|
107
|
+
return profile
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function subscribeChatItems(
|
|
111
|
+
bot: api.ChatApi,
|
|
112
|
+
onMessage: ((chatItem: T.AChatItem, content: T.MsgContent, chat: api.ChatApi) => void | Promise<void>) | undefined,
|
|
113
|
+
commands: {[K in string]?: ((chatItem: T.AChatItem, command: util.BotCommand, chat: api.ChatApi) => void | Promise<void>)}
|
|
114
|
+
) {
|
|
115
|
+
bot.on("newChatItems", async ({chatItems}) => {
|
|
116
|
+
for (const ci of chatItems) {
|
|
117
|
+
const content = ci.chatItem.content
|
|
118
|
+
if (content.type !== "rcvMsgContent") continue
|
|
119
|
+
const cmd = util.ciBotCommand(ci.chatItem)
|
|
120
|
+
const cmdFunc = cmd && (commands[cmd.keyword] || commands[""])
|
|
121
|
+
try {
|
|
122
|
+
if (cmd && cmdFunc) await cmdFunc(ci, cmd, bot)
|
|
123
|
+
else if (onMessage) await onMessage(ci, content.msgContent, bot)
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.log(cmd && cmdFunc ? `${cmd.keyword} command processing error` : "message processing error", e)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function subscribeLogEvents(bot: api.ChatApi, opts: Required<BotOptions>) {
|
|
132
|
+
if (opts.logContacts) {
|
|
133
|
+
bot.on({
|
|
134
|
+
"contactConnected": ({contact}) => console.log(`${contact.profile.displayName} connected`),
|
|
135
|
+
"contactDeletedByContact": ({contact}) => console.log(`${contact.profile.displayName} deleted connection with bot`)
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
if (opts.logNetwork) {
|
|
139
|
+
bot.on({
|
|
140
|
+
"hostConnected": ({transportHost}) => console.log(`connected server ${transportHost}`),
|
|
141
|
+
"hostDisconnected": ({transportHost}) => console.log(`diconnected server ${transportHost}`),
|
|
142
|
+
"subscriptionStatus": ({subscriptionStatus, connections}) => console.log(`${connections.length} subscription(s) ${subscriptionStatus.type}`)
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function createBotUser(bot: api.ChatApi, profile: T.Profile): Promise<T.User> {
|
|
148
|
+
let user = await bot.apiGetActiveUser()
|
|
149
|
+
if (!user) {
|
|
150
|
+
console.log("No active user in database, creating...")
|
|
151
|
+
user = await bot.apiCreateActiveUser(profile)
|
|
152
|
+
}
|
|
153
|
+
console.log("Bot user: ", user.profile.displayName)
|
|
154
|
+
return user
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function createOrUpdateAddress(bot: api.ChatApi, user: T.User, opts: Required<BotOptions>): Promise<T.UserContactLink | undefined> {
|
|
158
|
+
const {userId} = user
|
|
159
|
+
let address = await bot.apiGetUserAddress(userId)
|
|
160
|
+
if (!address) {
|
|
161
|
+
if (opts.createAddress) {
|
|
162
|
+
console.log("Bot has no address, creating...")
|
|
163
|
+
await bot.apiCreateUserAddress(userId)
|
|
164
|
+
address = await bot.apiGetUserAddress(userId)
|
|
165
|
+
if (!address) {
|
|
166
|
+
console.log("Failed reading created user address, exiting")
|
|
167
|
+
process.exit()
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
console.log("Warning: bot has no address")
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const addressSettings = opts.addressSettings || defaultOpts.addressSettings
|
|
176
|
+
if (!equal(util.botAddressSettings(address), addressSettings)) {
|
|
177
|
+
if (opts.updateAddress) {
|
|
178
|
+
console.log("Bot address settings changed, updating...")
|
|
179
|
+
await bot.apiSetAddressSettings(userId, addressSettings)
|
|
180
|
+
} else {
|
|
181
|
+
console.log("Bot address settings changed")
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return address
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function updateBotSimplexDomain(bot: api.ChatApi, user: T.User, simplexDomain: string | null | undefined, opts: Required<BotOptions>): Promise<T.User> {
|
|
189
|
+
if (simplexDomain === undefined) return user
|
|
190
|
+
const domain = simplexDomain === null ? undefined : simplexDomain.toLowerCase()
|
|
191
|
+
if (user.profile.contactDomain?.domain === domain) return user
|
|
192
|
+
if (!opts.updateAddress) {
|
|
193
|
+
console.log("Bot SimpleX name changed")
|
|
194
|
+
return user
|
|
195
|
+
}
|
|
196
|
+
console.log("Bot SimpleX name changed, updating...")
|
|
197
|
+
try {
|
|
198
|
+
return await bot.apiSetUserDomain(user.userId, domain)
|
|
199
|
+
} catch (e) {
|
|
200
|
+
console.log("Error updating bot SimpleX name", e)
|
|
201
|
+
return user
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function updateBotUserProfile(bot: api.ChatApi, user: T.User, profile: T.Profile, opts: Required<BotOptions>): Promise<void> {
|
|
206
|
+
const {userId} = user
|
|
207
|
+
const {contactDomain, ...currentProfile} = util.fromLocalProfile(user.profile)
|
|
208
|
+
if (!equal(currentProfile, profile)) {
|
|
209
|
+
if (opts.updateProfile) {
|
|
210
|
+
console.log("Bot profile changed, updating...")
|
|
211
|
+
const summary = await bot.apiUpdateProfile(userId, {...profile, contactDomain})
|
|
212
|
+
console.log(
|
|
213
|
+
summary
|
|
214
|
+
? `Bot profile updated: ${summary.updateSuccesses} updated contact(s), ${summary.updateFailures} failed contact update(s).`
|
|
215
|
+
: "Unexpected: profile did not change!"
|
|
216
|
+
)
|
|
217
|
+
} else {
|
|
218
|
+
console.log("Bot profile changed")
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {parseArgs} from "util"
|
|
3
|
+
import type {Backend} from "./core"
|
|
4
|
+
import {resolveAddonPath, resolveLibsDir} from "./libs"
|
|
5
|
+
|
|
6
|
+
const USAGE = "usage: simplex-chat install [--backend sqlite|postgres]"
|
|
7
|
+
const EXIT_INSTALL_FAILED = 1
|
|
8
|
+
const EXIT_USAGE = 2
|
|
9
|
+
|
|
10
|
+
export function parseInstallArgs(argv: string[]): Backend {
|
|
11
|
+
const {positionals, values: {backend = "sqlite"}} = parseArgs({args: argv, allowPositionals: true, options: {backend: {type: "string"}}})
|
|
12
|
+
if (positionals.length !== 1 || positionals[0] !== "install") throw new Error("expected command: install")
|
|
13
|
+
if (backend !== "sqlite" && backend !== "postgres") throw new Error(`invalid backend: ${backend}`)
|
|
14
|
+
return backend
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function main(argv: string[]): Promise<number> {
|
|
18
|
+
if (argv.includes("-h") || argv.includes("--help")) {
|
|
19
|
+
console.log(USAGE)
|
|
20
|
+
return 0
|
|
21
|
+
}
|
|
22
|
+
let backend: Backend
|
|
23
|
+
try {
|
|
24
|
+
backend = parseInstallArgs(argv)
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.error(`${(e as Error).message}\n${USAGE}`)
|
|
27
|
+
return EXIT_USAGE
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const [dir, addon] = await Promise.all([resolveLibsDir(backend), resolveAddonPath()])
|
|
31
|
+
console.log(`libsimplex installed at: ${dir}`)
|
|
32
|
+
console.log(`Node.js add-on installed at: ${addon}`)
|
|
33
|
+
return 0
|
|
34
|
+
} catch (e) {
|
|
35
|
+
console.error(`install failed: ${(e as Error).message}`)
|
|
36
|
+
return EXIT_INSTALL_FAILED
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (require.main === module) {
|
|
41
|
+
main(process.argv.slice(2)).then(code => { process.exitCode = code })
|
|
42
|
+
}
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import {ChatEvent, ChatResponse, T} from "@simplex-chat/types"
|
|
2
|
+
import * as libs from "./libs"
|
|
3
|
+
import * as simplex from "./simplex"
|
|
4
|
+
|
|
5
|
+
export type Backend = "sqlite" | "postgres"
|
|
6
|
+
|
|
7
|
+
let loading: {backend: Backend, promise: Promise<void>} | undefined
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolve (downloading on first use) and load libsimplex for the backend.
|
|
11
|
+
* One libsimplex per process: the Haskell runtime is initialized once, and another backend is rejected.
|
|
12
|
+
*/
|
|
13
|
+
export function loadLibrary(backend: Backend): Promise<void> {
|
|
14
|
+
if (loading) {
|
|
15
|
+
if (loading.backend === backend) return loading.promise
|
|
16
|
+
return Promise.reject(new Error(`libsimplex already loaded with backend=${loading.backend}; cannot switch to ${backend} in the same process`))
|
|
17
|
+
}
|
|
18
|
+
const promise = Promise.all([libs.resolveLibsDir(backend), libs.resolveAddonPath()])
|
|
19
|
+
.then(([dir, addon]) => simplex.load(addon, libs.libPath(dir)))
|
|
20
|
+
loading = {backend, promise}
|
|
21
|
+
// a failed download or load can be retried
|
|
22
|
+
promise.catch(() => { loading = undefined })
|
|
23
|
+
return promise
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initialize chat controller
|
|
28
|
+
* @param {number} [queueSize] - Size of internal queues, the core default is used when omitted.
|
|
29
|
+
*/
|
|
30
|
+
export async function chatMigrateInit(dbPath: string, dbKey: string, confirm: MigrationConfirmation, queueSize?: number): Promise<bigint> {
|
|
31
|
+
const [ctrl, res] = queueSize === undefined
|
|
32
|
+
? await simplex.chat_migrate_init(dbPath, dbKey, confirm)
|
|
33
|
+
: await simplex.chat_migrate_init_queue(dbPath, dbKey, confirm, queueSize)
|
|
34
|
+
const json = JSON.parse(res)
|
|
35
|
+
if (json.type === 'ok') return ctrl
|
|
36
|
+
throw new ChatInitError("Database or migration error (see dbMigrationError property)", json as DBMigrationError)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Close chat store
|
|
41
|
+
*/
|
|
42
|
+
export async function chatCloseStore(ctrl: bigint): Promise<void> {
|
|
43
|
+
const res = await simplex.chat_close_store(ctrl)
|
|
44
|
+
if (res !== "") throw new Error(res)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Send chat command as string
|
|
49
|
+
*/
|
|
50
|
+
export async function chatSendCmd(ctrl: bigint, cmd: string): Promise<ChatResponse> {
|
|
51
|
+
const res = await simplex.chat_send_cmd(ctrl, cmd)
|
|
52
|
+
const json = JSON.parse(res) as APIResult<ChatResponse>
|
|
53
|
+
// console.log(cmd.slice(0, 16), json.result?.type || json.error)
|
|
54
|
+
if (typeof json.result === 'object') return json.result
|
|
55
|
+
if (typeof json.error === 'object') throw new ChatAPIError("Chat command error (see chatError property)", json.error as T.ChatError)
|
|
56
|
+
throw new ChatAPIError("Invalid chat command result")
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Receive chat event
|
|
61
|
+
*/
|
|
62
|
+
export async function chatRecvMsgWait(ctrl: bigint, wait: number): Promise<ChatEvent | undefined> {
|
|
63
|
+
const res = await simplex.chat_recv_msg_wait(ctrl, wait)
|
|
64
|
+
if (res === "") return undefined
|
|
65
|
+
const json = JSON.parse(res) as APIResult<ChatEvent>
|
|
66
|
+
// if (json.result) console.log("event", json.result.type)
|
|
67
|
+
if (typeof json.result === 'object') return json.result
|
|
68
|
+
if (typeof json.error === 'object') throw new ChatAPIError("Chat event error (see chatError property)", json.error as T.ChatError)
|
|
69
|
+
throw new ChatAPIError("Invalid chat event")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Write buffer to encrypted file
|
|
74
|
+
*/
|
|
75
|
+
export async function chatWriteFile(ctrl: bigint, path: string, buffer: ArrayBuffer | Uint8Array): Promise<CryptoArgs> {
|
|
76
|
+
const res = await simplex.chat_write_file(ctrl, path, buffer)
|
|
77
|
+
return cryptoArgsResult(res)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Read buffer from encrypted file
|
|
82
|
+
*/
|
|
83
|
+
export async function chatReadFile(path: string, {fileKey, fileNonce}: CryptoArgs): Promise<Buffer> {
|
|
84
|
+
return await simplex.chat_read_file(path, fileKey, fileNonce)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Encrypt file
|
|
89
|
+
*/
|
|
90
|
+
export async function chatEncryptFile(ctrl: bigint, fromPath: string, toPath: string): Promise<CryptoArgs> {
|
|
91
|
+
const res = await simplex.chat_encrypt_file(ctrl, fromPath, toPath)
|
|
92
|
+
return cryptoArgsResult(res)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Decrypt file
|
|
97
|
+
*/
|
|
98
|
+
export async function chatDecryptFile(fromPath: string, {fileKey, fileNonce}: CryptoArgs, toPath: string): Promise<void> {
|
|
99
|
+
const res = await simplex.chat_decrypt_file(fromPath, fileKey, fileNonce, toPath)
|
|
100
|
+
if (res !== "") throw new Error(res)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function cryptoArgsResult(res: string): CryptoArgs {
|
|
104
|
+
const json = JSON.parse(res)
|
|
105
|
+
switch (json.type) {
|
|
106
|
+
case "result": return json.cryptoArgs as CryptoArgs
|
|
107
|
+
case "error": throw Error(json.writeError)
|
|
108
|
+
default: throw Error("unexpected chat_write_file result: " + res)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface APIResult<R> {
|
|
113
|
+
result?: R
|
|
114
|
+
error?: T.ChatError
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class ChatAPIError extends Error {
|
|
118
|
+
constructor(public message: string, public chatError: T.ChatError | undefined = undefined) {
|
|
119
|
+
super(message)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Migration confirmation mode
|
|
125
|
+
*/
|
|
126
|
+
export enum MigrationConfirmation {
|
|
127
|
+
YesUp = "yesUp",
|
|
128
|
+
YesUpDown = "yesUpDown",
|
|
129
|
+
Console = "console",
|
|
130
|
+
Error = "error"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* File encryption key and nonce
|
|
135
|
+
*/
|
|
136
|
+
export interface CryptoArgs {
|
|
137
|
+
fileKey: string
|
|
138
|
+
fileNonce: string
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export class ChatInitError extends Error {
|
|
142
|
+
constructor(public message: string, public dbMigrationError: DBMigrationError) {
|
|
143
|
+
super(message)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export type DBMigrationError =
|
|
148
|
+
| DBMigrationError.InvalidConfirmation
|
|
149
|
+
| DBMigrationError.InvalidQueueSize
|
|
150
|
+
| DBMigrationError.ErrorNotADatabase // invalid/corrupt database file or incorrect encryption key
|
|
151
|
+
| DBMigrationError.ErrorMigration
|
|
152
|
+
| DBMigrationError.ErrorSQL
|
|
153
|
+
|
|
154
|
+
export namespace DBMigrationError {
|
|
155
|
+
export type Tag = "invalidConfirmation" | "invalidQueueSize" | "errorNotADatabase" | "errorMigration" | "errorSQL"
|
|
156
|
+
|
|
157
|
+
interface Interface {
|
|
158
|
+
type: Tag
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface InvalidConfirmation extends Interface {
|
|
162
|
+
type: "invalidConfirmation"
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface InvalidQueueSize extends Interface {
|
|
166
|
+
type: "invalidQueueSize"
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface ErrorNotADatabase extends Interface {
|
|
170
|
+
type: "errorNotADatabase"
|
|
171
|
+
dbFile: string
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface ErrorMigration extends Interface {
|
|
175
|
+
type: "errorMigration"
|
|
176
|
+
dbFile: string
|
|
177
|
+
migrationError: MigrationError
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface ErrorSQL extends Interface {
|
|
181
|
+
type: "errorSQL"
|
|
182
|
+
dbFile: string
|
|
183
|
+
migrationSQLError: string
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export type MigrationError =
|
|
188
|
+
| MigrationError.MEUpgrade
|
|
189
|
+
| MigrationError.MEDowngrade
|
|
190
|
+
| MigrationError.MigrationError
|
|
191
|
+
|
|
192
|
+
export namespace MigrationError {
|
|
193
|
+
export type Tag = "upgrade" | "downgrade" | "migrationError"
|
|
194
|
+
|
|
195
|
+
interface Interface {
|
|
196
|
+
type: Tag
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface MEUpgrade extends Interface {
|
|
200
|
+
type: "upgrade"
|
|
201
|
+
upMigrations: UpMigration[]
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface MEDowngrade extends Interface {
|
|
205
|
+
type: "downgrade"
|
|
206
|
+
downMigrations: string[]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface MigrationError extends Interface {
|
|
210
|
+
type: "migrationError"
|
|
211
|
+
mtrError: MTRError
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface UpMigration {
|
|
216
|
+
upName: string
|
|
217
|
+
withDown: boolean
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export type MTRError =
|
|
221
|
+
| MTRError.MTRENoDown
|
|
222
|
+
| MTRError.MTREDifferent
|
|
223
|
+
|
|
224
|
+
export namespace MTRError {
|
|
225
|
+
export type Tag = "noDown" | "different"
|
|
226
|
+
|
|
227
|
+
interface Interface {
|
|
228
|
+
type: Tag
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface MTRENoDown extends Interface {
|
|
232
|
+
type: "noDown"
|
|
233
|
+
dbMigrations: string[]
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface MTREDifferent extends Interface {
|
|
237
|
+
type: "different"
|
|
238
|
+
downMigrations: string[]
|
|
239
|
+
}
|
|
240
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A simple declarative API to run a chat-bot with a single function call.
|
|
3
|
+
* It automates creating and updating of the bot profile, address and bot commands shown in the app UI.
|
|
4
|
+
*/
|
|
5
|
+
export * as bot from "./bot"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* An API to send chat commands and receive chat events to/from chat core.
|
|
9
|
+
* You need to use it in bot event handlers, and for any other use cases.
|
|
10
|
+
*/
|
|
11
|
+
export * as api from "./api"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A low level API to the core library - the same that is used in desktop clients.
|
|
15
|
+
* You are unlikely to ever need to use this module directly.
|
|
16
|
+
*/
|
|
17
|
+
export * as core from "./core"
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Useful functions for chat events and types.
|
|
21
|
+
*/
|
|
22
|
+
export * as util from "./util"
|