diff --git a/packages/builder/src/api.ts b/packages/builder/src/api.ts new file mode 100644 index 0000000000..5d1a0beaeb --- /dev/null +++ b/packages/builder/src/api.ts @@ -0,0 +1,59 @@ +import { + createAPIClient, + CookieUtils, + Constants, +} from "@budibase/frontend-core" +import { appStore } from "stores/builder" +import { get } from "svelte/store" +import { auth, navigation } from "./stores/portal" + +export const API = createAPIClient({ + attachHeaders: (headers: Record) => { + // Attach app ID header from store + let appId = get(appStore).appId + if (appId) { + headers["x-budibase-app-id"] = appId + } + + // Add csrf token if authenticated + const user: any = get(auth).user + if (user?.csrfToken) { + headers["x-csrf-token"] = user.csrfToken + } + }, + + onError: (error: any) => { + const { url, message, status, method, handled } = error || {} + + // Log any errors that we haven't manually handled + if (!handled) { + console.error("Unhandled error from API client", error) + return + } + + // Log all errors to console + console.warn(`[Builder] HTTP ${status} on ${method}:${url}\n\t${message}`) + + // Logout on 403's + if (status === 403) { + // Remove cookies + CookieUtils.removeCookie(Constants.Cookies.Auth) + + // Reload after removing cookie, go to login + if (!url.includes("self") && !url.includes("login")) { + location.reload() + } + } + }, + onMigrationDetected: (appId: string) => { + const updatingUrl = `/builder/app/updating/${appId}` + + if (window.location.pathname === updatingUrl) { + return + } + + get(navigation)?.goto( + `${updatingUrl}?returnUrl=${encodeURIComponent(window.location.pathname)}` + ) + }, +}) diff --git a/packages/builder/src/stores/portal/navigation.ts b/packages/builder/src/stores/portal/navigation.ts new file mode 100644 index 0000000000..2b230622f6 --- /dev/null +++ b/packages/builder/src/stores/portal/navigation.ts @@ -0,0 +1,38 @@ +import { writable } from "svelte/store" + +type GotoFuncType = (path: string) => void + +interface Store { + initialisated: boolean + goto: GotoFuncType +} + +export function createNavigationStore() { + const store = writable({ + initialisated: false, + goto: undefined as any, + }) + const { set, subscribe } = store + + const init = (gotoFunc: GotoFuncType) => { + if (typeof gotoFunc !== "function") { + throw new Error( + `gotoFunc must be a function, found a "${typeof gotoFunc}" instead` + ) + } + + set({ + initialisated: true, + goto: gotoFunc, + }) + } + + return { + subscribe, + actions: { + init, + }, + } +} + +export const navigation = createNavigationStore() diff --git a/packages/builder/src/stores/portal/plugins.ts b/packages/builder/src/stores/portal/plugins.ts new file mode 100644 index 0000000000..ff4dc3618f --- /dev/null +++ b/packages/builder/src/stores/portal/plugins.ts @@ -0,0 +1,81 @@ +import { writable } from "svelte/store" +import { PluginSource } from "constants/index" + +const { API } = require("api") + +interface Plugin { + _id: string +} + +export function createPluginsStore() { + const { subscribe, set, update } = writable([]) + + async function load() { + const plugins = await API.getPlugins() + set(plugins) + } + + async function deletePlugin(pluginId: string) { + await API.deletePlugin(pluginId) + update(state => { + state = state.filter(existing => existing._id !== pluginId) + return state + }) + } + + async function createPlugin(source: string, url: string, auth = null) { + let pluginData: any = { + source, + url, + } + + switch (source) { + case PluginSource.URL: + pluginData.headers = auth + break + case PluginSource.GITHUB: + pluginData.githubToken = auth + break + } + + let res = await API.createPlugin(pluginData) + let newPlugin = res.plugin + update(state => { + const currentIdx = state.findIndex(plugin => plugin._id === newPlugin._id) + if (currentIdx >= 0) { + state.splice(currentIdx, 1, newPlugin) + } else { + state.push(newPlugin) + } + return state + }) + } + + async function uploadPlugin(file: File) { + if (!file) { + return + } + let data = new FormData() + data.append("file", file) + let resp = await API.uploadPlugin(data) + let newPlugin = resp.plugins[0] + update(state => { + const currentIdx = state.findIndex(plugin => plugin._id === newPlugin._id) + if (currentIdx >= 0) { + state.splice(currentIdx, 1, newPlugin) + } else { + state.push(newPlugin) + } + return state + }) + } + return { + subscribe, + load, + createPlugin, + deletePlugin, + uploadPlugin, + } +} + +export const plugins = createPluginsStore()