simplex-chat 0.3.0 → 6.5.0-beta.10

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.
Files changed (116) hide show
  1. package/README.md +107 -22
  2. package/binding.gyp +50 -0
  3. package/cpp/simplex.cc +426 -0
  4. package/cpp/simplex.h +46 -0
  5. package/dist/api.d.ts +423 -0
  6. package/dist/api.js +857 -0
  7. package/dist/api.js.map +1 -0
  8. package/dist/bot.d.ts +29 -0
  9. package/dist/bot.js +189 -0
  10. package/dist/bot.js.map +1 -0
  11. package/dist/core.d.ts +128 -0
  12. package/dist/core.js +121 -0
  13. package/dist/core.js.map +1 -0
  14. package/dist/index.d.ts +19 -2
  15. package/dist/index.js +20 -4
  16. package/dist/index.js.map +1 -1
  17. package/dist/simplex.d.ts +10 -0
  18. package/dist/simplex.js +1 -0
  19. package/dist/util.d.ts +15 -0
  20. package/dist/util.js +91 -0
  21. package/dist/util.js.map +1 -0
  22. package/docs/Namespace.api.md +33 -0
  23. package/docs/Namespace.bot.md +23 -0
  24. package/docs/Namespace.core.md +48 -0
  25. package/docs/Namespace.util.md +25 -0
  26. package/docs/README.md +12 -0
  27. package/docs/api.Class.ChatApi.md +1752 -0
  28. package/docs/api.Class.ChatCommandError.md +205 -0
  29. package/docs/api.Enumeration.ConnReqType.md +27 -0
  30. package/docs/api.Interface.BotAddressSettings.md +60 -0
  31. package/docs/api.TypeAlias.DbConfig.md +64 -0
  32. package/docs/api.TypeAlias.EventSubscriberFunc.md +27 -0
  33. package/docs/api.TypeAlias.EventSubscribers.md +11 -0
  34. package/docs/api.Variable.defaultBotAddressSettings.md +11 -0
  35. package/docs/bot.Function.run.md +21 -0
  36. package/docs/bot.Interface.BotConfig.md +75 -0
  37. package/docs/bot.Interface.BotOptions.md +81 -0
  38. package/docs/bot.TypeAlias.BotDbOpts.md +17 -0
  39. package/docs/core.Class.ChatAPIError.md +205 -0
  40. package/docs/core.Class.ChatInitError.md +205 -0
  41. package/docs/core.DBMigrationError.Interface.ErrorMigration.md +41 -0
  42. package/docs/core.DBMigrationError.Interface.ErrorNotADatabase.md +33 -0
  43. package/docs/core.DBMigrationError.Interface.ErrorSQL.md +41 -0
  44. package/docs/core.DBMigrationError.Interface.InvalidConfirmation.md +25 -0
  45. package/docs/core.DBMigrationError.TypeAlias.Tag.md +11 -0
  46. package/docs/core.Enumeration.MigrationConfirmation.md +43 -0
  47. package/docs/core.Function.chatCloseStore.md +23 -0
  48. package/docs/core.Function.chatDecryptFile.md +31 -0
  49. package/docs/core.Function.chatEncryptFile.md +31 -0
  50. package/docs/core.Function.chatMigrateInit.md +31 -0
  51. package/docs/core.Function.chatReadFile.md +27 -0
  52. package/docs/core.Function.chatRecvMsgWait.md +27 -0
  53. package/docs/core.Function.chatSendCmd.md +27 -0
  54. package/docs/core.Function.chatWriteFile.md +31 -0
  55. package/docs/core.Interface.APIResult.md +31 -0
  56. package/docs/core.Interface.CryptoArgs.md +27 -0
  57. package/docs/core.Interface.UpMigration.md +25 -0
  58. package/docs/core.MTRError.Interface.MTREDifferent.md +33 -0
  59. package/docs/core.MTRError.Interface.MTRENoDown.md +33 -0
  60. package/docs/core.MTRError.TypeAlias.Tag.md +11 -0
  61. package/docs/core.MigrationError.Interface.MEDowngrade.md +33 -0
  62. package/docs/core.MigrationError.Interface.MEUpgrade.md +33 -0
  63. package/docs/core.MigrationError.Interface.MigrationError.md +33 -0
  64. package/docs/core.MigrationError.TypeAlias.Tag.md +11 -0
  65. package/docs/core.Namespace.DBMigrationError.md +18 -0
  66. package/docs/core.Namespace.MTRError.md +16 -0
  67. package/docs/core.Namespace.MigrationError.md +17 -0
  68. package/docs/core.TypeAlias.DBMigrationError.md +11 -0
  69. package/docs/core.TypeAlias.MTRError.md +11 -0
  70. package/docs/core.TypeAlias.MigrationError.md +11 -0
  71. package/docs/util.Function.botAddressSettings.md +21 -0
  72. package/docs/util.Function.chatInfoName.md +21 -0
  73. package/docs/util.Function.chatInfoRef.md +21 -0
  74. package/docs/util.Function.ciBotCommand.md +21 -0
  75. package/docs/util.Function.ciContentText.md +21 -0
  76. package/docs/util.Function.contactAddressStr.md +21 -0
  77. package/docs/util.Function.fromLocalProfile.md +21 -0
  78. package/docs/util.Function.reactionText.md +21 -0
  79. package/docs/util.Function.senderName.md +25 -0
  80. package/docs/util.Interface.BotCommand.md +25 -0
  81. package/examples/squaring-bot-pg.js +21 -0
  82. package/examples/squaring-bot-readme.js +17 -0
  83. package/examples/squaring-bot.ts +42 -0
  84. package/package.json +36 -48
  85. package/src/api.ts +939 -0
  86. package/src/bot.ts +214 -0
  87. package/src/core.ts +210 -0
  88. package/src/download-libs.js +239 -0
  89. package/src/index.ts +22 -0
  90. package/src/simplex.d.ts +10 -0
  91. package/src/simplex.js +1 -0
  92. package/src/util.ts +92 -0
  93. package/tsconfig.json +23 -0
  94. package/dist/client.d.ts +0 -61
  95. package/dist/client.js +0 -271
  96. package/dist/client.js.map +0 -1
  97. package/dist/command.d.ts +0 -408
  98. package/dist/command.js +0 -184
  99. package/dist/command.js.map +0 -1
  100. package/dist/index-web.d.ts +0 -1
  101. package/dist/index-web.js +0 -6
  102. package/dist/index-web.js.map +0 -1
  103. package/dist/index.bundle.js +0 -11
  104. package/dist/queue.d.ts +0 -24
  105. package/dist/queue.js +0 -77
  106. package/dist/queue.js.map +0 -1
  107. package/dist/response.d.ts +0 -754
  108. package/dist/response.js +0 -21
  109. package/dist/response.js.map +0 -1
  110. package/dist/test.html +0 -7
  111. package/dist/transport.d.ts +0 -54
  112. package/dist/transport.js +0 -131
  113. package/dist/transport.js.map +0 -1
  114. package/dist/websocket.d.ts +0 -8
  115. package/dist/websocket.js +0 -4
  116. package/dist/websocket.js.map +0 -1
