UNPKG

@silexlabs/silex

Version:

Free and easy website builder for everyone.

204 lines (188 loc) 7.91 kB
/* * Silex website builder, free/libre no-code tool for makers. * Copyright (c) 2023 lexoyo and Silex Labs foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ import { Readable } from 'stream' import { ServerConfig } from '../config' import { JobStatus, JobData, ConnectorData, ConnectorId, WebsiteId, ConnectorType, ConnectorUser, WebsiteMeta, WebsiteMetaFileContent, WebsiteData, ConnectorOptions } from '../../types' import { JobManager } from '../jobs' /** * @fileoverview define types for Silex connectors * Bakends can provide storage for website data and assets, and/or hosting to publish the website online */ /** * Connector data stored in the website meta file */ export type ConnectorFileContent = string | Buffer | Readable /** * Files are stored in connector as a File object * @see types/ClientSideFile * @see contentToString() */ export interface ConnectorFile { path: string, content: ConnectorFileContent, } /** * Callback to update the publication status */ export type StatusCallback = ({message, status}: {message: string, status: JobStatus}) => Promise<void> export type ConnectorSession = object /** * Connectors are the base interface for Storage and Hosting connectors */ export interface Connector<Session extends ConnectorSession = ConnectorSession> { connectorId: ConnectorId connectorType: ConnectorType displayName: string icon: string disableLogout?: boolean color: string background: string // Get the URL to start the authentication process with OAuth (not used for basic auth) getOAuthUrl(session: Session): Promise<string | null> // Get the form to display to the user to authenticate (not used for OAuth) getLoginForm(session: Session, redirectTo: string): Promise<string | null> // Get the form to display to the user to set the connector settings for a given website getSettingsForm(session: Session, redirectTo: string): Promise<string | null> // Auth and user management isLoggedIn(session: Session): Promise<boolean> setToken(session: Session, token: object): Promise<void> logout(session: Session): Promise<void> getUser(session: Session): Promise<ConnectorUser | null> // Get the connector options from login form // They are stored in the website meta file for hosting connectors // And in the user session for storage connectors getOptions(formData: object): ConnectorOptions } /** * Storage are used to store the website data and assets * And possibly rename files and directories, and get the URL of a file * */ export interface StorageConnector<Session extends ConnectorSession = ConnectorSession> extends Connector<Session> { // CRUD on websites listWebsites(session: Session): Promise<WebsiteMeta[]> readWebsite(session: Session, websiteId: WebsiteId): Promise<WebsiteData | Readable> createWebsite(session: Session, data: WebsiteMetaFileContent): Promise<WebsiteId> updateWebsite(session: Session, websiteId: WebsiteId, data: WebsiteData): Promise<void> deleteWebsite(session: Session, websiteId: WebsiteId): Promise<void> duplicateWebsite(session: Session, websiteId: WebsiteId): Promise<void> // CRUD on assets writeAssets(session: Session, websiteId: WebsiteId, files: ConnectorFile[], status?: StatusCallback): Promise<string[] | void> readAsset(session: Session, websiteId: WebsiteId, fileName: string): Promise<ConnectorFileContent> deleteAssets(session: Session, websiteId: WebsiteId, fileNames: string[]): Promise<void> //getFileUrl(session: Session, websiteId: WebsiteId, path: string): Promise<string> // Handle website meta file getWebsiteMeta(session: Session, websiteId: WebsiteId): Promise<WebsiteMeta> setWebsiteMeta(session: Session, websiteId: WebsiteId, data: WebsiteMetaFileContent): Promise<void> } /** * Hosting connectors are used to publish the website */ export interface HostingConnector<Session extends ConnectorSession = ConnectorSession> extends Connector<Session> { publish(session: Session, websiteId: WebsiteId, files: ConnectorFile[], jobManager: JobManager): Promise<JobData> // Pass the jobManager as plugins do not neccessarily share the same module instance getUrl(session: Session, websiteId: WebsiteId): Promise<string> } export function toConnectorEnum(type: string | ConnectorType): ConnectorType { const result = ConnectorType[type.toString().toUpperCase() as keyof typeof ConnectorType] if(!result) throw new Error(`Unknown connector type ${type}. It should be one of ${Object.keys(ConnectorType).join(', ')}`) return result } /** * Get a connector by id or by type */ export async function getConnector<T extends Connector>(config: ServerConfig, session: any, type: ConnectorType, connectorId?: ConnectorId): Promise<T | undefined> { // Get the connectors for this type const connectors = config.getConnectors(type) as T[] // Find the connector by id if (connectorId) return connectors.find(s => s.connectorId === connectorId && s.connectorType === type) // Find the first logged in connector for (const connector of connectors) { if (await connector.isLoggedIn(session)) { return connector } } // Defaults to the first connector return connectors[0] } /** * Convert a connector to a ConnectorData object to be sent to the frontend */ export async function toConnectorData(session: any, connector: Connector): Promise<ConnectorData> { return { connectorId: connector.connectorId, type: connector.connectorType, displayName: connector.displayName, icon: connector.icon, disableLogout: !!connector.disableLogout, isLoggedIn: await connector.isLoggedIn(session), oauthUrl: await connector.getOAuthUrl(session), color: connector.color, background: connector.background, } } export async function contentToString(content: ConnectorFileContent): Promise<string> { // String if (typeof content === 'string') return content // Buffer if (Buffer.isBuffer(content)) return content.toString('utf8') // Stream return new Promise((resolve, reject) => { let result = '' content.on('data', (chunk: Buffer) => { result += chunk.toString('utf8') }) content.on('error', (err: Error) => { reject(err) }) content.on('end', () => { resolve(result) }) }) } export async function contentToBuffer(content: ConnectorFileContent): Promise<Buffer> { // String if (typeof content === 'string') return Buffer.from(content, 'utf8') // Buffer if (Buffer.isBuffer(content)) return content // Stream if(content instanceof Readable) { return new Promise((resolve, reject) => { const chunks: Buffer[] = [] content.on('data', (chunk: Buffer) => { chunks.push(chunk) }) content.on('error', (err: Error) => { reject(err) }) content.on('end', () => { resolve(Buffer.concat(chunks)) }) }) } // Unknown throw new Error('Unknown content type') } export function contentToReadable(content: ConnectorFileContent): Readable { // String if (typeof content === 'string') return Readable.from([content]) // Buffer if (Buffer.isBuffer(content)) return Readable.from([content]) // Stream if (content instanceof Readable) return content // Unknown throw new Error('Unknown content type') }