add qrcode export import

This commit is contained in:
fyears 2021-12-11 17:33:55 +08:00
parent 563c2c3ff6
commit 8624141780
9 changed files with 255 additions and 53 deletions

View File

@ -25,6 +25,7 @@
"@types/mime-types": "^2.1.1", "@types/mime-types": "^2.1.1",
"@types/mocha": "^9.0.0", "@types/mocha": "^9.0.0",
"@types/node": "^14.14.37", "@types/node": "^14.14.37",
"@types/qrcode": "^1.4.1",
"builtin-modules": "^3.2.0", "builtin-modules": "^3.2.0",
"chai": "^4.3.4", "chai": "^4.3.4",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
@ -57,6 +58,7 @@
"obsidian": "^0.12.0", "obsidian": "^0.12.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"process": "^0.11.10", "process": "^0.11.10",
"qrcode": "^1.5.0",
"rfc4648": "^1.5.0", "rfc4648": "^1.5.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",

View File

@ -1,9 +1,45 @@
/** /**
* Only type defs here. * Only type defs here.
* To avoid circular dependency.
*/ */
export type SUPPORTED_SERVICES_TYPE = "s3" | "webdav" | "dropbox"; 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 { export interface RemoteItem {
key: string; key: string;
lastModified: number; lastModified: number;
@ -11,3 +47,13 @@ export interface RemoteItem {
remoteType: SUPPORTED_SERVICES_TYPE; remoteType: SUPPORTED_SERVICES_TYPE;
etag?: string; 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;
}

76
src/importExport.ts Normal file
View File

@ -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,
};
};

View File

