UNPKG

@grafana/runtime

Version:
1 lines 8.05 kB
{"version":3,"file":"userStorage.mjs","sources":["../../../src/utils/userStorage.tsx"],"sourcesContent":["import { get } from 'lodash';\nimport { lastValueFrom } from 'rxjs';\n\nimport { usePluginContext } from '@grafana/data';\n\nimport { config } from '../config';\nimport { BackendSrvRequest, getBackendSrv } from '../services';\n\nconst baseURL = `/apis/userstorage.grafana.app/v0alpha1/namespaces/${config.namespace}/user-storage`;\n\ninterface RequestOptions extends BackendSrvRequest {\n manageError?: (err: unknown) => { error: unknown };\n showErrorAlert?: boolean;\n\n // rtk codegen sets this\n body?: BackendSrvRequest['data'];\n}\n\nexport type UserStorageSpec = {\n data: { [key: string]: string };\n};\n\nasync function apiRequest<T>(requestOptions: RequestOptions) {\n try {\n const { data: responseData, ...meta } = await lastValueFrom(\n getBackendSrv().fetch<T>({\n ...requestOptions,\n url: baseURL + requestOptions.url,\n data: requestOptions.body,\n })\n );\n return { data: responseData, meta };\n } catch (error) {\n return requestOptions.manageError ? requestOptions.manageError(error) : { error };\n }\n}\n\n/**\n * A class for interacting with the backend user storage.\n * Unexported because it is currently only be used through the useUserStorage hook.\n */\nclass UserStorage {\n private service: string;\n private resourceName: string;\n private userUID: string;\n private canUseUserStorage: boolean;\n private storageSpec: UserStorageSpec | null | undefined;\n\n constructor(service: string) {\n this.service = service;\n this.userUID = config.bootData.user.uid === '' ? config.bootData.user.id.toString() : config.bootData.user.uid;\n this.resourceName = `${service}:${this.userUID}`;\n this.canUseUserStorage = config.featureToggles.userStorageAPI === true && config.bootData.user.isSignedIn;\n }\n\n private async init() {\n if (this.storageSpec !== undefined) {\n return;\n }\n const userStorage = await apiRequest<{ spec: UserStorageSpec }>({\n url: `/${this.resourceName}`,\n method: 'GET',\n showErrorAlert: false,\n });\n if ('error' in userStorage) {\n if (get(userStorage, 'error.status') !== 404) {\n console.error('Failed to get user storage', userStorage.error);\n }\n // No user storage found, return null\n this.storageSpec = null;\n } else {\n this.storageSpec = userStorage.data.spec;\n }\n }\n\n async getItem(key: string): Promise<string | null> {\n if (!this.canUseUserStorage) {\n // Fallback to localStorage\n return localStorage.getItem(this.resourceName);\n }\n // Ensure this.storageSpec is initialized\n await this.init();\n if (!this.storageSpec) {\n // Also, fallback to localStorage for backward compatibility once userStorageAPI is enabled\n return localStorage.getItem(this.resourceName);\n }\n return this.storageSpec.data[key];\n }\n\n async setItem(key: string, value: string): Promise<void> {\n if (!this.canUseUserStorage) {\n // Fallback to localStorage\n localStorage.setItem(key, value);\n return;\n }\n\n const newData = { data: { [key]: value } };\n // Ensure this.storageSpec is initialized\n await this.init();\n\n if (!this.storageSpec) {\n // No user storage found, create a new one\n await apiRequest<UserStorageSpec>({\n url: `/`,\n method: 'POST',\n body: {\n metadata: { name: this.resourceName, labels: { user: this.userUID, service: this.service } },\n spec: newData,\n },\n });\n this.storageSpec = newData;\n return;\n }\n\n // Update existing user storage\n this.storageSpec.data[key] = value;\n await apiRequest<UserStorageSpec>({\n headers: { 'Content-Type': 'application/merge-patch+json' },\n url: `/${this.resourceName}`,\n method: 'PATCH',\n body: { spec: newData },\n });\n }\n}\n\nexport interface PluginUserStorage {\n /**\n * Retrieves an item from the backend user storage or local storage if not enabled.\n * @param key - The key of the item to retrieve.\n * @returns A promise that resolves to the item value or null if not found.\n */\n getItem(key: string): Promise<string | null>;\n /**\n * Sets an item in the backend user storage or local storage if not enabled.\n * @param key - The key of the item to set.\n * @param value - The value of the item to set.\n * @returns A promise that resolves when the item is set.\n */\n setItem(key: string, value: string): Promise<void>;\n}\n\n/**\n * A hook for interacting with the backend user storage (or local storage if not enabled).\n * @returns An scoped object for a plugin and a user with getItem and setItem functions.\n * @alpha Experimental\n */\nexport function usePluginUserStorage(): PluginUserStorage {\n const context = usePluginContext();\n if (!context) {\n throw new Error(`No PluginContext found. The usePluginUserStorage() hook can only be used from a plugin.`);\n }\n return new UserStorage(context?.meta.id);\n}\n"],"names":[],"mappings":"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"}