Skip to content

Instantly share code, notes, and snippets.

@yagudaev
Last active February 5, 2024 11:39
Show Gist options
  • Save yagudaev/016588a8b4327500d3e6331724d2e807 to your computer and use it in GitHub Desktop.
Save yagudaev/016588a8b4327500d3e6331724d2e807 to your computer and use it in GitHub Desktop.
Firebase with Figma Storage Example
figma.ui.onmessage = async (msg, props) => {
if (originalOnMessage && isArray(msg)) {
originalOnMessage.apply(null, [msg, props])
return
}
switch (msg.type) {
case "req-read-local-storage":
await readLocalStorage(msg.data)
// figma.closePlugin()
return
case "req-update-local-storage":
await updateLocalStorage(msg.data)
return
default:
console.error("Unknown message type", msg.type)
}
}
import { figmaPluginPersistence } from "./figmaPluginPersistence"
// Initialize Firebase auth
const auth = initializeAuth(firebaseApp, {
persistence: isLocalStorageAvailable() ? browserLocalPersistence : figmaPluginPersistence
})
mport { Persistence } from "firebase/auth"
import * as FigmaPluginAPI from "./figmaPluginAPI"
export const enum PersistenceType {
SESSION = "SESSION",
LOCAL = "LOCAL",
NONE = "NONE"
}
export type PersistedBlob = Record<string, unknown>
export interface Instantiator<T> {
(blob: PersistedBlob): T
}
export type PersistenceValue = PersistedBlob | string
export const STORAGE_AVAILABLE_KEY = "__sak"
export interface StorageEventListener {
(value: PersistenceValue | null): void
}
export interface PersistenceInternal extends Persistence {
type: PersistenceType
_isAvailable(): Promise<boolean>
_set(key: string, value: PersistenceValue): Promise<void>
_get<T extends PersistenceValue>(key: string): Promise<T | null>
_remove(key: string): Promise<void>
_addListener(key: string, listener: StorageEventListener): void
_removeListener(key: string, listener: StorageEventListener): void
// Should this persistence allow migration up the chosen hierarchy?
_shouldAllowMigration?: boolean
}
interface Storage {
getItem: (key: string) => Promise<string | null>
setItem: (key: string, value: string) => Promise<void>
removeItem: (key: string) => Promise<void>
}
export const figmaPluginPersistence: Persistence = getFigmaPluginPersistence({
getItem: async (key: string) => {
const value = await FigmaPluginAPI.Storage.getItem(key)
return value
},
setItem: async (key: string, value: string) => {
await FigmaPluginAPI.Storage.setItem(key, value)
},
removeItem: async (key: string) => {
await FigmaPluginAPI.Storage.removeItem(key)
}
})
export function getFigmaPluginPersistence(storage: Storage): Persistence {
// In the _getInstance() implementation (see src/core/persistence/index.ts),
// we expect each "externs.Persistence" object passed to us by the user to
// be able to be instantiated (as a class) using "new". That function also
// expects the constructor to be empty. Since ReactNativeStorage requires the
// underlying storage layer, we need to be able to create subclasses
// (closures, esentially) that have the storage layer but empty constructor.
return class implements PersistenceInternal {
static type: "LOCAL" = "LOCAL"
readonly type: PersistenceType = PersistenceType.LOCAL
async _isAvailable(): Promise<boolean> {
try {
if (!storage) {
return false
}
await storage.setItem(STORAGE_AVAILABLE_KEY, "1")
await storage.removeItem(STORAGE_AVAILABLE_KEY)
return true
} catch {
return false
}
}
_set(key: string, value: PersistenceValue): Promise<void> {
return storage.setItem(key, JSON.stringify(value))
}
async _get<T extends PersistenceValue>(key: string): Promise<T | null> {
const json = await storage.getItem(key)
return json ? JSON.parse(json) : null
}
_remove(key: string): Promise<void> {
return storage.removeItem(key)
}
_addListener(_key: string, _listener: StorageEventListener): void {
// Listeners are not supported for React Native storage.
return
}
_removeListener(_key: string, _listener: StorageEventListener): void {
// Listeners are not supported for React Native storage.
return
}
}
}
import { emit, on, once } from "@create-figma-plugin/utilities"
export const Storage = {
getItem: async (key: string): Promise<string | null> => {
return new Promise((resolve, reject) => {
const unsubscribe = once("res-get-local-storage", ({ data }) => {
resolve(data)
unsubscribe()
})
emit("req-get-local-storage", { key })
})
},
setItem: async (key: string, value: string): Promise<void> => {
return new Promise((resolve, reject) => {
const unsubscribe = once("res-set-local-storage", () => {
resolve()
unsubscribe()
})
emit("req-set-local-storage", { key, value })
})
},
removeItem: async (key: string): Promise<void> => {
return new Promise((resolve, reject) => {
const unsubscribe = once("res-remove-local-storage", () => {
resolve()
unsubscribe()
})
emit("req-remove-local-storage", { key })
})
}
}
@yagudaev
Copy link
Author

yagudaev commented Jun 1, 2023

The above code makes it possible to use Firebase Auth with Figma by replacing localStorage with figma.clientStorage. It requires message passing between the UI and Main (figma sandbox running using QuickJS).

This can be rolled into an NPM package for easy usage.

If you do endup doing that, please link back here 😁.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment