From 86241417806a75ed5362eb76008d08487f42d428 Mon Sep 17 00:00:00 2001 From: fyears Date: Sat, 11 Dec 2021 17:33:55 +0800 Subject: [PATCH] add qrcode export import --- package.json | 2 + src/baseTypes.ts | 46 +++++++++++++++ src/importExport.ts | 76 ++++++++++++++++++++++++ src/main.ts | 127 +++++++++++++++++++++++++++++++++++----- src/remote.ts | 19 +++--- src/remoteForDropbox.ts | 12 +--- src/remoteForS3.ts | 10 +--- src/remoteForWebdav.ts | 11 +--- styles.css | 5 ++ 9 files changed, 255 insertions(+), 53 deletions(-) create mode 100644 src/importExport.ts diff --git a/package.json b/package.json index 683a811..0d1273f 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@types/mime-types": "^2.1.1", "@types/mocha": "^9.0.0", "@types/node": "^14.14.37", + "@types/qrcode": "^1.4.1", "builtin-modules": "^3.2.0", "chai": "^4.3.4", "chai-as-promised": "^7.1.1", @@ -57,6 +58,7 @@ "obsidian": "^0.12.0", "path-browserify": "^1.0.1", "process": "^0.11.10", + "qrcode": "^1.5.0", "rfc4648": "^1.5.0", "rimraf": "^3.0.2", "stream-browserify": "^3.0.0", diff --git a/src/baseTypes.ts b/src/baseTypes.ts index 13e70c7..425477f 100644 --- a/src/baseTypes.ts +++ b/src/baseTypes.ts @@ -1,9 +1,45 @@ /** * Only type defs here. + * To avoid circular dependency. */ export type SUPPORTED_SERVICES_TYPE = "s3" | "webdav" | "dropbox"; +export interface S3Config { + s3Endpoint: string; + s3Region: string; + s3AccessKeyID: string; + s3SecretAccessKey: string; + s3BucketName: string; +} + +export interface DropboxConfig { + accessToken: string; + clientID: string; + refreshToken: string; + accessTokenExpiresInSeconds: number; + accessTokenExpiresAtTime: number; + accountID: string; + username: string; +} + +export type WebdavAuthType = "digest" | "basic"; + +export interface WebdavConfig { + address: string; + username: string; + password: string; + authType: WebdavAuthType; +} + +export interface RemotelySavePluginSettings { + s3: S3Config; + webdav: WebdavConfig; + dropbox: DropboxConfig; + password: string; + serviceType: SUPPORTED_SERVICES_TYPE; +} + export interface RemoteItem { key: string; lastModified: number; @@ -11,3 +47,13 @@ export interface RemoteItem { remoteType: SUPPORTED_SERVICES_TYPE; etag?: string; } + +export const COMMAND_URI = "remotely-save"; +export const COMMAND_CALLBACK = "remotely-save-cb"; + +export interface UriParams { + func?: string; + vault?: string; + ver?: string; + data?: string; +} diff --git a/src/importExport.ts b/src/importExport.ts new file mode 100644 index 0000000..b71c035 --- /dev/null +++ b/src/importExport.ts @@ -0,0 +1,76 @@ +import QRCode from "qrcode"; + +import { + COMMAND_URI, + UriParams, + RemotelySavePluginSettings, +} from "./baseTypes"; + +export const exportQrCodeUri = async ( + settings: RemotelySavePluginSettings, + currentVaultName: string, + pluginVersion: string +) => { + const data = encodeURIComponent(JSON.stringify(settings)); + const vault = encodeURIComponent(currentVaultName); + const version = encodeURIComponent(pluginVersion); + const rawUri = `obsidian://${COMMAND_URI}?func=settings&version=${version}&vault=${vault}&data=${data}`; + // console.log(uri) + const imgUri = await QRCode.toDataURL(rawUri); + return { + rawUri, + imgUri, + }; +}; + +export interface ProcessQrCodeResultType { + status: "error" | "ok"; + message: string; + result?: RemotelySavePluginSettings; +} + +export const importQrCodeUri = ( + inputParams: any, + currentVaultName: string +): ProcessQrCodeResultType => { + let params = inputParams as UriParams; + if ( + params.func === undefined || + params.func !== "settings" || + params.vault === undefined || + params.data === undefined + ) { + return { + status: "error", + message: `the uri is not for exporting/importing settings: ${JSON.stringify( + inputParams + )}`, + }; + } + + if (params.vault !== currentVaultName) { + return { + status: "error", + message: `the target vault is ${ + params.vault + } but you are currently in ${currentVaultName}: ${JSON.stringify( + inputParams + )}`, + }; + } + + let settings = {} as RemotelySavePluginSettings; + try { + settings = JSON.parse(params.data); + } catch (e) { + return { + status: "error", + message: `errors while parsing settings: ${JSON.stringify(inputParams)}`, + }; + } + return { + status: "ok", + message: "ok", + result: settings, + }; +}; diff --git a/src/main.ts b/src/main.ts index 826b0da..7301e5d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -27,14 +27,9 @@ import type { InternalDBs } from "./localdb"; import type { SyncStatusType, PasswordCheckType } from "./sync"; import { isPasswordOk, getSyncPlan, doActualSync } from "./sync"; -import { S3Config, DEFAULT_S3_CONFIG } from "./remoteForS3"; +import { DEFAULT_S3_CONFIG } from "./remoteForS3"; +import { DEFAULT_WEBDAV_CONFIG } from "./remoteForWebdav"; import { - WebdavConfig, - DEFAULT_WEBDAV_CONFIG, - WebdavAuthType, -} from "./remoteForWebdav"; -import { - DropboxConfig, DEFAULT_DROPBOX_CONFIG, getAuthUrlAndVerifier, sendAuthReq, @@ -43,15 +38,17 @@ import { import { RemoteClient } from "./remote"; import { exportSyncPlansToFiles } from "./debugMode"; -import { SUPPORTED_SERVICES_TYPE } from "./baseTypes"; - -interface RemotelySavePluginSettings { - s3: S3Config; - webdav: WebdavConfig; - dropbox: DropboxConfig; - password: string; - serviceType: SUPPORTED_SERVICES_TYPE; -} +import { COMMAND_URI, COMMAND_CALLBACK } from "./baseTypes"; +import type { + SUPPORTED_SERVICES_TYPE, + S3Config, + DropboxConfig, + WebdavAuthType, + WebdavConfig, + RemotelySavePluginSettings, +} from "./baseTypes"; +import type { ProcessQrCodeResultType } from "./importExport"; +import { exportQrCodeUri, importQrCodeUri } from "./importExport"; const DEFAULT_SETTINGS: RemotelySavePluginSettings = { s3: DEFAULT_S3_CONFIG, @@ -88,6 +85,32 @@ export default class RemotelySavePlugin extends Plugin { }) ); + this.registerObsidianProtocolHandler(COMMAND_URI, async (inputParams) => { + const parsed = importQrCodeUri(inputParams, this.app.vault.getName()); + if (parsed.status === "error") { + new Notice(parsed.message); + } else { + const copied = JSON.parse(JSON.stringify(parsed.result)); + // new Notice(JSON.stringify(copied)) + this.settings = copied; + this.saveSettings(); + new Notice( + `New settings for ${this.manifest.name} saved. Reopen the plugin Settings to the effect.` + ); + } + }); + + this.registerObsidianProtocolHandler( + COMMAND_CALLBACK, + async (inputParams) => { + new Notice( + `Your uri call a callback that's not supported yet: ${JSON.stringify( + inputParams + )}` + ); + } + ); + this.addRibbonIcon("switch", "Remotely Save", async () => { if (this.syncStatus !== "idle") { new Notice( @@ -382,6 +405,60 @@ export class DropboxAuthModal extends Modal { } } +export class ExportSettingsQrCodeModal extends Modal { + plugin: RemotelySavePlugin; + constructor(app: App, plugin: RemotelySavePlugin) { + super(app); + this.plugin = plugin; + } + + async onOpen() { + let { contentEl } = this; + + const { rawUri, imgUri } = await exportQrCodeUri( + this.plugin.settings, + this.app.vault.getName(), + this.plugin.manifest.version + ); + + contentEl.createEl("p", { + text: "You can use another device to scan this qrcode.", + }); + + contentEl.createEl("p", { + text: "Or, you can click the button to copy the special url.", + }); + + contentEl.createEl( + "button", + { + text: "Click to copy the special URI", + }, + (el) => { + el.onclick = async () => { + await navigator.clipboard.writeText(rawUri); + new Notice("special uri copied to clipboard!"); + }; + } + ); + + contentEl.createEl( + "img", + { + cls: "qrcode-img", + }, + async (el) => { + el.src = imgUri; + } + ); + } + + onClose() { + let { contentEl } = this; + contentEl.empty(); + } +} + class RemotelySaveSettingTab extends PluginSettingTab { plugin: RemotelySavePlugin; @@ -799,6 +876,24 @@ class RemotelySaveSettingTab extends PluginSettingTab { }); }); + // import and export + const importExportDiv = containerEl.createEl("div"); + importExportDiv.createEl("h2", { text: "Import and Export Settings" }); + + new Setting(importExportDiv) + .setName("export") + .setDesc("export all settings by generating qrcode") + .addButton(async (button) => { + button.setButtonText("Get QR Code"); + button.onClick(async () => { + new ExportSettingsQrCodeModal(this.app, this.plugin).open(); + }); + }); + + new Setting(importExportDiv) + .setName("import") + .setDesc("You should manually open a camera app to scan the QR code"); + const debugDiv = containerEl.createEl("div"); debugDiv.createEl("h2", { text: "Debug" }); const syncPlanDiv = debugDiv.createEl("div"); diff --git a/src/remote.ts b/src/remote.ts index f51ed74..85cbf3e 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -1,6 +1,11 @@ import { Vault } from "obsidian"; -import type { SUPPORTED_SERVICES_TYPE } from "./baseTypes"; +import type { + SUPPORTED_SERVICES_TYPE, + S3Config, + DropboxConfig, + WebdavConfig, +} from "./baseTypes"; import * as s3 from "./remoteForS3"; import * as webdav from "./remoteForWebdav"; import * as dropbox from "./remoteForDropbox"; @@ -8,17 +13,17 @@ import * as dropbox from "./remoteForDropbox"; export class RemoteClient { readonly serviceType: SUPPORTED_SERVICES_TYPE; readonly s3Client?: s3.S3Client; - readonly s3Config?: s3.S3Config; + readonly s3Config?: S3Config; readonly webdavClient?: webdav.WebDAVClient; - readonly webdavConfig?: webdav.WebdavConfig; + readonly webdavConfig?: WebdavConfig; readonly dropboxClient?: dropbox.WrappedDropboxClient; - readonly dropboxConfig?: dropbox.DropboxConfig; + readonly dropboxConfig?: DropboxConfig; constructor( serviceType: SUPPORTED_SERVICES_TYPE, - s3Config?: s3.S3Config, - webdavConfig?: webdav.WebdavConfig, - dropboxConfig?: dropbox.DropboxConfig, + s3Config?: S3Config, + webdavConfig?: WebdavConfig, + dropboxConfig?: DropboxConfig, vaultName?: string, saveUpdatedConfigFunc?: () => Promise ) { diff --git a/src/remoteForDropbox.ts b/src/remoteForDropbox.ts index d9e1506..1f39851 100644 --- a/src/remoteForDropbox.ts +++ b/src/remoteForDropbox.ts @@ -3,7 +3,7 @@ import { FileStats, Vault } from "obsidian"; import { Dropbox, DropboxAuth, DropboxResponse, files } from "dropbox"; export { Dropbox } from "dropbox"; -import { RemoteItem } from "./baseTypes"; +import { RemoteItem, DropboxConfig } from "./baseTypes"; import { arrayBufferToBuffer, bufferToArrayBuffer, @@ -14,16 +14,6 @@ import { } from "./misc"; import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt"; -export interface DropboxConfig { - accessToken: string; - clientID: string; - refreshToken: string; - accessTokenExpiresInSeconds: number; - accessTokenExpiresAtTime: number; - accountID: string; - username: string; -} - export const DEFAULT_DROPBOX_CONFIG = { accessToken: "", clientID: process.env.DEFAULT_DROPBOX_APP_KEY, diff --git a/src/remoteForS3.ts b/src/remoteForS3.ts index bf467c3..a9b8914 100644 --- a/src/remoteForS3.ts +++ b/src/remoteForS3.ts @@ -27,17 +27,9 @@ import { } from "./misc"; import * as mime from "mime-types"; -import { RemoteItem } from "./baseTypes"; +import { RemoteItem, S3Config } from "./baseTypes"; import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt"; -export interface S3Config { - s3Endpoint: string; - s3Region: string; - s3AccessKeyID: string; - s3SecretAccessKey: string; - s3BucketName: string; -} - export const DEFAULT_S3_CONFIG = { s3Endpoint: "", s3Region: "", diff --git a/src/remoteForWebdav.ts b/src/remoteForWebdav.ts index e2b90f3..06579f3 100644 --- a/src/remoteForWebdav.ts +++ b/src/remoteForWebdav.ts @@ -4,7 +4,7 @@ import { AuthType, BufferLike, createClient } from "webdav/web"; import type { WebDAVClient, ResponseDataDetailed, FileStat } from "webdav/web"; export type { WebDAVClient } from "webdav/web"; -import type { RemoteItem } from "./baseTypes"; +import type { RemoteItem, WebdavAuthType, WebdavConfig } from "./baseTypes"; import { arrayBufferToBuffer, @@ -14,15 +14,6 @@ import { } from "./misc"; import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt"; -export type WebdavAuthType = "digest" | "basic"; - -export interface WebdavConfig { - address: string; - username: string; - password: string; - authType: WebdavAuthType; -} - export const DEFAULT_WEBDAV_CONFIG = { address: "", username: "", diff --git a/styles.css b/styles.css index b73801e..fd452c9 100644 --- a/styles.css +++ b/styles.css @@ -32,3 +32,8 @@ .webdav-hide { display: none; } + +.qrcode-img { + width: 350px; + height: 350px; +}