package/src/bot.ts ADDED
@@ -0,0 +1,214 @@
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
+ }
10
+
11
+ export interface BotOptions {
12
+ createAddress?: boolean
13
+ updateAddress?: boolean
14
+ updateProfile?: boolean
15
+ addressSettings?: api.BotAddressSettings
16
+ allowFiles?: boolean
17
+ commands?: T.ChatBotCommand[] // commands to show in client UI
18
+ useBotProfile?: boolean // create profile not marked as a bot, with default preferences
19
+ logContacts?: boolean
20
+ logNetwork?: boolean
21
+ }
22
+
23
+ const defaultOpts: Required<BotOptions> = {
24
+ createAddress: true,
25
+ updateAddress: true,
26
+ updateProfile: true,
27
+ addressSettings: api.defaultBotAddressSettings,
28
+ allowFiles: false,
29
+ commands: [],
30
+ useBotProfile: true,
31
+ logContacts: true,
32
+ logNetwork: false
33
+ }
34
+
35
+ export interface BotConfig {
36
+ profile: T.Profile,
37
+ dbOpts: BotDbOpts,
38
+ options: BotOptions,
39
+ onMessage?: (chatItem: T.AChatItem, content: T.MsgContent) => void | Promise<void>,
40
+ // command handlers can be different from commands to be shown in client UI
41
+ onCommands?: {[K in string]?: ((chatItem: T.AChatItem, command: util.BotCommand) => void | Promise<void>)},
42
+ // If you use `onMessage` and to subscribe "newChatItems" event, exclude content messages from processing
43
+ // If you use `onCommands` and to subscribe "newChatItems" event, exclude commands from processing
44
+ events?: api.EventSubscribers
45
+ }
46
+
47
+ export async function run({profile, dbOpts, options = defaultOpts, onMessage, onCommands = {}, events = {}}: BotConfig): Promise<[api.ChatApi, T.User, T.UserContactLink | undefined]> {
48
+ const bot = await api.ChatApi.init(dbOpts, dbOpts.confirmMigrations || core.MigrationConfirmation.YesUp)
49
+ const opts = fullOptions(options)
50
+ if (onMessage) subscribeMessages(bot, onMessage)
51
+ if (Object.keys(onCommands).length > 0) subscribeCommands(bot, onCommands)
52
+ if (Object.keys(events).length > 0) bot.on(events)
53
+ subscribeLogEvents(bot, opts)
54
+ const botProfile = mkBotProfile(profile, opts)
55
+ const user = await createBotUser(bot, botProfile)
56
+ await bot.startChat()
57
+ const address = await createOrUpdateAddress(bot, user, opts)
58
+ if (address) {
59
+ const addressLink = util.contactAddressStr(address.connLinkContact)
60
+ console.log(`Bot address: ${addressLink}`)
61
+ if (opts.useBotProfile) botProfile.contactLink = addressLink
62
+ }
63
+ await updateBotUserProfile(bot, user, botProfile, opts)
64
+ return [bot, user, address]
65
+ }
66
+
67
+ function fullOptions(options: BotOptions): Required<BotOptions> {
68
+ const opts = {
69
+ createAddress: options.createAddress ?? defaultOpts.createAddress,
70
+ updateAddress: options.updateAddress ?? defaultOpts.updateAddress,
71
+ updateProfile: options.updateProfile ?? defaultOpts.updateProfile,
72
+ addressSettings: options.addressSettings ?? defaultOpts.addressSettings,
73
+ allowFiles: options.allowFiles ?? defaultOpts.allowFiles,
74
+ commands: options.commands ?? defaultOpts.commands,
75
+ useBotProfile: options.useBotProfile ?? defaultOpts.useBotProfile,
76
+ logContacts: options.logContacts ?? defaultOpts.logContacts,
77
+ logNetwork: options.logNetwork ?? defaultOpts.logNetwork
78
+ }
79
+ const welcomeMessage = opts.addressSettings.welcomeMessage ?? defaultOpts.addressSettings.welcomeMessage
80
+ opts.addressSettings = {
81
+ autoAccept: opts.addressSettings.autoAccept ?? defaultOpts.addressSettings.autoAccept,
82
+ welcomeMessage: typeof welcomeMessage === "string" ? {type: "text", text: welcomeMessage} : welcomeMessage,
83
+ businessAddress: opts.addressSettings.businessAddress ?? defaultOpts.addressSettings.businessAddress
84
+ }
85
+ return opts
86
+ }
87
+
88
+ function mkBotProfile(profile: T.Profile, opts: Required<BotOptions>): T.Profile {
89
+ if (opts.useBotProfile) {
90
+ const prefs = profile.preferences || {}
91
+ if (prefs.files || prefs.calls || prefs.voice || prefs.commands) {
92
+ console.log("Option useBotProfile is enabled and profile preferences used for files, calls, voice or commands, exiting")
93
+ process.exit()
94
+ }
95
+ prefs.files = {allow: opts.allowFiles ? T.FeatureAllowed.Yes : T.FeatureAllowed.No}
96
+ prefs.calls = {allow: T.FeatureAllowed.No}
97
+ prefs.voice = {allow: T.FeatureAllowed.No}
98
+ prefs.commands = opts.commands
99
+ profile.preferences = prefs
100
+ profile.peerType = T.ChatPeerType.Bot
101
+ } else if (opts.commands.length > 0) {
102
+ console.log("Option useBotProfile is disabled and commands are passed, exiting")
103
+ process.exit()
104
+ }
105
+ return profile
106
+ }
107
+
108
+ function subscribeMessages(bot: api.ChatApi, onMessage: (chatItem: T.AChatItem, content: T.MsgContent) => void | Promise<void>) {
109
+ bot.on("newChatItems", async ({chatItems}) => {
110
+ for (const ci of chatItems) {
111
+ if (ci.chatItem.content.type === "rcvMsgContent") {
112
+ try {
113
+ const p = onMessage(ci, ci.chatItem.content.msgContent)
114
+ if (p instanceof Promise) await p
115
+ } catch (e) {
116
+ console.log("message processing error", e)
117
+ }
118
+ }
119
+ }
120
+ })
121
+ }
122
+
123
+ function subscribeCommands(bot: api.ChatApi, commands: {[K in string]?: ((chatItem: T.AChatItem, command: util.BotCommand) => void | Promise<void>)}) {
124
+ bot.on("newChatItems", async (evt) => {
125
+ for (const ci of evt.chatItems) {
126
+ const cmd = util.ciBotCommand(ci.chatItem)
127
+ if (cmd) {
128
+ const cmdFunc = commands[cmd.keyword] || commands[""]
129
+ if (cmdFunc) {
130
+ try {
131
+ const p = cmdFunc(ci, cmd)
132
+ if (p instanceof Promise) await p
133
+ } catch(e) {
134
+ console.log(`${cmd} command processing error`, e)
135
+ }
136
+ }
137
+ }
138
+ }
139
+ })
140
+ }
141
+
142
+ function subscribeLogEvents(bot: api.ChatApi, opts: Required<BotOptions>) {
143
+ if (opts.logContacts) {
144
+ bot.on({
145
+ "contactConnected": ({contact}) => console.log(`${contact.profile.displayName} connected`),
146
+ "contactDeletedByContact": ({contact}) => console.log(`${contact.profile.displayName} deleted connection with bot`)
147
+ })
148
+ }
149
+ if (opts.logNetwork) {
150
+ bot.on({
151
+ "hostConnected": ({transportHost}) => console.log(`connected server ${transportHost}`),
152
+ "hostDisconnected": ({transportHost}) => console.log(`diconnected server ${transportHost}`),
153
+ "subscriptionStatus": ({subscriptionStatus, connections}) => console.log(`${connections.length} subscription(s) ${subscriptionStatus.type}`)
154
+ })
155
+ }
156
+ }
157
+
158
+ async function createBotUser(bot: api.ChatApi, profile: T.Profile): Promise<T.User> {
159
+ let user = await bot.apiGetActiveUser()
160
+ if (!user) {
161
+ console.log("No active user in database, creating...")
162
+ user = await bot.apiCreateActiveUser(profile)
163
+ }
164
+ console.log("Bot user: ", user.profile.displayName)
165
+ return user
166
+ }
167
+
168
+ async function createOrUpdateAddress(bot: api.ChatApi, user: T.User, opts: Required<BotOptions>): Promise<T.UserContactLink | undefined> {
169
+ const {userId} = user
170
+ let address = await bot.apiGetUserAddress(userId)
171
+ if (!address) {
172
+ if (opts.createAddress) {
173
+ console.log("Bot has no address, creating...")
174
+ await bot.apiCreateUserAddress(userId)
175
+ address = await bot.apiGetUserAddress(userId)
176
+ if (!address) {
177
+ console.log("Failed reading created user address, exiting")
178
+ process.exit()
179
+ }
180
+ } else {
181
+ console.log("Warning: bot has no address")
182
+ return
183
+ }
184
+ }
185
+
186
+ const addressSettings = opts.addressSettings || defaultOpts.addressSettings
187
+ if (!equal(util.botAddressSettings(address), addressSettings)) {
188
+ if (opts.updateAddress) {
189
+ console.log("Bot address settings changed, updating...")
190
+ await bot.apiSetAddressSettings(userId, addressSettings)
191
+ } else {
192
+ console.log("Bot address settings changed")
193
+ }
194
+ }
195
+
196
+ return address
197
+ }
198
+
199
+ async function updateBotUserProfile(bot: api.ChatApi, user: T.User, profile: T.Profile, opts: Required<BotOptions>): Promise<void> {
200
+ const {userId} = user
201
+ if (!equal(util.fromLocalProfile(user.profile), profile)) {
202
+ if (opts.updateProfile) {
203
+ console.log("Bot profile changed, updating...")
204
+ const summary = await bot.apiUpdateProfile(userId, profile)
205
+ console.log(
206
+ summary
207
+ ? `Bot profile updated: ${summary.updateSuccesses} updated contact(s), ${summary.updateFailures} failed contact update(s).`
208
+ : "Unexpected: profile did not change!"
209
+ )
210
+ } else {
211
+ console.log("Bot profile changed")
212
+ }
213
+ }
214
+ }
package/src/core.ts ADDED
@@ -0,0 +1,210 @@
1
+ import {ChatEvent, ChatResponse, T} from "@simplex-chat/types"
2
+ import * as simplex from "./simplex"
3
+
4
+ /**
5
+ * Initialize chat controller
6
+ */
7
+ export async function chatMigrateInit(dbPath: string, dbKey: string, confirm: MigrationConfirmation): Promise<bigint> {
8
+ const [ctrl, res] = await simplex.chat_migrate_init(dbPath, dbKey, confirm)
9
+ const json = JSON.parse(res)
10
+ if (json.type === 'ok') return ctrl
11
+ throw new ChatInitError("Database or migration error (see dbMigrationError property)", json as DBMigrationError)
12
+ }
13
+
14
+ /**
15
+ * Close chat store
16
+ */
17
+ export async function chatCloseStore(ctrl: bigint): Promise<void> {
18
+ const res = await simplex.chat_close_store(ctrl)
19
+ if (res !== "") throw new Error(res)
20
+ }
21
+
22
+ /**
23
+ * Send chat command as string
24
+ */
25
+ export async function chatSendCmd(ctrl: bigint, cmd: string): Promise<ChatResponse> {
26
+ const res = await simplex.chat_send_cmd(ctrl, cmd)
27
+ const json = JSON.parse(res) as APIResult<ChatResponse>
28
+ // console.log(cmd.slice(0, 16), json.result?.type || json.error)
29
+ if (typeof json.result === 'object') return json.result
30
+ if (typeof json.error === 'object') throw new ChatAPIError("Chat command error (see chatError property)", json.error as T.ChatError)
31
+ throw new ChatAPIError("Invalid chat command result")
32
+ }
33
+
34
+ /**
35
+ * Receive chat event
36
+ */
37
+ export async function chatRecvMsgWait(ctrl: bigint, wait: number): Promise<ChatEvent | undefined> {
38
+ const res = await simplex.chat_recv_msg_wait(ctrl, wait)
39
+ if (res === "") return undefined
40
+ const json = JSON.parse(res) as APIResult<ChatEvent>
41
+ // if (json.result) console.log("event", json.result.type)
42
+ if (typeof json.result === 'object') return json.result
43
+ if (typeof json.error === 'object') throw new ChatAPIError("Chat event error (see chatError property)", json.error as T.ChatError)
44
+ throw new ChatAPIError("Invalid chat event")
45
+ }
46
+
47
+ /**
48
+ * Write buffer to encrypted file
49
+ */
50
+ export async function chatWriteFile(ctrl: bigint, path: string, buffer: ArrayBuffer): Promise<CryptoArgs> {
51
+ const res = await simplex.chat_write_file(ctrl, path, buffer)
52
+ return cryptoArgsResult(res)
53
+ }
54
+
55
+ /**
56
+ * Read buffer from encrypted file
57
+ */
58
+ export async function chatReadFile(path: string, {fileKey, fileNonce}: CryptoArgs): Promise<ArrayBuffer> {
59
+ return await simplex.chat_read_file(path, fileKey, fileNonce)
60
+ }
61
+
62
+ /**
63
+ * Encrypt file
64
+ */
65
+ export async function chatEncryptFile(ctrl: bigint, fromPath: string, toPath: string): Promise<CryptoArgs> {
66
+ const res = await simplex.chat_encrypt_file(ctrl, fromPath, toPath)
67
+ return cryptoArgsResult(res)
68
+ }
69
+
70
+ /**
71
+ * Decrypt file
72
+ */
73
+ export async function chatDecryptFile(fromPath: string, {fileKey, fileNonce}: CryptoArgs, toPath: string): Promise<void> {
74
+ const res = await simplex.chat_decrypt_file(fromPath, fileKey, fileNonce, toPath)
75
+ if (res !== "") throw new Error(res)
76
+ }
77
+
78
+ function cryptoArgsResult(res: string): CryptoArgs {
79
+ const json = JSON.parse(res)
80
+ switch (json.type) {
81
+ case "result": return json.cryptoArgs as CryptoArgs
82
+ case "error": throw Error(json.writeError)
83
+ default: throw Error("unexpected chat_write_file result: " + res)
84
+ }
85
+ }
86
+
87
+ export interface APIResult<R> {
88
+ result?: R
89
+ error?: T.ChatError
90
+ }
91
+
92
+ export class ChatAPIError extends Error {
93
+ constructor(public message: string, public chatError: T.ChatError | undefined = undefined) {
94
+ super(message)
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Migration confirmation mode
100
+ */
101
+ export enum MigrationConfirmation {
102
+ YesUp = "yesUp",
103
+ YesUpDown = "yesUpDown",
104
+ Console = "console",
105
+ Error = "error"
106
+ }
107
+
108
+ /**
109
+ * File encryption key and nonce
110
+ */
111
+ export interface CryptoArgs {
112
+ fileKey: string
113
+ fileNonce: string
114
+ }
115
+
116
+ export class ChatInitError extends Error {
117
+ constructor(public message: string, public dbMigrationError: DBMigrationError) {
118
+ super(message)
119
+ }
120
+ }
121
+
122
+ export type DBMigrationError =
123
+ | DBMigrationError.InvalidConfirmation
124
+ | DBMigrationError.ErrorNotADatabase // invalid/corrupt database file or incorrect encryption key
125
+ | DBMigrationError.ErrorMigration
126
+ | DBMigrationError.ErrorSQL
127
+
128
+ export namespace DBMigrationError {
129
+ export type Tag = "invalidConfirmation" | "errorNotADatabase" | "errorMigration" | "errorSQL"
130
+
131
+ interface Interface {
132
+ type: Tag
133
+ }
134
+
135
+ export interface InvalidConfirmation extends Interface {
136
+ type: "invalidConfirmation"
137
+ }
138
+
139
+ export interface ErrorNotADatabase extends Interface {
140
+ type: "errorNotADatabase"
141
+ dbFile: string
142
+ }
143
+
144
+ export interface ErrorMigration extends Interface {
145
+ type: "errorMigration"
146
+ dbFile: string
147
+ migrationError: MigrationError
148
+ }
149
+
150
+ export interface ErrorSQL extends Interface {
151
+ type: "errorSQL"
152
+ dbFile: string
153
+ migrationSQLError: string
154
+ }
155
+ }
156
+
157
+ export type MigrationError =
158
+ | MigrationError.MEUpgrade
159
+ | MigrationError.MEDowngrade
160
+ | MigrationError.MigrationError
161
+
162
+ export namespace MigrationError {
163
+ export type Tag = "upgrade" | "downgrade" | "migrationError"
164
+
165
+ interface Interface {
166
+ type: Tag
167
+ }
168
+
169
+ export interface MEUpgrade extends Interface {
170
+ type: "upgrade"
171
+ upMigrations: UpMigration
172
+ }
173
+
174
+ export interface MEDowngrade extends Interface {
175
+ type: "downgrade"
176
+ downMigrations: string[]
177
+ }
178
+
179
+ export interface MigrationError extends Interface {
180
+ type: "migrationError"
181
+ mtrError: MTRError
182
+ }
183
+ }
184
+
185
+ export interface UpMigration {
186
+ upName: string
187
+ withDown: boolean
188
+ }
189
+
190
+ export type MTRError =
191
+ | MTRError.MTRENoDown
192
+ | MTRError.MTREDifferent
193
+
194
+ export namespace MTRError {
195
+ export type Tag = "noDown" | "different"
196
+
197
+ interface Interface {
198
+ type: Tag
199
+ }
200
+
201
+ export interface MTRENoDown extends Interface {
202
+ type: "noDown"
203
+ upMigrations: UpMigration
204
+ }
205
+
206
+ export interface MTREDifferent extends Interface {
207
+ type: "different"
208
+ downMigrations: string[]
209
+ }
210
+ }
@@ -0,0 +1,239 @@
1
+ const https = require('https');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const extract = require('extract-zip');
5
+
6
+ const GITHUB_REPO = 'simplex-chat/simplex-chat-libs';
7
+ const RELEASE_TAG = 'v6.5.0-beta.10';
8
+ const BACKEND = (process.env.SIMPLEX_BACKEND || process.env.npm_config_simplex_backend || 'sqlite').toLowerCase();
9
+
10
+ if (BACKEND !== 'sqlite' && BACKEND !== 'postgres') {
11
+ console.error(`✗ Invalid SIMPLEX_BACKEND: "${BACKEND}". Must be "sqlite" or "postgres".`);
12
+ process.exit(1);
13
+ }
14
+
15
+ if (BACKEND === 'postgres' && (process.platform !== 'linux' || process.arch !== 'x64')) {
16
+ console.error(`✗ SIMPLEX_BACKEND=postgres is only supported on Linux x86_64.`);
17
+ process.exit(1);
18
+ }
19
+
20
+ const ROOT_DIR = process.cwd(); // Root of the package being installed
21
+ const LIBS_DIR = path.join(ROOT_DIR, 'libs')
22
+ const INSTALLED_FILE = path.join(LIBS_DIR, 'installed.txt');
23
+
24
+ // Detect platform and architecture
25
+ function getPlatformInfo() {
26
+ const platform = process.platform;
27
+ const arch = process.arch;
28
+
29
+ let platformName;
30
+ let archName;
31
+
32
+ if (platform === 'linux') {
33
+ platformName = 'linux';
34
+ } else if (platform === 'darwin') {
35
+ platformName = 'macos';
36
+ } else if (platform === 'win32') {
37
+ platformName = 'windows';
38
+ } else {
39
+ throw new Error(`Unsupported platform: ${platform}`);
40
+ }
41
+
42
+ if (arch === 'x64') {
43
+ archName = 'x86_64';
44
+ } else if (arch === 'arm64') {
45
+ archName = 'aarch64';
46
+ } else {
47
+ throw new Error(`Unsupported architecture: ${arch}`);
48
+ }
49
+
50
+ return { platformName, archName };
51
+ }
52
+
53
+ // Cleanup on libs version mismatch
54
+ function cleanLibsDirectory() {
55
+ if (fs.existsSync(LIBS_DIR)) {
56
+ console.log('Cleaning old libraries...');
57
+ fs.rmSync(LIBS_DIR, { recursive: true, force: true });
58
+ fs.mkdirSync(LIBS_DIR, { recursive: true });
59
+ console.log('✓ Old libraries removed');
60
+ }
61
+ }
62
+
63
+ // Check if libraries are already installed with the correct version
64
+ function isAlreadyInstalled() {
65
+ if (!fs.existsSync(INSTALLED_FILE)) {
66
+ return false;
67
+ }
68
+
69
+ try {
70
+ const installedVersion = fs.readFileSync(INSTALLED_FILE, 'utf-8').trim();
71
+ const expectedVersion = `${RELEASE_TAG}:${BACKEND}`;
72
+ if (installedVersion === expectedVersion) {
73
+ console.log(`✓ Libraries version ${RELEASE_TAG}:${BACKEND} already installed`);
74
+ return true;
75
+ } else {
76
+ console.log(`Version mismatch: installed ${installedVersion}, need ${expectedVersion}`);
77
+ cleanLibsDirectory();
78
+ return false;
79
+ }
80
+ } catch (err) {
81
+ console.warn(`Could not read installed.txt: ${err.message}`);
82
+ return false;
83
+ }
84
+ }
85
+
86
+ async function install() {
87
+ try {
88
+ // Check if already installed
89
+ if (isAlreadyInstalled()) {
90
+ return;
91
+ }
92
+
93
+ const { platformName, archName } = getPlatformInfo();
94
+ const repoName = GITHUB_REPO.split('/')[1];
95
+ const backendSuffix = BACKEND === 'postgres' ? '-postgres' : '';
96
+ const zipFilename = `${repoName}-${platformName}-${archName}${backendSuffix}.zip`;
97
+ const ZIP_URL = `https://github.com/${GITHUB_REPO}/releases/download/${RELEASE_TAG}/${zipFilename}`;
98
+ const ZIP_PATH = path.join(ROOT_DIR, zipFilename);
99
+ const TEMP_EXTRACT_DIR = path.join(ROOT_DIR, '.temp-extract');
100
+
101
+ console.log(`Detected: ${platformName} ${archName}`);
102
+ console.log(`Backend: ${BACKEND}`);
103
+ console.log(`Downloading: ${zipFilename}`);
104
+
105
+ // Create libs directory
106
+ if (!fs.existsSync(LIBS_DIR)) {
107
+ fs.mkdirSync(LIBS_DIR, { recursive: true });
108
+ }
109
+
110
+ // Download zip with error handling
111
+ await downloadFile(ZIP_URL, ZIP_PATH);
112
+
113
+ // Extract to temporary directory
114
+ console.log('Extracting to temporary directory...');
115
+ if (!fs.existsSync(TEMP_EXTRACT_DIR)) {
116
+ fs.mkdirSync(TEMP_EXTRACT_DIR, { recursive: true });
117
+ }
118
+ await extract(ZIP_PATH, { dir: TEMP_EXTRACT_DIR });
119
+
120
+ // Move libs folder contents to final location
121
+ console.log('Moving libraries to libs/...');
122
+ const libsSourcePath = path.join(TEMP_EXTRACT_DIR, 'libs');
123
+
124
+ if (fs.existsSync(libsSourcePath)) {
125
+ // Copy all files from libs folder to LIBS_DIR
126
+ const files = fs.readdirSync(libsSourcePath);
127
+ files.forEach(file => {
128
+ const src = path.join(libsSourcePath, file);
129
+ const dest = path.join(LIBS_DIR, file);
130
+
131
+ if (fs.statSync(src).isDirectory()) {
132
+ copyDirSync(src, dest);
133
+ } else {
134
+ fs.copyFileSync(src, dest);
135
+ }
136
+ });
137
+ } else {
138
+ throw new Error('libs folder not found in zip archive');
139
+ }
140
+
141
+ // Write installed.txt with version
142
+ fs.writeFileSync(INSTALLED_FILE, `${RELEASE_TAG}:${BACKEND}`, 'utf-8');
143
+ console.log(`✓ Wrote version ${RELEASE_TAG}:${BACKEND} to installed.txt`);
144
+
145
+ // Cleanup
146
+ fs.rmSync(TEMP_EXTRACT_DIR, { recursive: true, force: true });
147
+ fs.unlinkSync(ZIP_PATH);
148
+ console.log('✓ Installation complete');
149
+ } catch (err) {
150
+ console.error('✗ Failed:', err.message);
151
+ process.exit(1);
152
+ }
153
+ }
154
+
155
+ // Helper function to recursively copy directories
156
+ function copyDirSync(src, dest) {
157
+ if (!fs.existsSync(dest)) {
158
+ fs.mkdirSync(dest, { recursive: true });
159
+ }
160
+ const files = fs.readdirSync(src);
161
+ files.forEach(file => {
162
+ const srcFile = path.join(src, file);
163
+ const destFile = path.join(dest, file);
164
+ if (fs.statSync(srcFile).isDirectory()) {
165
+ copyDirSync(srcFile, destFile);
166
+ } else {
167
+ fs.copyFileSync(srcFile, destFile);
168
+ }
169
+ });
170
+ }
171
+
172
+ function downloadFile(url, dest) {
173
+ return new Promise((resolve, reject) => {
174
+ const file = fs.createWriteStream(dest);
175
+
176
+ https.get(url, { headers: { 'User-Agent': 'Node.js' } }, (response) => {
177
+ // Handle redirects
178
+ if (response.statusCode === 302 || response.statusCode === 301) {
179
+ file.destroy();
180
+ fs.unlink(dest, () => {});
181
+ return downloadFile(response.headers.location, dest)
182
+ .then(resolve)
183
+ .catch(reject);
184
+ }
185
+
186
+ // Handle 404
187
+ if (response.statusCode === 404) {
188
+ file.destroy();
189
+ fs.unlink(dest, () => {});
190
+ reject(new Error(
191
+ `Release artifact not found (404). Check:\n` +
192
+ ` - Repository exists: ${url.split('/releases')[0]}\n` +
193
+ ` - Release tag exists: ${RELEASE_TAG}\n` +
194
+ ` - Artifact filename is correct`
195
+ ));
196
+ return;
197
+ }
198
+
199
+ // Handle 403
200
+ if (response.statusCode === 403) {
201
+ file.destroy();
202
+ fs.unlink(dest, () => {});
203
+ reject(new Error(
204
+ `Access denied (403). The repository may be private.\n` +
205
+ `Set GITHUB_TOKEN environment variable for private repos.`
206
+ ));
207
+ return;
208
+ }
209
+
210
+ // Handle other HTTP errors
211
+ if (response.statusCode < 200 || response.statusCode >= 300) {
212
+ file.destroy();
213
+ fs.unlink(dest, () => {});
214
+ reject(new Error(
215
+ `HTTP ${response.statusCode}: Failed to download from ${url}`
216
+ ));
217
+ return;
218
+ }
219
+
220
+ response.pipe(file);
221
+
222
+ file.on('finish', () => {
223
+ file.close();
224
+ resolve();
225
+ });
226
+
227
+ file.on('error', (err) => {
228
+ fs.unlink(dest, () => {});
229
+ reject(new Error(`File write error: ${err.message}`));
230
+ });
231
+ }).on('error', (err) => {
232
+ file.destroy();
233
+ fs.unlink(dest, () => {});
234
+ reject(new Error(`Download error: ${err.message}`));
235
+ });
236
+ });
237
+ }
238
+
239
+ install();
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"
@@ -0,0 +1,10 @@
1
+ // These functions are defined in CPP add-on ../cpp/simplex.cc
2
+
3
+ export function chat_migrate_init(dbPath: string, dbKey: string, confirm: string): Promise<[bigint, string]>
4
+ export function chat_close_store(ctrl: bigint): Promise<string>
5
+ export function chat_send_cmd(ctrl: bigint, cmd: string): Promise<string>
6
+ export function chat_recv_msg_wait(ctrl: bigint, wait: number): Promise<string>
7
+ export function chat_write_file(ctrl: bigint, path: string, buffer: ArrayBuffer): Promise<string>
8
+ export function chat_read_file(path: string, key: string, nonce: string): Promise<ArrayBuffer>
9
+ export function chat_encrypt_file(ctrl: bigint, fromPath: string, toPath: string): Promise<string>
10
+ export function chat_decrypt_file(fromPath: string, key: string, nonce: string, toPath: string): Promise<string>
package/src/simplex.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require("../build/Release/simplex")