@silexlabs/silex 3.0.0-alpha.39 → 3.0.0-alpha.41

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 (110) hide show
  1. package/.silex.js +38 -8
  2. package/dist/client/client/config.d.ts +11 -41
  3. package/dist/client/client/grapesjs/LoginDialog.d.ts +13 -0
  4. package/dist/client/client/grapesjs/PublicationManager.d.ts +69 -0
  5. package/dist/client/client/grapesjs/PublicationUi.d.ts +58 -0
  6. package/dist/client/client/grapesjs/index.d.ts +18 -44
  7. package/dist/client/client/grapesjs/storage.d.ts +1 -0
  8. package/dist/client/client/services.d.ts +35 -0
  9. package/dist/client/constants.d.ts +17 -0
  10. package/dist/client/css/admin.css +86 -3
  11. package/dist/client/js/main.js +201 -90
  12. package/dist/client/js/main.js.LICENSE.txt +6 -0
  13. package/dist/client/types.d.ts +200 -29
  14. package/dist/constants.d.ts +21 -0
  15. package/dist/constants.js +38 -1
  16. package/dist/constants.js.map +1 -0
  17. package/dist/events.d.ts +2 -0
  18. package/dist/events.js +17 -0
  19. package/dist/events.js.map +1 -0
  20. package/dist/plugins/AuthPlugin.d.ts +1 -0
  21. package/dist/plugins/AuthPlugin.js +38 -0
  22. package/dist/plugins/AuthPlugin.js.map +1 -0
  23. package/dist/plugins/Cache.d.ts +2 -0
  24. package/dist/plugins/Cache.js +32 -0
  25. package/dist/plugins/Cache.js.map +1 -0
  26. package/dist/plugins/FtpBackend.d.ts +92 -0
  27. package/dist/plugins/FtpBackend.js +604 -0
  28. package/dist/plugins/FtpBackend.js.map +1 -0
  29. package/dist/plugins/HooksPlugin.d.ts +7 -0
  30. package/dist/plugins/HooksPlugin.js +76 -0
  31. package/dist/plugins/HooksPlugin.js.map +1 -0
  32. package/dist/plugins/PublicationPlugin.d.ts +17 -0
  33. package/dist/plugins/PublicationPlugin.js +170 -0
  34. package/dist/plugins/PublicationPlugin.js.map +1 -0
  35. package/dist/plugins/SslPlugin.d.ts +1 -0
  36. package/dist/plugins/SslPlugin.js +86 -0
  37. package/dist/plugins/SslPlugin.js.map +1 -0
  38. package/dist/plugins/StaticPlugin.d.ts +1 -0
  39. package/dist/plugins/StaticPlugin.js +66 -0
  40. package/dist/plugins/StaticPlugin.js.map +1 -0
  41. package/dist/plugins/WebsitePlugin.d.ts +14 -0
  42. package/dist/plugins/WebsitePlugin.js +302 -0
  43. package/dist/plugins/WebsitePlugin.js.map +1 -0
  44. package/dist/server/backends/FsBackend.d.ts +42 -0
  45. package/dist/server/backends/FsBackend.js +269 -0
  46. package/dist/server/backends/FsBackend.js.map +1 -0
  47. package/dist/server/backends/index.d.ts +95 -0
  48. package/dist/server/backends/index.js +255 -0
  49. package/dist/server/backends/index.js.map +1 -0
  50. package/dist/server/cli.js +0 -1
  51. package/dist/server/config.d.ts +58 -0
  52. package/dist/server/config.js +128 -24
  53. package/dist/server/config.js.map +1 -0
  54. package/dist/server/express.js +31 -3
  55. package/dist/server/index.js +9 -20
  56. package/dist/server/jobs.d.ts +6 -0
  57. package/dist/server/jobs.js +75 -0
  58. package/dist/server/jobs.js.map +1 -0
  59. package/dist/server/utils/validation.d.ts +7 -0
  60. package/dist/server/utils/validation.js +37 -0
  61. package/dist/server/utils/validation.js.map +1 -0
  62. package/dist/types.d.ts +270 -0
  63. package/dist/types.js +35 -28
  64. package/dist/types.js.map +1 -0
  65. package/package.json +19 -8
  66. package/public/silex.js +17 -0
  67. package/src/css/dialog.scss +9 -2
  68. package/src/css/grapesjs.scss +79 -0
  69. package/src/css/project-bar.scss +2 -2
  70. package/src/example-client-config.js +17 -0
  71. package/src/html/templates/includes.pug +0 -1
  72. package/src/ts/client/config.ts +7 -6
  73. package/src/ts/client/grapesjs/LoginDialog.ts +190 -0
  74. package/src/ts/client/grapesjs/PublicationManager.ts +333 -0
  75. package/src/ts/client/grapesjs/PublicationUi.ts +320 -0
  76. package/src/ts/client/grapesjs/index.ts +88 -85
  77. package/src/ts/client/grapesjs/storage.ts +110 -0
  78. package/src/ts/client/index.ts +5 -7
  79. package/src/ts/client/services.ts +129 -0
  80. package/src/ts/constants.ts +21 -0
  81. package/src/ts/plugins/AuthPlugin.ts +38 -0
  82. package/src/ts/plugins/Cache.ts +28 -0
  83. package/src/ts/plugins/FtpBackend.ts +689 -0
  84. package/src/ts/plugins/HooksPlugin.ts +79 -0
  85. package/src/ts/plugins/PublicationPlugin.ts +170 -0
  86. package/src/{plugins/SslPlugin.js → ts/plugins/SslPlugin.ts} +24 -6
  87. package/src/ts/plugins/StaticPlugin.ts +68 -0
  88. package/src/ts/plugins/WebsitePlugin.ts +318 -0
  89. package/src/ts/server/backends/FsBackend.ts +266 -0
  90. package/src/ts/server/backends/index.ts +309 -0
  91. package/src/ts/server/cli.ts +0 -2
  92. package/src/ts/server/config.ts +103 -33
  93. package/src/ts/server/express.ts +37 -3
  94. package/src/ts/server/index.ts +24 -14
  95. package/src/ts/server/jobs.test.ts +74 -0
  96. package/src/ts/server/jobs.ts +77 -0
  97. package/src/ts/server/utils/validation.ts +33 -0
  98. package/src/ts/types.ts +167 -59
  99. package/dist/client/client/grapesjs/publish.d.ts +0 -18
  100. package/dist/page.js +0 -15
  101. package/src/plugins/AuthPlugin.js +0 -21
  102. package/src/plugins/Cache.js +0 -11
  103. package/src/plugins/DefaultBackend.js +0 -149
  104. package/src/plugins/ExpressPlugin.js +0 -39
  105. package/src/plugins/HooksPlugin.js +0 -58
  106. package/src/plugins/PublishPlugin.js +0 -69
  107. package/src/plugins/StaticPlugin.js +0 -48
  108. package/src/plugins/WebsitePlugin.js +0 -207
  109. package/src/ts/client/grapesjs/publish.ts +0 -403
  110. /package/.silex/default/{.silex.data.json → website.json} +0 -0