@ -27,14 +27,9 @@ import type { InternalDBs } from "./localdb";
import type { SyncStatusType, PasswordCheckType } from "./sync"; import type { SyncStatusType, PasswordCheckType } from "./sync";
import { isPasswordOk, getSyncPlan, doActualSync } 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 { import {
WebdavConfig,
DEFAULT_WEBDAV_CONFIG,
WebdavAuthType,
} from "./remoteForWebdav";
import {
DropboxConfig,
DEFAULT_DROPBOX_CONFIG, DEFAULT_DROPBOX_CONFIG,
getAuthUrlAndVerifier, getAuthUrlAndVerifier,
sendAuthReq, sendAuthReq,
@ -43,15 +38,17 @@ import {
import { RemoteClient } from "./remote"; import { RemoteClient } from "./remote";
import { exportSyncPlansToFiles } from "./debugMode"; import { exportSyncPlansToFiles } from "./debugMode";
import { SUPPORTED_SERVICES_TYPE } from "./baseTypes"; import { COMMAND_URI, COMMAND_CALLBACK } from "./baseTypes";
import type {
interface RemotelySavePluginSettings { SUPPORTED_SERVICES_TYPE,
s3: S3Config; S3Config,
webdav: WebdavConfig; DropboxConfig,
dropbox: DropboxConfig; WebdavAuthType,
password: string; WebdavConfig,
serviceType: SUPPORTED_SERVICES_TYPE; RemotelySavePluginSettings,
} } from "./baseTypes";
import type { ProcessQrCodeResultType } from "./importExport";
import { exportQrCodeUri, importQrCodeUri } from "./importExport";
const DEFAULT_SETTINGS: RemotelySavePluginSettings = { const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
s3: DEFAULT_S3_CONFIG, 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 () => { this.addRibbonIcon("switch", "Remotely Save", async () => {
if (this.syncStatus !== "idle") { if (this.syncStatus !== "idle") {
new Notice( 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 { class RemotelySaveSettingTab extends PluginSettingTab {
plugin: RemotelySavePlugin; 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"); const debugDiv = containerEl.createEl("div");
debugDiv.createEl("h2", { text: "Debug" }); debugDiv.createEl("h2", { text: "Debug" });
const syncPlanDiv = debugDiv.createEl("div"); const syncPlanDiv = debugDiv.createEl("div");

View File

@ -1,6 +1,11 @@
import { Vault } from "obsidian"; 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 s3 from "./remoteForS3";
import * as webdav from "./remoteForWebdav"; import * as webdav from "./remoteForWebdav";
import * as dropbox from "./remoteForDropbox"; import * as dropbox from "./remoteForDropbox";
@ -8,17 +13,17 @@ import * as dropbox from "./remoteForDropbox";
export class RemoteClient { export class RemoteClient {
readonly serviceType: SUPPORTED_SERVICES_TYPE; readonly serviceType: SUPPORTED_SERVICES_TYPE;
readonly s3Client?: s3.S3Client; readonly s3Client?: s3.S3Client;
readonly s3Config?: s3.S3Config; readonly s3Config?: S3Config;
readonly webdavClient?: webdav.WebDAVClient; readonly webdavClient?: webdav.WebDAVClient;
readonly webdavConfig?: webdav.WebdavConfig; readonly webdavConfig?: WebdavConfig;
readonly dropboxClient?: dropbox.WrappedDropboxClient; readonly dropboxClient?: dropbox.WrappedDropboxClient;
readonly dropboxConfig?: dropbox.DropboxConfig; readonly dropboxConfig?: DropboxConfig;
constructor( constructor(
serviceType: SUPPORTED_SERVICES_TYPE, serviceType: SUPPORTED_SERVICES_TYPE,
s3Config?: s3.S3Config, s3Config?: S3Config,
webdavConfig?: webdav.WebdavConfig, webdavConfig?: WebdavConfig,
dropboxConfig?: dropbox.DropboxConfig, dropboxConfig?: DropboxConfig,
vaultName?: string, vaultName?: string,
saveUpdatedConfigFunc?: () => Promise<any> saveUpdatedConfigFunc?: () => Promise<any>
) { ) {

View File

@ -3,7 +3,7 @@ import { FileStats, Vault } from "obsidian";
import { Dropbox, DropboxAuth, DropboxResponse, files } from "dropbox"; import { Dropbox, DropboxAuth, DropboxResponse, files } from "dropbox";
export { Dropbox } from "dropbox"; export { Dropbox } from "dropbox";
import { RemoteItem } from "./baseTypes"; import { RemoteItem, DropboxConfig } from "./baseTypes";
import { import {
arrayBufferToBuffer, arrayBufferToBuffer,
bufferToArrayBuffer, bufferToArrayBuffer,
@ -14,16 +14,6 @@ import {
} from "./misc"; } from "./misc";
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt"; 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 = { export const DEFAULT_DROPBOX_CONFIG = {
accessToken: "", accessToken: "",
clientID: process.env.DEFAULT_DROPBOX_APP_KEY, clientID: process.env.DEFAULT_DROPBOX_APP_KEY,

View File

@ -27,17 +27,9 @@ import {
} from "./misc"; } from "./misc";
import * as mime from "mime-types"; import * as mime from "mime-types";
import { RemoteItem } from "./baseTypes"; import { RemoteItem, S3Config } from "./baseTypes";
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt"; import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
export interface S3Config {
s3Endpoint: string;
s3Region: string;
s3AccessKeyID: string;
s3SecretAccessKey: string;
s3BucketName: string;
}
export const DEFAULT_S3_CONFIG = { export const DEFAULT_S3_CONFIG = {
s3Endpoint: "", s3Endpoint: "",
s3Region: "", s3Region: "",

View File

@ -4,7 +4,7 @@ import { AuthType, BufferLike, createClient } from "webdav/web";
import type { WebDAVClient, ResponseDataDetailed, FileStat } from "webdav/web"; import type { WebDAVClient, ResponseDataDetailed, FileStat } from "webdav/web";
export type { WebDAVClient } from "webdav/web"; export type { WebDAVClient } from "webdav/web";
import type { RemoteItem } from "./baseTypes"; import type { RemoteItem, WebdavAuthType, WebdavConfig } from "./baseTypes";
import { import {
arrayBufferToBuffer, arrayBufferToBuffer,
@ -14,15 +14,6 @@ import {
} from "./misc"; } from "./misc";
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt"; 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 = { export const DEFAULT_WEBDAV_CONFIG = {
address: "", address: "",
username: "", username: "",

View File

@ -32,3 +32,8 @@
.webdav-hide { .webdav-hide {
display: none; display: none;
} }
.qrcode-img {
width: 350px;
height: 350px;
}