178 lines
6.3 KiB
Markdown
Executable file
178 lines
6.3 KiB
Markdown
Executable file
# Storage
|
|
|
|
Storage is a very important aspect of the library,
|
|
which should not be overlooked. It is primarily used to
|
|
handle caching and authorization (you wouldn't want to
|
|
log in every time, right?).
|
|
|
|
## In-memory storage
|
|
|
|
The simplest way to store data is to store it in-memory
|
|
and never persist it anywhere, and this is exactly
|
|
what `MemoryStorage` does.
|
|
|
|
```ts{4}
|
|
import { MemoryStorage } from '@mtcute/core'
|
|
|
|
const tg = new TelegramClient({
|
|
storage: new MemoryStorage()
|
|
})
|
|
```
|
|
|
|
::: warning
|
|
It is highly advised that you use some kind of persisted storage!
|
|
|
|
With in-memory storage, you will need to re-authorize every time
|
|
(assuming you don't use [session strings](#session-strings)),
|
|
and also caching won't work past a single run.
|
|
:::
|
|
|
|
## SQLite storage
|
|
|
|
The preferred storage for a server application is the one using SQLite,
|
|
because it does not require loading the entire thing into memory, and
|
|
is also faster than simply reading/writing a file.
|
|
|
|
mtcute implements sqlite storages in runtime-specific packages,
|
|
using the best libraries available for each runtime:
|
|
- Node.js: [better-sqlite3](https://www.npmjs.com/package/better-sqlite3)
|
|
- Bun: `bun:sqlite`
|
|
- Deno: [@db/sqlite](https://jsr.io/@db/sqlite)
|
|
|
|
```ts{4}
|
|
import { SqliteStorage } from '@mtcute/node' // or '@mtcute/bun' / '@mtcute/deno'
|
|
|
|
const tg = new TelegramClient({
|
|
storage: new SqliteStorage('my-account.session')
|
|
})
|
|
```
|
|
|
|
::: tip
|
|
In runtime-specific packages, SQLite storage is the default,
|
|
and you can simply pass a string with file name instead
|
|
of instantiating `SqliteStorage` manually:
|
|
|
|
```ts
|
|
const tg = new TelegramClient({
|
|
storage: 'my-account.session'
|
|
})
|
|
```
|
|
:::
|
|
|
|
To improve performance, we use WAL mode by default ([Learn more](https://github.com/JoshuaWise/better-sqlite3/blob/master/docs/performance.md)).
|
|
|
|
When using WAL, along with your SQLite file there may also
|
|
be `-shm` and `-wal` files. If you don't like seeing those files,
|
|
instead of disabling WAL altogether, consider putting your storage in a folder
|
|
(i.e. `new SqliteStorage('storage/my-account')`).
|
|
|
|
If you are in fact having problems with WAL mode, you can disable it
|
|
with `disableWal` parameter.
|
|
|
|
|
|
## IndexedDB storage
|
|
|
|
The preferred storage for a Web application is the one using IndexedDB,
|
|
which is basically a browser's version of SQLite.
|
|
|
|
```ts{4}
|
|
import { IdbStorage } from '@mtcute/web'
|
|
|
|
const tg = new TelegramClient({
|
|
storage: new IdbStorage('my-account')
|
|
})
|
|
```
|
|
|
|
> Note that the string passed will be used as-is as the database name,
|
|
> so you might want to prefix it to avoid conflicts.
|
|
|
|
::: tip
|
|
In `@mtcute/web`, IndexedDB storage is the default,
|
|
and you can simply pass a string with file name instead
|
|
of instantiating `IdbStorage` manually:
|
|
|
|
```ts
|
|
const tg = new TelegramClient({
|
|
storage: 'my-account'
|
|
})
|
|
```
|
|
:::
|
|
|
|
|
|
## Session strings
|
|
|
|
Sometimes it might be useful to export storage data to a string, and
|
|
import it later to another storage. For example, when deploying userbot
|
|
applications to a server, where you'll be using another storage.
|
|
|
|
To generate a session string, simply call `exportSession`:
|
|
|
|
```ts
|
|
await tg.start()
|
|
console.log(await tg.exportSession())
|
|
```
|
|
|
|
This will output a fairly long string (about 400 chars) to your console,
|
|
which can then be imported:
|
|
|
|
```ts
|
|
const tg = new TelegramClient({...})
|
|
|
|
await tg.importSession(SESSION_STRING)
|
|
// or
|
|
await tg.start({ session: SESSION_STRING })
|
|
```
|
|
|
|
You can import session into any storage, including in-memory storage.
|
|
This may be useful when deploying to services like [Heroku](https://www.heroku.com),
|
|
where their ephemeral file system makes it impossible to use file-based storage.
|
|
|
|
::: warning
|
|
Anyone with this string will be able to authorize as you and do anything.
|
|
Treat this as your password, and **never give it away**!
|
|
|
|
In case you have accidentally leaked this string, make sure to revoke
|
|
this session in account settings: "Privacy & Security" > "Active sessions" >
|
|
find the one containing "mtcute" > Revoke, or, in case this is a bot,
|
|
revoke bot token with [@BotFather](https://t.me/botfather)
|
|
|
|
Also note that you can't log in with the same session
|
|
string from multiple IPs at once, and that would immediately
|
|
revoke that session.
|
|
:::
|
|
|
|
::: details What is included?
|
|
You might be curious about the information that the session
|
|
string includes, and why is it so long.
|
|
|
|
Most of the string is occupied by 256 bytes long
|
|
MTProto authorization key, which, when Base64 encoded,
|
|
results in **344** characters. Additionally, information
|
|
about user (their ID and whether the user is a bot) and their DC
|
|
is included, which results in an average of ~**400** characters
|
|
:::
|
|
|
|
## Implementing custom storage
|
|
|
|
The easiest way to implement a custom storage would be to make a subclass of `MemoryStorage`.
|
|
|
|
Additionaly, mtcute abstracts away the sqlite storage implementation, so you can use the the `BaseSqliteStorage` API to implement sqlite storage using your library of choice (see [Node.js implementation](https://github.com/mtcute/mtcute/tree/master/packages/node/src/sqlite) for reference).
|
|
|
|
### Architecture
|
|
|
|
A storage provider in mtcute is composed of:
|
|
- **Driver**: the core of the storage, which handles reading and writing data to the storage and implements
|
|
lifecycle methods like `load` and `save`. Driver also manages migrations for the storage, however the migrations
|
|
themselves are not part of the driver, but are registered separately by repositories
|
|
- **Repository**: a set of methods to read and write data of a specific entity to the storage, allowing for
|
|
more efficient and organized access to the data. Repositories are registered in the driver and are used to
|
|
access the data in the storage
|
|
|
|
Such composable architecture allows for custom storages to implement a specific set of repositories, and to reuse the same driver for different providers.
|
|
|
|
In mtcute, these sets of repositories are defined:
|
|
- [IMtStorageProvider](https://ref.mtcute.dev/types/_mtcute_core.index.IMtStorageProvider.html), used by `BaseTelegramClient` for low-level
|
|
MTProto data storage
|
|
- [ITelegramStorageProvider](https://ref.mtcute.dev/interfaces/_mtcute_core.index.ITelegramStorageProvider.html), used by `TelegramClient` for basic caching
|
|
and update handling operations required for the client to work
|
|
- [IStateStorageProvider](https://ref.mtcute.dev/types/_mtcute_dispatcher.IStateStorageProvider.html), used by `Dispatcher` for FSM and Scenes storage
|