@@ -0,0 +1,266 @@
1
+ /*
2
+ * Silex website builder, free/libre no-code tool for makers.
3
+ * Copyright (c) 2023 lexoyo and Silex Labs foundation
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import fs, { stat } from 'fs/promises'
19
+ import { createWriteStream, mkdir } from 'fs'
20
+ import fsExtra from 'fs-extra'
21
+ import { File, StorageProvider, HostingProvider, StatusCallback, BackendSession} from '.'
22
+ import { join } from 'path'
23
+ import { BackendData, BackendUser, WebsiteMeta, FileMeta, JobData, JobId, JobStatus, WebsiteId, BackendType, PublicationJobData } from '../../types'
24
+ import { jobError, jobSuccess, startJob } from '../jobs'
25
+ import { userInfo } from 'os'
26
+ import { WEBSITE_DATA_FILE_NAME } from '../../constants'
27
+ import { requiredParam } from '../utils/validation'
28
+ import { ServerConfig } from '../config'
29
+
30
+ type FsSession = BackendSession
31
+
32
+ interface FsOptions {
33
+ rootPath?: string
34
+ type: BackendType
35
+ }
36
+
37
+ export class FsBackend implements StorageProvider<FsSession>, HostingProvider<FsSession> {
38
+ id = 'fs'
39
+ displayName = 'File system'
40
+ icon = 'file'
41
+ rootPath: string
42
+ disableLogout = true
43
+ options: { rootPath: string, type: BackendType }
44
+ type: BackendType
45
+
46
+ constructor(config: ServerConfig | null, opts: FsOptions) {
47
+ this.options = {
48
+ rootPath: __dirname + '/../../../.silex',
49
+ ...opts,
50
+ }
51
+ this.type = this.options.type
52
+ }
53
+
54
+ // ********************
55
+ // Utils methods
56
+ // ********************
57
+ updateStatus(filesStatuses, status, statusCbk) {
58
+ statusCbk && statusCbk({
59
+ message: `<p>Writing files:<ul><li>${filesStatuses.map(({file, message}) => `${file.path}: ${message}`).join('</li><li>')}</li></ul></p>`,
60
+ status,
61
+ })
62
+ }
63
+ initStatus(files) {
64
+ return files.map(file => ({
65
+ file,
66
+ message: 'Waiting',
67
+ status: JobStatus.IN_PROGRESS,
68
+ }))
69
+ }
70
+
71
+ // ********************
72
+ // Backend interface
73
+ // ********************
74
+ async getAdminUrl(session: FsSession, id: WebsiteId): Promise<string> {
75
+ return ''
76
+ }
77
+
78
+ async getAuthorizeURL(session: FsSession): Promise<string> {
79
+ return ''
80
+ }
81
+
82
+ async isLoggedIn(session: FsSession): Promise<boolean> {
83
+ return true
84
+ }
85
+
86
+ async login(session: FsSession): Promise<void> {}
87
+
88
+ async logout(session: FsSession): Promise<void> {}
89
+
90
+ async getUserData(session: FsSession): Promise<BackendUser> {
91
+ const { username, } = userInfo()
92
+ return {
93
+ name: username,
94
+ picture: this.icon,
95
+ }
96
+ }
97
+
98
+ async getSiteMeta(session: FsSession, id: string): Promise<WebsiteMeta> {
99
+ const websiteId = requiredParam<WebsiteId>(id, 'website id')
100
+ const files = await this.listDir(session, websiteId, '/')
101
+ const file = files.find(f => f.name === WEBSITE_DATA_FILE_NAME)
102
+ if(!file) throw new Error('Website not found')
103
+ return {
104
+ websiteId,
105
+ // url: await this.getFileUrl(session, websiteId, WEBSITE_DATA_FILE_NAME),
106
+ //metadata: {
107
+ // path: session.ftp.path,
108
+ //},
109
+ ...file,
110
+ }
111
+ }
112
+
113
+ async init(session: FsSession, id: WebsiteId): Promise<void> {
114
+ await fs.mkdir(join(this.options.rootPath, id), { recursive: true })
115
+ }
116
+
117
+ // ********************
118
+ // Storage interface
119
+ // ********************
120
+ async listWebsites(session: any): Promise<WebsiteMeta[]> {
121
+ const list = await fs.readdir(this.options.rootPath)
122
+ return Promise.all(list.map(async fileName => {
123
+ const filePath = join(this.options.rootPath, fileName)
124
+ const fileStat = await stat(filePath)
125
+ return {
126
+ websiteId: fileName,
127
+ createdAt: fileStat.birthtime,
128
+ updatedAt: fileStat.mtime,
129
+ metadata: {
130
+ ...fileStat,
131
+ },
132
+ } as WebsiteMeta
133
+ }))
134
+ }
135
+
136
+ async readFile(session: FsSession, id: WebsiteId, path: string): Promise<File> {
137
+ const fullPath = join(this.options.rootPath, id, path)
138
+ const content = await fs.readFile(fullPath)
139
+ console.log('read file', path, fullPath, content.length)
140
+ return { path, content }
141
+ }
142
+
143
+ async writeFiles(session: FsSession, id: WebsiteId, files: File[], statusCbk?: StatusCallback): Promise<string[]> {
144
+ const filesStatuses = this.initStatus(files)
145
+ let error = false
146
+ const filePaths = [] as string[]
147
+ for (const fileStatus of filesStatuses) {
148
+ const {file} = fileStatus
149
+ const path = join(this.options.rootPath, id, file.path)
150
+ filePaths.push(path)
151
+ if (typeof file.content === 'string') {
152
+ fileStatus.message = 'Writing'
153
+ this.updateStatus(filesStatuses, JobStatus.IN_PROGRESS, statusCbk)
154
+ try {
155
+ console.log('write file', path, file.content.length)
156
+ await fs.writeFile(path, file.content)
157
+ } catch(err) {
158
+ fileStatus.message = `Error (${err})`
159
+ console.log('write file stream', path, file.content.length)
160
+ this.updateStatus(filesStatuses, JobStatus.IN_PROGRESS, statusCbk)
161
+ error = true
162
+ continue
163
+ }
164
+ fileStatus.message = 'Success'
165
+ this.updateStatus(filesStatuses, JobStatus.IN_PROGRESS, statusCbk)
166
+ } else {
167
+ fileStatus.message = 'Writing'
168
+ this.updateStatus(filesStatuses, JobStatus.IN_PROGRESS, statusCbk)
169
+ const writeStream = createWriteStream(path)
170
+ file.content.pipe(writeStream)
171
+ await new Promise((resolve) => {
172
+ writeStream.on('finish', () => {
173
+ fileStatus.message = 'Success'
174
+ this.updateStatus(filesStatuses, JobStatus.IN_PROGRESS, statusCbk)
175
+ resolve(file)
176
+ })
177
+ writeStream.on('error', err => {
178
+ console.error('writeStream error', err)
179
+ fileStatus.message = `Error (${err})`
180
+ this.updateStatus(filesStatuses, JobStatus.IN_PROGRESS, statusCbk)
181
+ error = true
182
+ resolve(file)
183
+ })
184
+ })
185
+ }
186
+ }
187
+ this.updateStatus(filesStatuses, error ? JobStatus.ERROR : JobStatus.SUCCESS, statusCbk)
188
+ return filePaths
189
+ }
190
+
191
+ async deleteFiles(session: FsSession, id: WebsiteId, paths: string[]): Promise<void> {
192
+ for (const path of paths) {
193
+ await fs.unlink(join(this.options.rootPath, id, path))
194
+ }
195
+ }
196
+
197
+ async listDir(session: FsSession, id: WebsiteId, path: string): Promise<FileMeta[]> {
198
+ const list = await fs.readdir(join(this.options.rootPath, id, path))
199
+ return Promise.all(list.map(async fileName => {
200
+ const filePath = join(this.options.rootPath, id, path, fileName)
201
+ const fileStat = await stat(filePath)
202
+ return {
203
+ name: fileName,
204
+ size: fileStat.size,
205
+ isDir: fileStat.isDirectory(),
206
+ createdAt: fileStat.birthtime,
207
+ updatedAt: fileStat.mtime,
208
+ }
209
+ }))
210
+ }
211
+
212
+ // async listDir(session: FsSession, id: string, path: string): Promise<FileMeta[]> {
213
+ // const fileNames = await fs.readdir(this.options.rootPath)
214
+ // // Filter directories only
215
+ // // That's a way to do an async filter
216
+ // const withType = await Promise.all(fileNames.map(async fileName => ({
217
+ // fileName,
218
+ // isDir: (await stat(join(this.options.rootPath, fileName))).isDirectory(),
219
+ // })))
220
+ // return withType
221
+ // .filter(({fileName, isDir}) => isDir)
222
+ // .map(({fileName}) => fileName as WebsiteId)
223
+ // }
224
+
225
+ async createDir(session: FsSession, id: WebsiteId, path: string): Promise<void> {
226
+ await fs.mkdir(join(this.options.rootPath, id, path), { recursive: true })
227
+ }
228
+
229
+ async deleteDir(session: FsSession, id: WebsiteId, path: string): Promise<void> {
230
+ await fsExtra.remove(join(this.options.rootPath, id, path))
231
+ }
232
+
233
+ async getFileUrl(session: FsSession, id: WebsiteId, path: string): Promise<string> {
234
+ const filePath = join(this.options.rootPath, id, path)
235
+ const fileUrl = new URL(filePath, 'file://')
236
+ return fileUrl.toString()
237
+ }
238
+
239
+ // ********************
240
+ // Hosting provider interface
241
+ // ********************
242
+ async publish(session: FsSession, id: WebsiteId, backendData: BackendData, files: File[]): Promise<JobData> {
243
+ const job = startJob(`Publishing to ${this.displayName}`) as PublicationJobData
244
+ job.logs = [[`Publishing to ${this.displayName}`]]
245
+ job.errors = [[]]
246
+ this.writeFiles(session, id, files, async ({status, message}) => {
247
+ // Update the job status
248
+ job.status = status
249
+ job.message = message
250
+ job.logs[0].push(message)
251
+ // Add the website url
252
+ job.url = await this.getFileUrl(session, id, 'index.html')
253
+ if(status === JobStatus.SUCCESS) {
254
+ jobSuccess(job.jobId, message)
255
+ } else if(status === JobStatus.ERROR) {
256
+ job.errors[0].push(message)
257
+ jobError(job.jobId, message)
258
+ }
259
+ })
260
+ return job
261
+ }
262
+
263
+ async getWebsiteUrl(session: FsSession, id: WebsiteId): Promise<string> {
264
+ return this.getFileUrl(session, id, '/index.html')
265
+ }
266
+ }
@@ -0,0 +1,309 @@
1
+ /*
2
+ * Silex website builder, free/libre no-code tool for makers.
3
+ * Copyright (c) 2023 lexoyo and Silex Labs foundation
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU Affero General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU Affero General Public License
15
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import { Application, Response, Router } from 'express'
19
+ import { Readable } from 'stream'
20
+ import { ServerConfig } from '../config'
21
+ import { JobStatus, JobData, BackendData, BackendId, WebsiteId, ApiBackendListResponse, ApiBackendListRequestQuery, ApiResponseError, ApiBackendLoginRequestQuery, ApiBackendLoggedInPostMessage, ApiBackendLogoutRequestQuery, BackendType, BackendUser, WebsiteMeta, ApiBackendLoginStatusRequestQuery, ApiBackendLoginStatusResponse, FileMeta } from '../../types'
22
+ import { API_BACKEND_LIST, API_BACKEND_LOGIN_CALLBACK, API_BACKEND_LOGIN_STATUS, API_BACKEND_LOGOUT } from '../../constants'
23
+ import { requiredParam } from '../utils/validation'
24
+
25
+ /**
26
+ * @fileoverview define types for Silex backends
27
+ * Bakends can provide storage for website data and assets, and/or hosting to publish the website online
28
+ */
29
+
30
+ /**
31
+ * Files are stored in backend as a File object
32
+ * @see types/ClientSideFile
33
+ */
34
+ export interface File {
35
+ path: string,
36
+ content: Buffer | string | Readable,
37
+ }
38
+
39
+ /**
40
+ * Callback to update the publication status
41
+ */
42
+ export type StatusCallback = ({message, status}: {message: string, status: JobStatus}) => Promise<void>
43
+
44
+ export type BackendSession = any
45
+ /**
46
+ * Backends are the base interface for Storage and Hosting providers
47
+ */
48
+ export interface Backend<Session extends BackendSession = BackendSession> {
49
+ id: BackendId
50
+ displayName: string
51
+ icon: string
52
+ disableLogout?: boolean
53
+ type: BackendType
54
+ getAdminUrl(session: Session, id: WebsiteId): Promise<string | null>
55
+ getAuthorizeURL(session: Session): Promise<string | null>
56
+ isLoggedIn(session: Session): Promise<boolean>
57
+ //login(session: Session, userData: any): Promise<void>
58
+ logout(session: Session): Promise<void>
59
+ getUserData(session: Session): Promise<BackendUser>
60
+ init(session: Session, id: WebsiteId): Promise<void>
61
+ }
62
+
63
+ /**
64
+ * Storage are used to store the website data and assets
65
+ * And possibly rename files and directories, and get the URL of a file
66
+ *
67
+ */
68
+ export interface StorageProvider<Session = BackendSession> extends Backend<Session> {
69
+ listWebsites(session: Session): Promise<WebsiteMeta[]>
70
+ readFile(session: Session, id: WebsiteId, path: string): Promise<File>
71
+ writeFiles(session: Session, id: WebsiteId, files: File[], status?: StatusCallback): Promise<string[]> // Returns the files paths on storage
72
+ deleteFiles(session: Session, id: WebsiteId, paths: string[]): Promise<void>
73
+ listDir(session: Session, id: WebsiteId, path: string): Promise<FileMeta[]>
74
+ createDir(session: Session, id: WebsiteId, path: string): Promise<void>
75
+ deleteDir(session: Session, id: WebsiteId, path: string): Promise<void>
76
+ getSiteMeta(session: Session, id: WebsiteId): Promise<WebsiteMeta>
77
+ //getFileUrl(session: Session, id: WebsiteId, path: string): Promise<string>
78
+ }
79
+
80
+ /**
81
+ * Hosting providers are used to publish the website
82
+ */
83
+ export interface HostingProvider<Session = BackendSession> extends Backend<Session> {
84
+ publish(session: Session, id: WebsiteId, backendData: BackendData, files: File[]): Promise<JobData>
85
+ getWebsiteUrl(session: Session, id: WebsiteId): Promise<string>
86
+ }
87
+
88
+ export function toBackendEnum(type: string): BackendType {
89
+ return BackendType[type.toUpperCase() as keyof typeof BackendType]
90
+ }
91
+
92
+ /**
93
+ * Get a backend by id or by type
94
+ */
95
+ export async function getBackend<T extends Backend>(config: ServerConfig, session: any, type: BackendType, backendId?: BackendId): Promise<T | undefined> {
96
+ const backends = config.getBackends<T>(type)
97
+ // Find the backend by id
98
+ if (backendId) return backends.find(s => s.id === backendId && s.type === type)
99
+ // Find the first logged in backend
100
+ for (const backend of backends) {
101
+ if (await backend.isLoggedIn(session)) {
102
+ return backend
103
+ }
104
+ }
105
+ // Defaults to the first backend
106
+ return backends[0]
107
+ }
108
+
109
+ /**
110
+ * Convert a backend to a BackendData object to be sent to the frontend
111
+ */
112
+ export async function toBackendData(session: any, backend: Backend): Promise<BackendData> {
113
+ return {
114
+ backendId: backend.id,
115
+ type: backend.type,
116
+ displayName: backend.displayName,
117
+ icon: backend.icon,
118
+ disableLogout: !!backend.disableLogout,
119
+ isLoggedIn: await backend.isLoggedIn(session),
120
+ authUrl: await backend.getAuthorizeURL(session),
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Add routes to the express app
126
+ */
127
+ export function addRoutes(config: ServerConfig, app: Application) {
128
+ // Create the router
129
+ const router = Router()
130
+ app.use(router)
131
+
132
+ // Backend routes
133
+ router.get(`/${API_BACKEND_LOGIN_STATUS}`, routeLoginStatus)
134
+ router.get(`/${API_BACKEND_LIST}`, routeListBackends)
135
+ router.post(`/${API_BACKEND_LOGOUT}`, routeLogout)
136
+ router.get(`/${API_BACKEND_LOGIN_CALLBACK}`, routeLoginSuccess)
137
+ }
138
+
139
+ /**
140
+ * Express route to check if the user is logged in
141
+ * Returns user data and backend data
142
+ */
143
+ export async function routeLoginStatus(req, res) {
144
+ try {
145
+ const config = requiredParam(req.app.get('config') as ServerConfig, 'Config object on express js APP')
146
+ const query = requiredParam(req.query as ApiBackendLoginStatusRequestQuery, 'Query object')
147
+ const session = requiredParam(req['session'], 'Session object')
148
+ const websiteId = query.id
149
+ const backendId = query.backendId
150
+ const type = query.type
151
+ const backend = await getBackend<Backend>(config, session, type, backendId)
152
+ if (!backend) {
153
+ res
154
+ .status(500)
155
+ .json({
156
+ error: true,
157
+ message: 'No backend found',
158
+ } as ApiResponseError)
159
+ return
160
+ }
161
+ if(!await backend.isLoggedIn(session)) {
162
+ res
163
+ .status(401)
164
+ .json({
165
+ error: true,
166
+ message: 'Not logged in',
167
+ } as ApiResponseError)
168
+ return
169
+ }
170
+ const backendData = await toBackendData(session, backend)
171
+ const user = await backend.getUserData(session)
172
+ const websiteMeta = websiteId && backend.type === BackendType.STORAGE && await (backend as StorageProvider).getSiteMeta(session, websiteId)
173
+ res.json({
174
+ backend: backendData,
175
+ user,
176
+ websiteMeta,
177
+ } as ApiBackendLoginStatusResponse)
178
+ } catch (error) {
179
+ console.error('Error in the login status request', error)
180
+ res.status(error?.code ?? 500).json({
181
+ error: true,
182
+ message: error.message,
183
+ } as ApiResponseError)
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Express route to list the backends
189
+ */
190
+ export async function routeListBackends(req, res) {
191
+ try {
192
+ const config = requiredParam(req.app.get('config') as ServerConfig, 'Config object on express js APP')
193
+ const query = req.query as ApiBackendListRequestQuery
194
+ const type = toBackendEnum(requiredParam(query.type, 'Backend type'))
195
+ const backends = config.getBackends<Backend>(type)
196
+ try {
197
+ const list = await Promise.all(backends.map(async backend => toBackendData(req['session'], backend)))
198
+ res.json(list as ApiBackendListResponse)
199
+ } catch (error) {
200
+ console.error('Error while listing backends', error)
201
+ res.status(error?.code ?? 500).json({
202
+ error: true,
203
+ message: 'Error while listing backends: ' + error.message,
204
+ } as ApiResponseError)
205
+ }
206
+ } catch (error) {
207
+ console.error('Error in the list backends request', error)
208
+ res.status(error?.code ?? 400).json({
209
+ error: true,
210
+ message: 'Error in the list backends request: ' + error.message,
211
+ } as ApiResponseError)
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Utility function to send an HTML page to the browser
217
+ * This page will send a postMessage to the parent window and close itself
218
+ */
219
+ function sendHtml(res: Response, message: string, backendId?: BackendId, error?: Error, defaultErrorCode?: number) {
220
+ error && console.error('Error while logging in', error)
221
+ // Data for postMessage
222
+ const data = {
223
+ type: 'login', // For postMessage
224
+ error: error ? true : false,
225
+ message,
226
+ backendId,
227
+ } as ApiBackendLoggedInPostMessage
228
+ // HTTP status code
229
+ const status = error ? error['code'] ?? defaultErrorCode ?? 500 : 200
230
+ // Send the HTML
231
+ res
232
+ .status(status)
233
+ .send(`
234
+ <html>
235
+ <head>
236
+ <script>
237
+ window.opener.postMessage(${JSON.stringify(data)}, '*')
238
+ window.close()
239
+ </script>
240
+ </head>
241
+ <body>
242
+ <p>${message}</p>
243
+ <p>Close this window</p>
244
+ </body>
245
+ </html>
246
+ `)
247
+ }
248
+
249
+ /**
250
+ * Express route to serve as redirect after a successful login
251
+ * The returned HTML will postMessage data and close the popup window
252
+ */
253
+ export async function routeLoginSuccess(req, res) {
254
+ try {
255
+ const query = req.query as ApiBackendLoginRequestQuery
256
+ if(query.error) throw new Error(query.error)
257
+
258
+ const backendId = requiredParam(query.backendId, 'Backend id')
259
+ const config = requiredParam(req.app.get('config') as ServerConfig, 'Config object on express js APP')
260
+ const type = toBackendEnum(requiredParam(query.type, 'Backend type'))
261
+ const backend = await getBackend<Backend>(config, req['session'], type, backendId)
262
+ if (!backend) throw new Error('Backend not found ' + backendId)
263
+ try {
264
+ sendHtml(res, 'Logged in', backendId)
265
+ } catch (error) {
266
+ sendHtml(res, 'Error while logging in', undefined, error, 500)
267
+ }
268
+ } catch (error) {
269
+ sendHtml(res, 'Error in the request ' + error.message, undefined, error, 400)
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Express route to logout from a backend
275
+ */
276
+ export async function routeLogout(req, res) {
277
+ try {
278
+ const query = req.query as ApiBackendLogoutRequestQuery
279
+ // Get the backend
280
+ const backendId = requiredParam(query.backendId, 'Backend id')
281
+ const config = requiredParam(req.app.get('config') as ServerConfig, 'Config object on express js APP')
282
+ const type = toBackendEnum(requiredParam(query.type, 'Backend type'))
283
+ const backend = await getBackend<Backend>(config, req['session'], type, backendId)
284
+ if (!backend) throw new Error(`Backend not found ${backendId} ${type}`)
285
+ try {
286
+ // Logout
287
+ await backend.logout(req['session'])
288
+ // Return success
289
+ res.json({
290
+ error: false,
291
+ message: 'OK',
292
+ } as ApiResponseError)
293
+ } catch (error) {
294
+ console.error('Error while logging out', error)
295
+ res.status(error?.code ?? 500).json({
296
+ error: true,
297
+ message: 'Error while logging out: ' + error.message,
298
+ } as ApiResponseError)
299
+ return
300
+ }
301
+ } catch (error) {
302
+ console.error('Error in the logout request', error)
303
+ res.status(error?.code ?? 400).json({
304
+ error: true,
305
+ message: 'Error in the logout request: ' + error.message,
306
+ } as ApiResponseError)
307
+ return
308
+ }
309
+ }
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
1
  /*
4
2
  * Silex website builder, free/libre no-code tool for makers.
5
3
  * Copyright (c) 2023 lexoyo and Silex Labs foundation