@muhammedaksam/easiarr 1.4.0 → 1.4.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/dist/index.js +29837 -0
- package/package.json +15 -15
- package/src/VersionInfo.ts +0 -12
- package/src/api/arr-api.ts +0 -521
- package/src/api/auto-setup-types.ts +0 -64
- package/src/api/base-api.ts +0 -216
- package/src/api/bazarr-api.ts +0 -297
- package/src/api/cloudflare-api.ts +0 -493
- package/src/api/custom-format-api.ts +0 -224
- package/src/api/grafana-api.ts +0 -230
- package/src/api/heimdall-api.ts +0 -141
- package/src/api/homarr-api.ts +0 -209
- package/src/api/huntarr-api.ts +0 -457
- package/src/api/index.ts +0 -9
- package/src/api/jellyfin-api.ts +0 -389
- package/src/api/jellyseerr-api.ts +0 -559
- package/src/api/maintainerr-api.ts +0 -461
- package/src/api/naming-config.ts +0 -101
- package/src/api/overseerr-api.ts +0 -392
- package/src/api/plex-api.ts +0 -308
- package/src/api/portainer-api.ts +0 -374
- package/src/api/profilarr-api.ts +0 -391
- package/src/api/prowlarr-api.ts +0 -488
- package/src/api/qbittorrent-api.ts +0 -300
- package/src/api/quality-profile-api.ts +0 -272
- package/src/api/tautulli-api.ts +0 -244
- package/src/api/uptime-kuma-api.ts +0 -343
- package/src/apps/categories.ts +0 -14
- package/src/apps/index.ts +0 -2
- package/src/apps/registry.ts +0 -1275
- package/src/compose/caddy-config.ts +0 -130
- package/src/compose/generator.ts +0 -287
- package/src/compose/index.ts +0 -7
- package/src/compose/templates.ts +0 -78
- package/src/compose/traefik-config.ts +0 -187
- package/src/config/bookmarks-generator.ts +0 -160
- package/src/config/defaults.ts +0 -37
- package/src/config/homepage-config.ts +0 -381
- package/src/config/index.ts +0 -6
- package/src/config/manager.ts +0 -150
- package/src/config/recyclarr-config.ts +0 -181
- package/src/config/schema.ts +0 -310
- package/src/config/slskd-config.ts +0 -86
- package/src/config/soularr-config.ts +0 -105
- package/src/config/trash-quality-definitions.ts +0 -213
- package/src/data/lidarr-custom-formats.ts +0 -127
- package/src/data/trash-profiles.ts +0 -333
- package/src/docker/client.ts +0 -312
- package/src/docker/index.ts +0 -18
- package/src/index.ts +0 -77
- package/src/setup/actions/arr-common.ts +0 -307
- package/src/setup/actions/dashboards.ts +0 -79
- package/src/setup/actions/download-clients.ts +0 -54
- package/src/setup/actions/jellyseerr.ts +0 -331
- package/src/setup/actions/media-servers.ts +0 -68
- package/src/setup/actions/monitoring.ts +0 -120
- package/src/setup/actions/prowlarr.ts +0 -137
- package/src/setup/actions/utilities.ts +0 -490
- package/src/setup/index.ts +0 -78
- package/src/setup/types.ts +0 -144
- package/src/structure/manager.ts +0 -129
- package/src/ui/App.ts +0 -111
- package/src/ui/components/ApplicationSelector.ts +0 -275
- package/src/ui/components/CredentialsForm.ts +0 -180
- package/src/ui/components/DownloadClientForm.ts +0 -114
- package/src/ui/components/FileEditor.ts +0 -91
- package/src/ui/components/FooterHint.ts +0 -119
- package/src/ui/components/PageLayout.ts +0 -160
- package/src/ui/components/UpdateNotification.ts +0 -102
- package/src/ui/index.ts +0 -7
- package/src/ui/screens/AdvancedSettings.ts +0 -188
- package/src/ui/screens/ApiKeyViewer.ts +0 -511
- package/src/ui/screens/AppConfigurator.ts +0 -543
- package/src/ui/screens/AppManager.ts +0 -284
- package/src/ui/screens/ContainerControl.ts +0 -581
- package/src/ui/screens/LogsViewer.ts +0 -478
- package/src/ui/screens/MainMenu.ts +0 -299
- package/src/ui/screens/MonitorDashboard.ts +0 -881
- package/src/ui/screens/SecretsEditor.ts +0 -216
- package/src/ui/screens/SettingsScreen.ts +0 -633
- package/src/ui/screens/index.ts +0 -22
- package/src/ui/screens/setup/BaseAppSetupScreen.ts +0 -375
- package/src/ui/screens/setup/CloudflaredSetup.ts +0 -460
- package/src/ui/screens/setup/FullAutoSetup.ts +0 -766
- package/src/ui/screens/setup/HomepageSetup.ts +0 -165
- package/src/ui/screens/setup/JellyfinSetup.ts +0 -328
- package/src/ui/screens/setup/JellyseerrSetup.ts +0 -242
- package/src/ui/screens/setup/ProwlarrSetup.ts +0 -500
- package/src/ui/screens/setup/QBittorrentSetup.ts +0 -221
- package/src/ui/screens/setup/QuickSetup.ts +0 -1169
- package/src/ui/screens/setup/RecyclarrSetup.ts +0 -270
- package/src/ui/screens/setup/TRaSHProfileSetup.ts +0 -381
- package/src/utils/arch.ts +0 -78
- package/src/utils/browser.ts +0 -27
- package/src/utils/categories.ts +0 -61
- package/src/utils/debug.ts +0 -84
- package/src/utils/env.ts +0 -138
- package/src/utils/index.ts +0 -8
- package/src/utils/logs.ts +0 -119
- package/src/utils/migrations/1765626338_rename_env_variables.ts +0 -102
- package/src/utils/migrations/1765707135_rename_easiarr_status.ts +0 -91
- package/src/utils/migrations/1765732722_remove_cloudflare_dns_api_token.ts +0 -45
- package/src/utils/migrations/1769515650_migrate_to_xdg.ts +0 -114
- package/src/utils/migrations.ts +0 -165
- package/src/utils/password.ts +0 -42
- package/src/utils/paths.ts +0 -33
- package/src/utils/unraid.ts +0 -103
- package/src/utils/update-checker.ts +0 -210
- package/src/utils/url-utils.ts +0 -38
package/src/api/jellyfin-api.ts
DELETED
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Jellyfin API Client
|
|
3
|
-
* Handles setup wizard automation and media library management
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { AutoSetupOptions, AutoSetupResult, IAutoSetupClient } from "./auto-setup-types"
|
|
7
|
-
import { debugLog } from "~/utils/debug"
|
|
8
|
-
import { BaseApiClient } from "./base-api"
|
|
9
|
-
|
|
10
|
-
// ==========================================
|
|
11
|
-
// Startup Wizard Types
|
|
12
|
-
// ==========================================
|
|
13
|
-
|
|
14
|
-
export interface StartupConfiguration {
|
|
15
|
-
UICulture?: string
|
|
16
|
-
MetadataCountryCode?: string
|
|
17
|
-
PreferredMetadataLanguage?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface StartupUser {
|
|
21
|
-
Name: string
|
|
22
|
-
Password: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface StartupRemoteAccess {
|
|
26
|
-
EnableRemoteAccess: boolean
|
|
27
|
-
EnableAutomaticPortMapping: boolean
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ==========================================
|
|
31
|
-
// Library Types
|
|
32
|
-
// ==========================================
|
|
33
|
-
|
|
34
|
-
export interface VirtualFolderInfo {
|
|
35
|
-
Name: string
|
|
36
|
-
Locations: string[]
|
|
37
|
-
CollectionType: LibraryType
|
|
38
|
-
ItemId?: string
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type LibraryType =
|
|
42
|
-
| "movies"
|
|
43
|
-
| "tvshows"
|
|
44
|
-
| "music"
|
|
45
|
-
| "books"
|
|
46
|
-
| "homevideos"
|
|
47
|
-
| "musicvideos"
|
|
48
|
-
| "photos"
|
|
49
|
-
| "playlists"
|
|
50
|
-
| "boxsets"
|
|
51
|
-
|
|
52
|
-
export interface AddVirtualFolderOptions {
|
|
53
|
-
name: string
|
|
54
|
-
collectionType: LibraryType
|
|
55
|
-
paths: string[]
|
|
56
|
-
refreshLibrary?: boolean
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ==========================================
|
|
60
|
-
// System Types
|
|
61
|
-
// ==========================================
|
|
62
|
-
|
|
63
|
-
export interface SystemInfo {
|
|
64
|
-
ServerName: string
|
|
65
|
-
Version: string
|
|
66
|
-
Id: string
|
|
67
|
-
OperatingSystem: string
|
|
68
|
-
StartupWizardCompleted: boolean
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ==========================================
|
|
72
|
-
// User Types
|
|
73
|
-
// ==========================================
|
|
74
|
-
|
|
75
|
-
export interface UserPolicy {
|
|
76
|
-
IsAdministrator: boolean
|
|
77
|
-
IsHidden: boolean
|
|
78
|
-
IsDisabled: boolean
|
|
79
|
-
EnableRemoteAccess: boolean
|
|
80
|
-
AuthenticationProviderId?: string
|
|
81
|
-
PasswordResetProviderId?: string
|
|
82
|
-
[key: string]: unknown
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface UserDto {
|
|
86
|
-
Id: string
|
|
87
|
-
Name?: string
|
|
88
|
-
ServerId?: string
|
|
89
|
-
HasPassword: boolean
|
|
90
|
-
LastLoginDate?: string
|
|
91
|
-
Policy?: UserPolicy
|
|
92
|
-
[key: string]: unknown
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export interface AuthResult {
|
|
96
|
-
AccessToken: string
|
|
97
|
-
ServerId: string
|
|
98
|
-
User: UserDto
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ==========================================
|
|
102
|
-
// Jellyfin Client
|
|
103
|
-
// ==========================================
|
|
104
|
-
|
|
105
|
-
export class JellyfinClient extends BaseApiClient implements IAutoSetupClient {
|
|
106
|
-
protected readonly logPrefix = "JellyfinAPI"
|
|
107
|
-
private accessToken?: string
|
|
108
|
-
|
|
109
|
-
constructor(host: string, port: number, accessToken?: string) {
|
|
110
|
-
super(host, port)
|
|
111
|
-
this.accessToken = accessToken
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Jellyfin requires special authorization header
|
|
116
|
-
*/
|
|
117
|
-
private getAuthHeader(): string {
|
|
118
|
-
return (
|
|
119
|
-
'MediaBrowser Client="easiarr", Device="Server", DeviceId="easiarr-setup", Version="1.0.0"' +
|
|
120
|
-
(this.accessToken ? `, Token="${this.accessToken}"` : "")
|
|
121
|
-
)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Make a request to Jellyfin API
|
|
126
|
-
*/
|
|
127
|
-
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
|
128
|
-
const url = `${this.baseUrl}${endpoint}`
|
|
129
|
-
const headers: Record<string, string> = {
|
|
130
|
-
"Content-Type": "application/json",
|
|
131
|
-
"X-Emby-Authorization": this.getAuthHeader(),
|
|
132
|
-
...((options.headers as Record<string, string>) || {}),
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
debugLog("JellyfinAPI", `${options.method || "GET"} ${url}`)
|
|
136
|
-
|
|
137
|
-
const response = await fetch(url, { ...options, headers })
|
|
138
|
-
const text = await response.text()
|
|
139
|
-
|
|
140
|
-
if (!response.ok) {
|
|
141
|
-
throw new Error(`Jellyfin API request failed: ${response.status} ${response.statusText}`)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (!text) return {} as T
|
|
145
|
-
return JSON.parse(text) as T
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ==========================================
|
|
149
|
-
// User Management
|
|
150
|
-
// ==========================================
|
|
151
|
-
|
|
152
|
-
async getUser(userId: string): Promise<UserDto> {
|
|
153
|
-
return this.request<UserDto>(`/Users/${userId}`)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async updateUserPolicy(userId: string, policy: Partial<UserPolicy>): Promise<void> {
|
|
157
|
-
await this.request(`/Users/${userId}/Policy`, {
|
|
158
|
-
method: "POST",
|
|
159
|
-
body: JSON.stringify(policy),
|
|
160
|
-
})
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ==========================================
|
|
164
|
-
// Setup Wizard Methods
|
|
165
|
-
// ==========================================
|
|
166
|
-
|
|
167
|
-
async isStartupComplete(): Promise<boolean> {
|
|
168
|
-
try {
|
|
169
|
-
const info = await this.request<SystemInfo>("/System/Info/Public")
|
|
170
|
-
return info.StartupWizardCompleted === true
|
|
171
|
-
} catch {
|
|
172
|
-
return false
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async getStartupConfiguration(): Promise<StartupConfiguration> {
|
|
177
|
-
return this.request<StartupConfiguration>("/Startup/Configuration")
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async setStartupConfiguration(config: StartupConfiguration): Promise<void> {
|
|
181
|
-
await this.request("/Startup/Configuration", {
|
|
182
|
-
method: "POST",
|
|
183
|
-
body: JSON.stringify(config),
|
|
184
|
-
})
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async getFirstUser(): Promise<{ Name: string; Password: string }> {
|
|
188
|
-
return this.request<{ Name: string; Password: string }>("/Startup/FirstUser")
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async createAdminUser(name: string, password: string): Promise<void> {
|
|
192
|
-
await this.getFirstUser()
|
|
193
|
-
const user: StartupUser = { Name: name, Password: password }
|
|
194
|
-
await this.request("/Startup/User", {
|
|
195
|
-
method: "POST",
|
|
196
|
-
body: JSON.stringify(user),
|
|
197
|
-
})
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async setRemoteAccess(enableRemote: boolean, enableUPnP: boolean = false): Promise<void> {
|
|
201
|
-
const config: StartupRemoteAccess = {
|
|
202
|
-
EnableRemoteAccess: enableRemote,
|
|
203
|
-
EnableAutomaticPortMapping: enableUPnP,
|
|
204
|
-
}
|
|
205
|
-
await this.request("/Startup/RemoteAccess", {
|
|
206
|
-
method: "POST",
|
|
207
|
-
body: JSON.stringify(config),
|
|
208
|
-
})
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async completeStartup(): Promise<void> {
|
|
212
|
-
await this.request("/Startup/Complete", { method: "POST" })
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async runSetupWizard(
|
|
216
|
-
adminName: string,
|
|
217
|
-
adminPassword: string,
|
|
218
|
-
options: {
|
|
219
|
-
uiCulture?: string
|
|
220
|
-
metadataCountry?: string
|
|
221
|
-
metadataLanguage?: string
|
|
222
|
-
enableRemoteAccess?: boolean
|
|
223
|
-
enableUPnP?: boolean
|
|
224
|
-
} = {}
|
|
225
|
-
): Promise<void> {
|
|
226
|
-
const {
|
|
227
|
-
uiCulture = "en-US",
|
|
228
|
-
metadataCountry = "US",
|
|
229
|
-
metadataLanguage = "en",
|
|
230
|
-
enableRemoteAccess = true,
|
|
231
|
-
enableUPnP = false,
|
|
232
|
-
} = options
|
|
233
|
-
|
|
234
|
-
await this.setStartupConfiguration({
|
|
235
|
-
UICulture: uiCulture,
|
|
236
|
-
MetadataCountryCode: metadataCountry,
|
|
237
|
-
PreferredMetadataLanguage: metadataLanguage,
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
await this.createAdminUser(adminName, adminPassword)
|
|
241
|
-
await this.setRemoteAccess(enableRemoteAccess, enableUPnP)
|
|
242
|
-
await this.completeStartup()
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// ==========================================
|
|
246
|
-
// Authentication
|
|
247
|
-
// ==========================================
|
|
248
|
-
|
|
249
|
-
async authenticate(username: string, password: string): Promise<AuthResult> {
|
|
250
|
-
const result = await this.request<AuthResult>("/Users/AuthenticateByName", {
|
|
251
|
-
method: "POST",
|
|
252
|
-
body: JSON.stringify({ Username: username, Pw: password }),
|
|
253
|
-
})
|
|
254
|
-
this.accessToken = result.AccessToken
|
|
255
|
-
return result
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
setAccessToken(token: string): void {
|
|
259
|
-
this.accessToken = token
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// ==========================================
|
|
263
|
-
// Library Management
|
|
264
|
-
// ==========================================
|
|
265
|
-
|
|
266
|
-
async getVirtualFolders(): Promise<VirtualFolderInfo[]> {
|
|
267
|
-
return this.request<VirtualFolderInfo[]>("/Library/VirtualFolders")
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
async addVirtualFolder(options: AddVirtualFolderOptions): Promise<void> {
|
|
271
|
-
const params = new URLSearchParams({
|
|
272
|
-
name: options.name,
|
|
273
|
-
collectionType: options.collectionType,
|
|
274
|
-
refreshLibrary: String(options.refreshLibrary ?? true),
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
await this.request(`/Library/VirtualFolders?${params.toString()}`, {
|
|
278
|
-
method: "POST",
|
|
279
|
-
body: JSON.stringify({
|
|
280
|
-
LibraryOptions: {
|
|
281
|
-
PathInfos: options.paths.map((path) => ({ Path: path })),
|
|
282
|
-
},
|
|
283
|
-
}),
|
|
284
|
-
})
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async addDefaultLibraries(): Promise<void> {
|
|
288
|
-
const defaultLibraries: AddVirtualFolderOptions[] = [
|
|
289
|
-
{ name: "Movies", collectionType: "movies", paths: ["/data/media/movies"] },
|
|
290
|
-
{ name: "TV Shows", collectionType: "tvshows", paths: ["/data/media/tv"] },
|
|
291
|
-
{ name: "Music", collectionType: "music", paths: ["/data/media/music"] },
|
|
292
|
-
]
|
|
293
|
-
|
|
294
|
-
for (const lib of defaultLibraries) {
|
|
295
|
-
try {
|
|
296
|
-
await this.addVirtualFolder(lib)
|
|
297
|
-
} catch (error) {
|
|
298
|
-
debugLog("JellyfinAPI", `Failed to add library ${lib.name}: ${error}`)
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// ==========================================
|
|
304
|
-
// API Key Management
|
|
305
|
-
// ==========================================
|
|
306
|
-
|
|
307
|
-
async createApiKey(appName: string): Promise<string> {
|
|
308
|
-
await this.request(`/Auth/Keys?app=${encodeURIComponent(appName)}`, { method: "POST" })
|
|
309
|
-
|
|
310
|
-
const keys = await this.getApiKeys()
|
|
311
|
-
const key = keys.find((k) => k.AppName === appName)
|
|
312
|
-
return key?.AccessToken || ""
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
async getApiKeys(): Promise<{ AccessToken: string; AppName: string; DateCreated: string }[]> {
|
|
316
|
-
const result = await this.request<{
|
|
317
|
-
Items: { AccessToken: string; AppName: string; DateCreated: string }[]
|
|
318
|
-
}>("/Auth/Keys")
|
|
319
|
-
return result.Items || []
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// ==========================================
|
|
323
|
-
// Health Check
|
|
324
|
-
// ==========================================
|
|
325
|
-
|
|
326
|
-
async isHealthy(): Promise<boolean> {
|
|
327
|
-
try {
|
|
328
|
-
await this.request<SystemInfo>("/System/Info/Public")
|
|
329
|
-
return true
|
|
330
|
-
} catch {
|
|
331
|
-
return false
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
async getPublicSystemInfo(): Promise<SystemInfo> {
|
|
336
|
-
return this.request<SystemInfo>("/System/Info/Public")
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
async isInitialized(): Promise<boolean> {
|
|
340
|
-
return this.isStartupComplete()
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// ==========================================
|
|
344
|
-
// Auto-Setup
|
|
345
|
-
// ==========================================
|
|
346
|
-
|
|
347
|
-
async setup(options: AutoSetupOptions): Promise<AutoSetupResult> {
|
|
348
|
-
const { username, password } = options
|
|
349
|
-
|
|
350
|
-
try {
|
|
351
|
-
const healthy = await this.isHealthy()
|
|
352
|
-
if (!healthy) {
|
|
353
|
-
return { success: false, message: "Jellyfin not reachable" }
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const initialized = await this.isStartupComplete()
|
|
357
|
-
if (initialized) {
|
|
358
|
-
return {
|
|
359
|
-
success: true,
|
|
360
|
-
message: "Already configured",
|
|
361
|
-
data: { alreadyInitialized: true },
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
await this.runSetupWizard(username, password)
|
|
366
|
-
const authResult = await this.authenticate(username, password)
|
|
367
|
-
|
|
368
|
-
let apiKey: string | undefined
|
|
369
|
-
try {
|
|
370
|
-
apiKey = await this.createApiKey("easiarr")
|
|
371
|
-
} catch {
|
|
372
|
-
// API key creation may fail
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return {
|
|
376
|
-
success: true,
|
|
377
|
-
message: "Setup wizard completed",
|
|
378
|
-
data: {
|
|
379
|
-
accessToken: authResult.AccessToken,
|
|
380
|
-
serverId: authResult.ServerId,
|
|
381
|
-
apiKey,
|
|
382
|
-
},
|
|
383
|
-
envUpdates: apiKey ? { API_KEY_JELLYFIN: apiKey } : undefined,
|
|
384
|
-
}
|
|
385
|
-
} catch (error) {
|
|
386
|
-
return { success: false, message: `${error}` }
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|