basically working onedrive
This commit is contained in:
parent
f3e6bc2fdd
commit
6552fd32d1
@ -12,6 +12,8 @@ if you want to view the source, please visit the github repository of this plugi
|
|||||||
const prod = process.argv[2] === "production";
|
const prod = process.argv[2] === "production";
|
||||||
|
|
||||||
const DEFAULT_DROPBOX_APP_KEY = process.env.DROPBOX_APP_KEY || "";
|
const DEFAULT_DROPBOX_APP_KEY = process.env.DROPBOX_APP_KEY || "";
|
||||||
|
const DEFAULT_ONEDRIVE_CLIENT_ID = process.env.ONEDRIVE_CLIENT_ID || "";
|
||||||
|
const DEFAULT_ONEDRIVE_AUTHORITY = process.env.ONEDRIVE_AUTHORITY || "";
|
||||||
|
|
||||||
esbuild
|
esbuild
|
||||||
.build({
|
.build({
|
||||||
@ -40,6 +42,8 @@ esbuild
|
|||||||
outfile: "main.js",
|
outfile: "main.js",
|
||||||
define: {
|
define: {
|
||||||
"process.env.DEFAULT_DROPBOX_APP_KEY": `"${DEFAULT_DROPBOX_APP_KEY}"`,
|
"process.env.DEFAULT_DROPBOX_APP_KEY": `"${DEFAULT_DROPBOX_APP_KEY}"`,
|
||||||
|
"process.env.DEFAULT_ONEDRIVE_CLIENT_ID": `"${DEFAULT_ONEDRIVE_CLIENT_ID}"`,
|
||||||
|
"process.env.DEFAULT_ONEDRIVE_AUTHORITY": `"${DEFAULT_ONEDRIVE_AUTHORITY}"`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.catch(() => process.exit(1));
|
.catch(() => process.exit(1));
|
||||||
|
|||||||
@ -13,13 +13,15 @@
|
|||||||
"browser": {
|
"browser": {
|
||||||
"path": "path-browserify",
|
"path": "path-browserify",
|
||||||
"process": "process/browser",
|
"process": "process/browser",
|
||||||
"stream": "stream-browserify"
|
"stream": "stream-browserify",
|
||||||
|
"crypto": "crypto-browserify"
|
||||||
},
|
},
|
||||||
"source": "main.ts",
|
"source": "main.ts",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@microsoft/microsoft-graph-types": "^2.11.0",
|
||||||
"@types/chai": "^4.2.22",
|
"@types/chai": "^4.2.22",
|
||||||
"@types/chai-as-promised": "^7.1.4",
|
"@types/chai-as-promised": "^7.1.4",
|
||||||
"@types/jsdom": "^16.2.13",
|
"@types/jsdom": "^16.2.13",
|
||||||
@ -48,6 +50,8 @@
|
|||||||
"@aws-sdk/client-s3": "^3.37.0",
|
"@aws-sdk/client-s3": "^3.37.0",
|
||||||
"@aws-sdk/lib-storage": "^3.40.1",
|
"@aws-sdk/lib-storage": "^3.40.1",
|
||||||
"@aws-sdk/signature-v4-crt": "^3.37.0",
|
"@aws-sdk/signature-v4-crt": "^3.37.0",
|
||||||
|
"@azure/msal-node": "^1.4.0",
|
||||||
|
"@microsoft/microsoft-graph-client": "^3.0.1",
|
||||||
"acorn": "^8.5.0",
|
"acorn": "^8.5.0",
|
||||||
"assert": "^2.0.0",
|
"assert": "^2.0.0",
|
||||||
"aws-crt": "^1.10.1",
|
"aws-crt": "^1.10.1",
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
* To avoid circular dependency.
|
* To avoid circular dependency.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type SUPPORTED_SERVICES_TYPE = "s3" | "webdav" | "dropbox";
|
export type SUPPORTED_SERVICES_TYPE = "s3" | "webdav" | "dropbox" | "onedrive";
|
||||||
|
|
||||||
export interface S3Config {
|
export interface S3Config {
|
||||||
s3Endpoint: string;
|
s3Endpoint: string;
|
||||||
@ -32,10 +32,22 @@ export interface WebdavConfig {
|
|||||||
authType: WebdavAuthType;
|
authType: WebdavAuthType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OnedriveConfig {
|
||||||
|
accessToken: string;
|
||||||
|
clientID: string;
|
||||||
|
authority: string;
|
||||||
|
refreshToken: string;
|
||||||
|
accessTokenExpiresInSeconds: number;
|
||||||
|
accessTokenExpiresAtTime: number;
|
||||||
|
deltaLink: string;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RemotelySavePluginSettings {
|
export interface RemotelySavePluginSettings {
|
||||||
s3: S3Config;
|
s3: S3Config;
|
||||||
webdav: WebdavConfig;
|
webdav: WebdavConfig;
|
||||||
dropbox: DropboxConfig;
|
dropbox: DropboxConfig;
|
||||||
|
onedrive: OnedriveConfig;
|
||||||
password: string;
|
password: string;
|
||||||
serviceType: SUPPORTED_SERVICES_TYPE;
|
serviceType: SUPPORTED_SERVICES_TYPE;
|
||||||
}
|
}
|
||||||
@ -50,6 +62,7 @@ export interface RemoteItem {
|
|||||||
|
|
||||||
export const COMMAND_URI = "remotely-save";
|
export const COMMAND_URI = "remotely-save";
|
||||||
export const COMMAND_CALLBACK = "remotely-save-cb";
|
export const COMMAND_CALLBACK = "remotely-save-cb";
|
||||||
|
export const COMMAND_CALLBACK_ONEDRIVE = "remotely-save-cb-onedrive";
|
||||||
|
|
||||||
export interface UriParams {
|
export interface UriParams {
|
||||||
func?: string;
|
func?: string;
|
||||||
|
|||||||
287
src/main.ts
287
src/main.ts
@ -31,8 +31,8 @@ import { DEFAULT_S3_CONFIG } from "./remoteForS3";
|
|||||||
import { DEFAULT_WEBDAV_CONFIG } from "./remoteForWebdav";
|
import { DEFAULT_WEBDAV_CONFIG } from "./remoteForWebdav";
|
||||||
import {
|
import {
|
||||||
DEFAULT_DROPBOX_CONFIG,
|
DEFAULT_DROPBOX_CONFIG,
|
||||||
getAuthUrlAndVerifier,
|
getAuthUrlAndVerifier as getAuthUrlAndVerifierDropbox,
|
||||||
sendAuthReq,
|
sendAuthReq as sendAuthReqDropbox,
|
||||||
setConfigBySuccessfullAuthInplace,
|
setConfigBySuccessfullAuthInplace,
|
||||||
} from "./remoteForDropbox";
|
} from "./remoteForDropbox";
|
||||||
|
|
||||||
@ -50,23 +50,49 @@ import type {
|
|||||||
import type { ProcessQrCodeResultType } from "./importExport";
|
import type { ProcessQrCodeResultType } from "./importExport";
|
||||||
import { exportQrCodeUri, importQrCodeUri } from "./importExport";
|
import { exportQrCodeUri, importQrCodeUri } from "./importExport";
|
||||||
|
|
||||||
|
import {
|
||||||
|
getAuthUrlAndVerifier as getAuthUrlAndVerifierOnedrive,
|
||||||
|
sendAuthReq as sendAuthReqOnedrive,
|
||||||
|
DEFAULT_ONEDRIVE_CONFIG,
|
||||||
|
WrappedOnedriveClient,
|
||||||
|
AccessCodeResponseSuccessfulType,
|
||||||
|
} from "./remoteForOnedrive";
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
||||||
s3: DEFAULT_S3_CONFIG,
|
s3: DEFAULT_S3_CONFIG,
|
||||||
webdav: DEFAULT_WEBDAV_CONFIG,
|
webdav: DEFAULT_WEBDAV_CONFIG,
|
||||||
dropbox: DEFAULT_DROPBOX_CONFIG,
|
dropbox: DEFAULT_DROPBOX_CONFIG,
|
||||||
|
onedrive: DEFAULT_ONEDRIVE_CONFIG,
|
||||||
password: "",
|
password: "",
|
||||||
serviceType: "s3",
|
serviceType: "s3",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface OAuth2Info {
|
||||||
|
verifier?: string;
|
||||||
|
helperModal?: Modal;
|
||||||
|
authDiv?: HTMLElement;
|
||||||
|
revokeDiv?: HTMLElement;
|
||||||
|
revokeAuthSetting?: Setting;
|
||||||
|
}
|
||||||
|
|
||||||
export default class RemotelySavePlugin extends Plugin {
|
export default class RemotelySavePlugin extends Plugin {
|
||||||
settings: RemotelySavePluginSettings;
|
settings: RemotelySavePluginSettings;
|
||||||
// cm: CodeMirror.Editor;
|
// cm: CodeMirror.Editor;
|
||||||
db: InternalDBs;
|
db: InternalDBs;
|
||||||
syncStatus: SyncStatusType;
|
syncStatus: SyncStatusType;
|
||||||
|
oauth2Info: OAuth2Info;
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
console.log(`loading plugin ${this.manifest.id}`);
|
console.log(`loading plugin ${this.manifest.id}`);
|
||||||
|
|
||||||
|
this.oauth2Info = {
|
||||||
|
verifier: "",
|
||||||
|
helperModal: undefined,
|
||||||
|
authDiv: undefined,
|
||||||
|
revokeDiv: undefined,
|
||||||
|
revokeAuthSetting: undefined,
|
||||||
|
}; // init
|
||||||
|
|
||||||
await this.loadSettings();
|
await this.loadSettings();
|
||||||
|
|
||||||
await this.prepareDB();
|
await this.prepareDB();
|
||||||
@ -110,6 +136,77 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
this.registerObsidianProtocolHandler(
|
||||||
|
"remotely-save-cb-onedrive",
|
||||||
|
async (inputParams) => {
|
||||||
|
if (inputParams.code !== undefined) {
|
||||||
|
let rsp = await sendAuthReqOnedrive(
|
||||||
|
this.settings.onedrive.clientID,
|
||||||
|
this.settings.onedrive.authority,
|
||||||
|
inputParams.code,
|
||||||
|
this.oauth2Info.verifier
|
||||||
|
);
|
||||||
|
|
||||||
|
if ((rsp as any).error !== undefined) {
|
||||||
|
throw Error(`${JSON.stringify(rsp)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.oauth2Info.helperModal !== undefined) {
|
||||||
|
this.oauth2Info.helperModal.contentEl.empty();
|
||||||
|
this.oauth2Info.helperModal.contentEl.createEl("p", {
|
||||||
|
text: "Please wait, the plugin is trying to connect to Onedrive...",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rsp = rsp as AccessCodeResponseSuccessfulType;
|
||||||
|
this.settings.onedrive.accessToken = rsp.access_token;
|
||||||
|
this.settings.onedrive.accessTokenExpiresAtTime =
|
||||||
|
Date.now() + rsp.expires_in - 5 * 60 * 1000;
|
||||||
|
this.settings.onedrive.accessTokenExpiresInSeconds = rsp.expires_in;
|
||||||
|
this.settings.onedrive.refreshToken = rsp.refresh_token;
|
||||||
|
this.saveSettings();
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
const client = new RemoteClient(
|
||||||
|
"onedrive",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
this.settings.onedrive,
|
||||||
|
this.app.vault.getName(),
|
||||||
|
() => self.saveSettings()
|
||||||
|
);
|
||||||
|
this.settings.onedrive.username = await client.getUser();
|
||||||
|
this.saveSettings();
|
||||||
|
|
||||||
|
this.oauth2Info.verifier = ""; // reset it
|
||||||
|
this.oauth2Info.helperModal?.close(); // close it
|
||||||
|
this.oauth2Info.helperModal = undefined;
|
||||||
|
|
||||||
|
this.oauth2Info.authDiv?.toggleClass(
|
||||||
|
"onedrive-auth-button-hide",
|
||||||
|
this.settings.onedrive.username !== ""
|
||||||
|
);
|
||||||
|
this.oauth2Info.authDiv = undefined;
|
||||||
|
|
||||||
|
this.oauth2Info.revokeAuthSetting?.setDesc(
|
||||||
|
`You've connected as user ${this.settings.dropbox.username}. If you want to disconnect, click this button.`
|
||||||
|
);
|
||||||
|
this.oauth2Info.revokeAuthSetting = undefined;
|
||||||
|
this.oauth2Info.revokeDiv?.toggleClass(
|
||||||
|
"onedrive-revoke-auth-button-hide",
|
||||||
|
this.settings.onedrive.username === ""
|
||||||
|
);
|
||||||
|
this.oauth2Info.revokeDiv = undefined;
|
||||||
|
} else {
|
||||||
|
throw Error(
|
||||||
|
`do not know how to deal with the callback: ${JSON.stringify(
|
||||||
|
inputParams
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
this.addRibbonIcon("switch", "Remotely Save", async () => {
|
this.addRibbonIcon("switch", "Remotely Save", async () => {
|
||||||
if (this.syncStatus !== "idle") {
|
if (this.syncStatus !== "idle") {
|
||||||
@ -134,6 +231,7 @@ export default class RemotelySavePlugin extends Plugin {
|
|||||||
this.settings.s3,
|
this.settings.s3,
|
||||||
this.settings.webdav,
|
this.settings.webdav,
|
||||||
this.settings.dropbox,
|
this.settings.dropbox,
|
||||||
|
this.settings.onedrive,
|
||||||
this.app.vault.getName(),
|
this.app.vault.getName(),
|
||||||
() => self.saveSettings()
|
() => self.saveSettings()
|
||||||
);
|
);
|
||||||
@ -325,7 +423,7 @@ export class DropboxAuthModal extends Modal {
|
|||||||
async onOpen() {
|
async onOpen() {
|
||||||
let { contentEl } = this;
|
let { contentEl } = this;
|
||||||
|
|
||||||
const { authUrl, verifier } = await getAuthUrlAndVerifier(
|
const { authUrl, verifier } = await getAuthUrlAndVerifierDropbox(
|
||||||
this.plugin.settings.dropbox.clientID
|
this.plugin.settings.dropbox.clientID
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -358,7 +456,7 @@ export class DropboxAuthModal extends Modal {
|
|||||||
button.onClick(async () => {
|
button.onClick(async () => {
|
||||||
new Notice("Trying to connect to Dropbox");
|
new Notice("Trying to connect to Dropbox");
|
||||||
try {
|
try {
|
||||||
const authRes = await sendAuthReq(
|
const authRes = await sendAuthReqDropbox(
|
||||||
this.plugin.settings.dropbox.clientID,
|
this.plugin.settings.dropbox.clientID,
|
||||||
verifier,
|
verifier,
|
||||||
authCode
|
authCode
|
||||||
@ -374,6 +472,7 @@ export class DropboxAuthModal extends Modal {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
this.plugin.settings.dropbox,
|
this.plugin.settings.dropbox,
|
||||||
|
undefined,
|
||||||
this.app.vault.getName(),
|
this.app.vault.getName(),
|
||||||
() => self.plugin.saveSettings()
|
() => self.plugin.saveSettings()
|
||||||
);
|
);
|
||||||
@ -407,6 +506,52 @@ export class DropboxAuthModal extends Modal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class OnedriveAuthModal extends Modal {
|
||||||
|
readonly plugin: RemotelySavePlugin;
|
||||||
|
readonly authDiv: HTMLDivElement;
|
||||||
|
readonly revokeAuthDiv: HTMLDivElement;
|
||||||
|
readonly revokeAuthSetting: Setting;
|
||||||
|
constructor(
|
||||||
|
app: App,
|
||||||
|
plugin: RemotelySavePlugin,
|
||||||
|
authDiv: HTMLDivElement,
|
||||||
|
revokeAuthDiv: HTMLDivElement,
|
||||||
|
revokeAuthSetting: Setting
|
||||||
|
) {
|
||||||
|
super(app);
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.authDiv = authDiv;
|
||||||
|
this.revokeAuthDiv = revokeAuthDiv;
|
||||||
|
this.revokeAuthSetting = revokeAuthSetting;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onOpen() {
|
||||||
|
let { contentEl } = this;
|
||||||
|
|
||||||
|
const { authUrl, verifier } = await getAuthUrlAndVerifierOnedrive(
|
||||||
|
this.plugin.settings.onedrive.clientID,
|
||||||
|
this.plugin.settings.onedrive.authority
|
||||||
|
);
|
||||||
|
this.plugin.oauth2Info.verifier = verifier;
|
||||||
|
|
||||||
|
contentEl.createEl("p", {
|
||||||
|
text: "Visit the address in a browser, and follow the steps.",
|
||||||
|
});
|
||||||
|
contentEl.createEl("p", {
|
||||||
|
text: "Finally you should be redirected to Obsidian.",
|
||||||
|
});
|
||||||
|
contentEl.createEl("p").createEl("a", {
|
||||||
|
href: authUrl,
|
||||||
|
text: authUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose() {
|
||||||
|
let { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ExportSettingsQrCodeModal extends Modal {
|
export class ExportSettingsQrCodeModal extends Modal {
|
||||||
plugin: RemotelySavePlugin;
|
plugin: RemotelySavePlugin;
|
||||||
constructor(app: App, plugin: RemotelySavePlugin) {
|
constructor(app: App, plugin: RemotelySavePlugin) {
|
||||||
@ -617,11 +762,7 @@ class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
button.setButtonText("Check");
|
button.setButtonText("Check");
|
||||||
button.onClick(async () => {
|
button.onClick(async () => {
|
||||||
new Notice("Checking...");
|
new Notice("Checking...");
|
||||||
const client = new RemoteClient(
|
const client = new RemoteClient("s3", this.plugin.settings.s3);
|
||||||
"s3",
|
|
||||||
this.plugin.settings.s3,
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
const res = await client.checkConnectivity();
|
const res = await client.checkConnectivity();
|
||||||
if (res) {
|
if (res) {
|
||||||
new Notice("Great! The bucket can be accessed.");
|
new Notice("Great! The bucket can be accessed.");
|
||||||
@ -659,7 +800,7 @@ class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
cls: "dropbox-revoke-auth-button-hide",
|
cls: "dropbox-revoke-auth-button-hide",
|
||||||
});
|
});
|
||||||
|
|
||||||
const revokeAuthSetting = new Setting(dropboxRevokeAuthDiv)
|
const dropboxRevokeAuthSetting = new Setting(dropboxRevokeAuthDiv)
|
||||||
.setName("Revoke Auth")
|
.setName("Revoke Auth")
|
||||||
.setDesc(
|
.setDesc(
|
||||||
`You've connected as user ${this.plugin.settings.dropbox.username}. If you want to disconnect, click this button`
|
`You've connected as user ${this.plugin.settings.dropbox.username}. If you want to disconnect, click this button`
|
||||||
@ -674,6 +815,7 @@ class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
this.plugin.settings.dropbox,
|
this.plugin.settings.dropbox,
|
||||||
|
undefined,
|
||||||
this.app.vault.getName(),
|
this.app.vault.getName(),
|
||||||
() => self.plugin.saveSettings()
|
() => self.plugin.saveSettings()
|
||||||
);
|
);
|
||||||
@ -709,7 +851,7 @@ class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
this.plugin,
|
this.plugin,
|
||||||
dropboxAuthDiv,
|
dropboxAuthDiv,
|
||||||
dropboxRevokeAuthDiv,
|
dropboxRevokeAuthDiv,
|
||||||
revokeAuthSetting
|
dropboxRevokeAuthSetting
|
||||||
).open();
|
).open();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -736,6 +878,7 @@ class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
this.plugin.settings.dropbox,
|
this.plugin.settings.dropbox,
|
||||||
|
undefined,
|
||||||
this.app.vault.getName(),
|
this.app.vault.getName(),
|
||||||
() => self.plugin.saveSettings()
|
() => self.plugin.saveSettings()
|
||||||
);
|
);
|
||||||
@ -749,6 +892,120 @@ class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const onedriveDiv = containerEl.createEl("div", { cls: "onedrive-hide" });
|
||||||
|
onedriveDiv.toggleClass(
|
||||||
|
"onedrive-hide",
|
||||||
|
this.plugin.settings.serviceType !== "onedrive"
|
||||||
|
);
|
||||||
|
onedriveDiv.createEl("h2", { text: "Remote For Onedrive" });
|
||||||
|
onedriveDiv.createEl("p", {
|
||||||
|
text: "Disclaimer: This app is NOT an official Onedrive product.",
|
||||||
|
cls: "onedrive-disclaimer",
|
||||||
|
});
|
||||||
|
onedriveDiv.createEl("p", {
|
||||||
|
text: "Disclaimer: The information is stored in PLAIN TEXT locally. Other malicious/harmful/faulty plugins could read the info. If you see any unintentional access to your Onedrive, please immediately disconnect this app on https://microsoft.com/consent .",
|
||||||
|
cls: "onedrive-disclaimer",
|
||||||
|
});
|
||||||
|
onedriveDiv.createEl("p", {
|
||||||
|
text: `We will create and sync inside the folder /Apps/${
|
||||||
|
this.plugin.manifest.id
|
||||||
|
}/${this.app.vault.getName()} on your Onedrive.`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onedriveSelectAuthDiv = onedriveDiv.createDiv();
|
||||||
|
const onedriveAuthDiv = onedriveSelectAuthDiv.createDiv({
|
||||||
|
cls: "onedrive-auth-button-hide",
|
||||||
|
});
|
||||||
|
const onedriveRevokeAuthDiv = onedriveSelectAuthDiv.createDiv({
|
||||||
|
cls: "onedrive-revoke-auth-button-hide",
|
||||||
|
});
|
||||||
|
|
||||||
|
const onedriveRevokeAuthSetting = new Setting(onedriveRevokeAuthDiv)
|
||||||
|
.setName("Revoke Auth")
|
||||||
|
.setDesc(
|
||||||
|
`You've connected as user ${this.plugin.settings.onedrive.username}. If you want to disconnect, click this button`
|
||||||
|
)
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText("Revoke Auth");
|
||||||
|
button.onClick(async () => {
|
||||||
|
try {
|
||||||
|
this.plugin.settings.onedrive = JSON.parse(
|
||||||
|
JSON.stringify(DEFAULT_ONEDRIVE_CONFIG)
|
||||||
|
);
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
onedriveAuthDiv.toggleClass(
|
||||||
|
"onedrive-auth-button-hide",
|
||||||
|
this.plugin.settings.onedrive.username !== ""
|
||||||
|
);
|
||||||
|
onedriveRevokeAuthDiv.toggleClass(
|
||||||
|
"onedrive-revoke-auth-button-hide",
|
||||||
|
this.plugin.settings.onedrive.username === ""
|
||||||
|
);
|
||||||
|
new Notice("Revoked!");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
new Notice("Something goes wrong while revoking");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Setting(onedriveAuthDiv)
|
||||||
|
.setName("Auth")
|
||||||
|
.setDesc("Auth")
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText("Auth");
|
||||||
|
button.onClick(async () => {
|
||||||
|
const modal = new OnedriveAuthModal(
|
||||||
|
this.app,
|
||||||
|
this.plugin,
|
||||||
|
onedriveAuthDiv,
|
||||||
|
onedriveRevokeAuthDiv,
|
||||||
|
onedriveRevokeAuthSetting
|
||||||
|
);
|
||||||
|
this.plugin.oauth2Info.helperModal = modal;
|
||||||
|
this.plugin.oauth2Info.authDiv = onedriveAuthDiv;
|
||||||
|
this.plugin.oauth2Info.revokeDiv = onedriveRevokeAuthDiv;
|
||||||
|
this.plugin.oauth2Info.revokeAuthSetting = onedriveRevokeAuthSetting;
|
||||||
|
modal.open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onedriveAuthDiv.toggleClass(
|
||||||
|
"onedrive-auth-button-hide",
|
||||||
|
this.plugin.settings.onedrive.username !== ""
|
||||||
|
);
|
||||||
|
onedriveRevokeAuthDiv.toggleClass(
|
||||||
|
"onedrive-revoke-auth-button-hide",
|
||||||
|
this.plugin.settings.onedrive.username === ""
|
||||||
|
);
|
||||||
|
|
||||||
|
new Setting(onedriveDiv)
|
||||||
|
.setName("check connectivity")
|
||||||
|
.setDesc("check connectivity")
|
||||||
|
.addButton(async (button) => {
|
||||||
|
button.setButtonText("Check");
|
||||||
|
button.onClick(async () => {
|
||||||
|
new Notice("Checking...");
|
||||||
|
const self = this;
|
||||||
|
const client = new RemoteClient(
|
||||||
|
"onedrive",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
this.plugin.settings.onedrive,
|
||||||
|
this.app.vault.getName(),
|
||||||
|
() => self.plugin.saveSettings()
|
||||||
|
);
|
||||||
|
|
||||||
|
const res = await client.checkConnectivity();
|
||||||
|
if (res) {
|
||||||
|
new Notice("Great! We can connect to Onedrive!");
|
||||||
|
} else {
|
||||||
|
new Notice("We cannot connect to Onedrive.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const webdavDiv = containerEl.createEl("div", { cls: "webdav-hide" });
|
const webdavDiv = containerEl.createEl("div", { cls: "webdav-hide" });
|
||||||
webdavDiv.toggleClass(
|
webdavDiv.toggleClass(
|
||||||
"webdav-hide",
|
"webdav-hide",
|
||||||
@ -836,6 +1093,7 @@ class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
undefined,
|
undefined,
|
||||||
this.plugin.settings.webdav,
|
this.plugin.settings.webdav,
|
||||||
undefined,
|
undefined,
|
||||||
|
undefined,
|
||||||
this.app.vault.getName()
|
this.app.vault.getName()
|
||||||
);
|
);
|
||||||
const res = await client.checkConnectivity();
|
const res = await client.checkConnectivity();
|
||||||
@ -853,11 +1111,10 @@ class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
.setName("Choose service")
|
.setName("Choose service")
|
||||||
.setDesc("Choose a service.")
|
.setDesc("Choose a service.")
|
||||||
.addDropdown(async (dropdown) => {
|
.addDropdown(async (dropdown) => {
|
||||||
const currService = this.plugin.settings.serviceType;
|
|
||||||
|
|
||||||
dropdown.addOption("s3", "S3 (-compatible)");
|
dropdown.addOption("s3", "S3 (-compatible)");
|
||||||
dropdown.addOption("dropbox", "Dropbox");
|
dropdown.addOption("dropbox", "Dropbox");
|
||||||
dropdown.addOption("webdav", "Webdav");
|
dropdown.addOption("webdav", "Webdav");
|
||||||
|
dropdown.addOption("onedrive", "OneDrive (alpha)");
|
||||||
dropdown
|
dropdown
|
||||||
.setValue(this.plugin.settings.serviceType)
|
.setValue(this.plugin.settings.serviceType)
|
||||||
.onChange(async (val: SUPPORTED_SERVICES_TYPE) => {
|
.onChange(async (val: SUPPORTED_SERVICES_TYPE) => {
|
||||||
@ -870,6 +1127,10 @@ class RemotelySaveSettingTab extends PluginSettingTab {
|
|||||||
"dropbox-hide",
|
"dropbox-hide",
|
||||||
this.plugin.settings.serviceType !== "dropbox"
|
this.plugin.settings.serviceType !== "dropbox"
|
||||||
);
|
);
|
||||||
|
onedriveDiv.toggleClass(
|
||||||
|
"onedrive-hide",
|
||||||
|
this.plugin.settings.serviceType !== "onedrive"
|
||||||
|
);
|
||||||
webdavDiv.toggleClass(
|
webdavDiv.toggleClass(
|
||||||
"webdav-hide",
|
"webdav-hide",
|
||||||
this.plugin.settings.serviceType !== "webdav"
|
this.plugin.settings.serviceType !== "webdav"
|
||||||
|
|||||||
25
src/misc.ts
25
src/misc.ts
@ -163,3 +163,28 @@ export const extractSvgSub = (x: string, subEl: string = "rect") => {
|
|||||||
svg.setAttribute("viewbox", "0 0 10 10");
|
svg.setAttribute("viewbox", "0 0 10 10");
|
||||||
return svg.innerHTML;
|
return svg.innerHTML;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://stackoverflow.com/questions/18230217
|
||||||
|
* @param min
|
||||||
|
* @param max
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getRandomIntInclusive = (min: number, max: number) => {
|
||||||
|
const randomBuffer = new Uint32Array(1);
|
||||||
|
window.crypto.getRandomValues(randomBuffer);
|
||||||
|
let randomNumber = randomBuffer[0] / (0xffffffff + 1);
|
||||||
|
min = Math.ceil(min);
|
||||||
|
max = Math.floor(max);
|
||||||
|
return Math.floor(randomNumber * (max - min + 1)) + min;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Random buffer
|
||||||
|
* @param byteLength
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getRandomArrayBuffer = (byteLength: number) => {
|
||||||
|
const k = window.crypto.getRandomValues(new Uint8Array(byteLength));
|
||||||
|
return bufferToArrayBuffer(k);
|
||||||
|
};
|
||||||
|
|||||||
@ -5,10 +5,12 @@ import type {
|
|||||||
S3Config,
|
S3Config,
|
||||||
DropboxConfig,
|
DropboxConfig,
|
||||||
WebdavConfig,
|
WebdavConfig,
|
||||||
|
OnedriveConfig,
|
||||||
} from "./baseTypes";
|
} 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";
|
||||||
|
import * as onedrive from "./remoteForOnedrive";
|
||||||
|
|
||||||
export class RemoteClient {
|
export class RemoteClient {
|
||||||
readonly serviceType: SUPPORTED_SERVICES_TYPE;
|
readonly serviceType: SUPPORTED_SERVICES_TYPE;
|
||||||
@ -18,12 +20,15 @@ export class RemoteClient {
|
|||||||
readonly webdavConfig?: WebdavConfig;
|
readonly webdavConfig?: WebdavConfig;
|
||||||
readonly dropboxClient?: dropbox.WrappedDropboxClient;
|
readonly dropboxClient?: dropbox.WrappedDropboxClient;
|
||||||
readonly dropboxConfig?: DropboxConfig;
|
readonly dropboxConfig?: DropboxConfig;
|
||||||
|
readonly onedriveClient?: onedrive.WrappedOnedriveClient;
|
||||||
|
readonly onedriveConfig?: OnedriveConfig;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
serviceType: SUPPORTED_SERVICES_TYPE,
|
serviceType: SUPPORTED_SERVICES_TYPE,
|
||||||
s3Config?: S3Config,
|
s3Config?: S3Config,
|
||||||
webdavConfig?: WebdavConfig,
|
webdavConfig?: WebdavConfig,
|
||||||
dropboxConfig?: DropboxConfig,
|
dropboxConfig?: DropboxConfig,
|
||||||
|
onedriveConfig?: OnedriveConfig,
|
||||||
vaultName?: string,
|
vaultName?: string,
|
||||||
saveUpdatedConfigFunc?: () => Promise<any>
|
saveUpdatedConfigFunc?: () => Promise<any>
|
||||||
) {
|
) {
|
||||||
@ -51,6 +56,18 @@ export class RemoteClient {
|
|||||||
vaultName,
|
vaultName,
|
||||||
saveUpdatedConfigFunc
|
saveUpdatedConfigFunc
|
||||||
);
|
);
|
||||||
|
} else if (serviceType === "onedrive") {
|
||||||
|
if (vaultName === undefined || saveUpdatedConfigFunc === undefined) {
|
||||||
|
throw Error(
|
||||||
|
"remember to provide vault name and callback while init onedrive client"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.onedriveConfig = onedriveConfig;
|
||||||
|
this.onedriveClient = onedrive.getOnedriveClient(
|
||||||
|
this.onedriveConfig,
|
||||||
|
vaultName,
|
||||||
|
saveUpdatedConfigFunc
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported service type ${this.serviceType}`);
|
throw Error(`not supported service type ${this.serviceType}`);
|
||||||
}
|
}
|
||||||
@ -67,6 +84,11 @@ export class RemoteClient {
|
|||||||
return await webdav.getRemoteMeta(this.webdavClient, fileOrFolderPath);
|
return await webdav.getRemoteMeta(this.webdavClient, fileOrFolderPath);
|
||||||
} else if (this.serviceType === "dropbox") {
|
} else if (this.serviceType === "dropbox") {
|
||||||
return await dropbox.getRemoteMeta(this.dropboxClient, fileOrFolderPath);
|
return await dropbox.getRemoteMeta(this.dropboxClient, fileOrFolderPath);
|
||||||
|
} else if (this.serviceType === "onedrive") {
|
||||||
|
return await onedrive.getRemoteMeta(
|
||||||
|
this.onedriveClient,
|
||||||
|
fileOrFolderPath
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported service type ${this.serviceType}`);
|
throw Error(`not supported service type ${this.serviceType}`);
|
||||||
}
|
}
|
||||||
@ -109,6 +131,16 @@ export class RemoteClient {
|
|||||||
remoteEncryptedKey,
|
remoteEncryptedKey,
|
||||||
foldersCreatedBefore
|
foldersCreatedBefore
|
||||||
);
|
);
|
||||||
|
} else if (this.serviceType === "onedrive") {
|
||||||
|
return await onedrive.uploadToRemote(
|
||||||
|
this.onedriveClient,
|
||||||
|
fileOrFolderPath,
|
||||||
|
vault,
|
||||||
|
isRecursively,
|
||||||
|
password,
|
||||||
|
remoteEncryptedKey,
|
||||||
|
foldersCreatedBefore
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported service type ${this.serviceType}`);
|
throw Error(`not supported service type ${this.serviceType}`);
|
||||||
}
|
}
|
||||||
@ -121,6 +153,8 @@ export class RemoteClient {
|
|||||||
return await webdav.listFromRemote(this.webdavClient, prefix);
|
return await webdav.listFromRemote(this.webdavClient, prefix);
|
||||||
} else if (this.serviceType === "dropbox") {
|
} else if (this.serviceType === "dropbox") {
|
||||||
return await dropbox.listFromRemote(this.dropboxClient, prefix);
|
return await dropbox.listFromRemote(this.dropboxClient, prefix);
|
||||||
|
} else if (this.serviceType === "onedrive") {
|
||||||
|
return await onedrive.listFromRemote(this.onedriveClient, prefix);
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported service type ${this.serviceType}`);
|
throw Error(`not supported service type ${this.serviceType}`);
|
||||||
}
|
}
|
||||||
@ -161,6 +195,15 @@ export class RemoteClient {
|
|||||||
password,
|
password,
|
||||||
remoteEncryptedKey
|
remoteEncryptedKey
|
||||||
);
|
);
|
||||||
|
} else if (this.serviceType === "onedrive") {
|
||||||
|
return await onedrive.downloadFromRemote(
|
||||||
|
this.onedriveClient,
|
||||||
|
fileOrFolderPath,
|
||||||
|
vault,
|
||||||
|
mtime,
|
||||||
|
password,
|
||||||
|
remoteEncryptedKey
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported service type ${this.serviceType}`);
|
throw Error(`not supported service type ${this.serviceType}`);
|
||||||
}
|
}
|
||||||
@ -193,6 +236,13 @@ export class RemoteClient {
|
|||||||
password,
|
password,
|
||||||
remoteEncryptedKey
|
remoteEncryptedKey
|
||||||
);
|
);
|
||||||
|
} else if (this.serviceType === "onedrive") {
|
||||||
|
return await onedrive.deleteFromRemote(
|
||||||
|
this.onedriveClient,
|
||||||
|
fileOrFolderPath,
|
||||||
|
password,
|
||||||
|
remoteEncryptedKey
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported service type ${this.serviceType}`);
|
throw Error(`not supported service type ${this.serviceType}`);
|
||||||
}
|
}
|
||||||
@ -205,6 +255,8 @@ export class RemoteClient {
|
|||||||
return await webdav.checkConnectivity(this.webdavClient);
|
return await webdav.checkConnectivity(this.webdavClient);
|
||||||
} else if (this.serviceType === "dropbox") {
|
} else if (this.serviceType === "dropbox") {
|
||||||
return await dropbox.checkConnectivity(this.dropboxClient);
|
return await dropbox.checkConnectivity(this.dropboxClient);
|
||||||
|
} else if (this.serviceType === "onedrive") {
|
||||||
|
return await onedrive.checkConnectivity(this.onedriveClient);
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported service type ${this.serviceType}`);
|
throw Error(`not supported service type ${this.serviceType}`);
|
||||||
}
|
}
|
||||||
@ -213,6 +265,8 @@ export class RemoteClient {
|
|||||||
getUser = async () => {
|
getUser = async () => {
|
||||||
if (this.serviceType === "dropbox") {
|
if (this.serviceType === "dropbox") {
|
||||||
return await dropbox.getUserDisplayName(this.dropboxClient);
|
return await dropbox.getUserDisplayName(this.dropboxClient);
|
||||||
|
} else if (this.serviceType === "onedrive") {
|
||||||
|
return await onedrive.getUserDisplayName(this.onedriveClient);
|
||||||
} else {
|
} else {
|
||||||
throw Error(`not supported service type ${this.serviceType}`);
|
throw Error(`not supported service type ${this.serviceType}`);
|
||||||
}
|
}
|
||||||
|
|||||||
691
src/remoteForOnedrive.ts
Normal file
691
src/remoteForOnedrive.ts
Normal file
@ -0,0 +1,691 @@
|
|||||||
|
import * as path from "path";
|
||||||
|
import { request, Vault } from "obsidian";
|
||||||
|
import { PublicClientApplication, CryptoProvider } from "@azure/msal-node";
|
||||||
|
import { COMMAND_CALLBACK_ONEDRIVE } from "./baseTypes";
|
||||||
|
import type { OnedriveConfig, RemoteItem } from "./baseTypes";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Client,
|
||||||
|
FileUpload,
|
||||||
|
UploadEventHandlers,
|
||||||
|
AuthenticationProvider,
|
||||||
|
AuthenticationProviderOptions,
|
||||||
|
Range,
|
||||||
|
LargeFileUploadSession,
|
||||||
|
LargeFileUploadTask,
|
||||||
|
LargeFileUploadTaskOptions,
|
||||||
|
UploadResult,
|
||||||
|
} from "@microsoft/microsoft-graph-client";
|
||||||
|
import type { Drive, DriveItem, User } from "@microsoft/microsoft-graph-types";
|
||||||
|
import {
|
||||||
|
getFolderLevels,
|
||||||
|
getPathFolder,
|
||||||
|
getRandomArrayBuffer,
|
||||||
|
getRandomIntInclusive,
|
||||||
|
mkdirpInVault,
|
||||||
|
} from "./misc";
|
||||||
|
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
|
||||||
|
|
||||||
|
const SCOPES = ["User.Read", "Files.ReadWrite.AppFolder", "offline_access"];
|
||||||
|
const REDIRECT_URI = `obsidian://${COMMAND_CALLBACK_ONEDRIVE}`;
|
||||||
|
|
||||||
|
export const DEFAULT_ONEDRIVE_CONFIG: OnedriveConfig = {
|
||||||
|
accessToken: "",
|
||||||
|
clientID: process.env.DEFAULT_ONEDRIVE_CLIENT_ID,
|
||||||
|
authority: process.env.DEFAULT_ONEDRIVE_AUTHORITY,
|
||||||
|
refreshToken: "",
|
||||||
|
accessTokenExpiresInSeconds: 0,
|
||||||
|
accessTokenExpiresAtTime: 0,
|
||||||
|
deltaLink: "",
|
||||||
|
username: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Onedrive authorization using PKCE
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
export async function getAuthUrlAndVerifier(
|
||||||
|
clientID: string,
|
||||||
|
authority: string
|
||||||
|
) {
|
||||||
|
const cryptoProvider = new CryptoProvider();
|
||||||
|
const { verifier, challenge } = await cryptoProvider.generatePkceCodes();
|
||||||
|
|
||||||
|
const pkceCodes = {
|
||||||
|
challengeMethod: "S256", // Use SHA256 Algorithm
|
||||||
|
verifier: verifier,
|
||||||
|
challenge: challenge,
|
||||||
|
};
|
||||||
|
|
||||||
|
const authCodeUrlParams = {
|
||||||
|
redirectUri: REDIRECT_URI,
|
||||||
|
scopes: SCOPES,
|
||||||
|
codeChallenge: pkceCodes.challenge, // PKCE Code Challenge
|
||||||
|
codeChallengeMethod: pkceCodes.challengeMethod, // PKCE Code Challenge Method
|
||||||
|
};
|
||||||
|
|
||||||
|
const pca = new PublicClientApplication({
|
||||||
|
auth: {
|
||||||
|
clientId: clientID,
|
||||||
|
authority: authority,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const authCodeUrl = await pca.getAuthCodeUrl(authCodeUrlParams);
|
||||||
|
|
||||||
|
return {
|
||||||
|
authUrl: authCodeUrl,
|
||||||
|
verifier: verifier,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check doc from
|
||||||
|
* https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
|
||||||
|
* https://docs.microsoft.com/en-us/onedrive/developer/rest-api/getting-started/graph-oauth?view=odsp-graph-online#code-flow
|
||||||
|
*/
|
||||||
|
export interface AccessCodeResponseSuccessfulType {
|
||||||
|
token_type: "Bearer" | "bearer";
|
||||||
|
expires_in: number;
|
||||||
|
ext_expires_in?: number;
|
||||||
|
scope: string;
|
||||||
|
access_token: string;
|
||||||
|
refresh_token?: string;
|
||||||
|
id_token?: string;
|
||||||
|
}
|
||||||
|
export interface AccessCodeResponseFailedType {
|
||||||
|
error: string;
|
||||||
|
error_description: string;
|
||||||
|
error_codes: number[];
|
||||||
|
timestamp: string;
|
||||||
|
trace_id: string;
|
||||||
|
correlation_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendAuthReq = async (
|
||||||
|
clientID: string,
|
||||||
|
authority: string,
|
||||||
|
authCode: string,
|
||||||
|
verifier: string
|
||||||
|
) => {
|
||||||
|
// // original code snippets for references
|
||||||
|
// const authResponse = await pca.acquireTokenByCode({
|
||||||
|
// redirectUri: REDIRECT_URI,
|
||||||
|
// scopes: SCOPES,
|
||||||
|
// code: authCode,
|
||||||
|
// codeVerifier: verifier, // PKCE Code Verifier
|
||||||
|
// });
|
||||||
|
// console.log('authResponse')
|
||||||
|
// console.log(authResponse)
|
||||||
|
// return authResponse;
|
||||||
|
|
||||||
|
// Because of the CORS problem,
|
||||||
|
// we need to construct raw request using Obsidian request,
|
||||||
|
// instead of using msal
|
||||||
|
// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
|
||||||
|
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/getting-started/graph-oauth?view=odsp-graph-online#code-flow
|
||||||
|
const rsp1 = await request({
|
||||||
|
url: `${authority}/oauth2/v2.0/token`,
|
||||||
|
method: "POST",
|
||||||
|
contentType: "application/x-www-form-urlencoded",
|
||||||
|
body: new URLSearchParams({
|
||||||
|
tenant: "consumers",
|
||||||
|
client_id: clientID,
|
||||||
|
scope: SCOPES.join(" "),
|
||||||
|
code: authCode,
|
||||||
|
redirect_uri: REDIRECT_URI,
|
||||||
|
grant_type: "authorization_code",
|
||||||
|
code_verifier: verifier,
|
||||||
|
}).toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const rsp2 = JSON.parse(rsp1);
|
||||||
|
// console.log(rsp2);
|
||||||
|
|
||||||
|
if (rsp2.error !== undefined) {
|
||||||
|
return rsp2 as AccessCodeResponseFailedType;
|
||||||
|
} else {
|
||||||
|
return rsp2 as AccessCodeResponseSuccessfulType;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendRefreshTokenReq = async (
|
||||||
|
clientID: string,
|
||||||
|
authority: string,
|
||||||
|
refreshToken: string
|
||||||
|
) => {
|
||||||
|
// also use Obsidian request to bypass CORS issue.
|
||||||
|
const rsp1 = await request({
|
||||||
|
url: `${authority}/oauth2/v2.0/token`,
|
||||||
|
method: "POST",
|
||||||
|
contentType: "application/x-www-form-urlencoded",
|
||||||
|
body: new URLSearchParams({
|
||||||
|
tenant: "consumers",
|
||||||
|
client_id: clientID,
|
||||||
|
scope: SCOPES.join(" "),
|
||||||
|
refresh_token: refreshToken,
|
||||||
|
grant_type: "refresh_token",
|
||||||
|
}).toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const rsp2 = JSON.parse(rsp1);
|
||||||
|
// console.log(rsp2);
|
||||||
|
|
||||||
|
if (rsp2.error !== undefined) {
|
||||||
|
return rsp2 as AccessCodeResponseFailedType;
|
||||||
|
} else {
|
||||||
|
return rsp2 as AccessCodeResponseSuccessfulType;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Other usual common methods
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const getOnedrivePath = (fileOrFolderPath: string, vaultName: string) => {
|
||||||
|
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/concepts/special-folders-appfolder?view=odsp-graph-online
|
||||||
|
const prefix = `/drive/special/approot:/${vaultName}`;
|
||||||
|
if (fileOrFolderPath.startsWith(prefix)) {
|
||||||
|
// already transformed, return as is
|
||||||
|
return fileOrFolderPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = fileOrFolderPath;
|
||||||
|
if (fileOrFolderPath === "/" || fileOrFolderPath === "") {
|
||||||
|
// special
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
if (key.endsWith("/")) {
|
||||||
|
key = key.slice(0, key.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
key = `${prefix}/${key}`;
|
||||||
|
return key;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNormPath = (fileOrFolderPath: string, vaultName: string) => {
|
||||||
|
const prefix = `/drive/special/approot:/${vaultName}`;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(fileOrFolderPath === prefix || fileOrFolderPath.startsWith(`${prefix}/`))
|
||||||
|
) {
|
||||||
|
throw Error(
|
||||||
|
`"${fileOrFolderPath}" doesn't starts with "${prefix}/" or equals to "${prefix}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileOrFolderPath === prefix) {
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
return fileOrFolderPath.slice(`${prefix}/`.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fromDriveItemToRemoteItem = (
|
||||||
|
x: DriveItem,
|
||||||
|
vaultName: string
|
||||||
|
): RemoteItem => {
|
||||||
|
let key = "";
|
||||||
|
|
||||||
|
const COMMON_PREFIX = `/drive/root:/Apps/remotely-save/${vaultName}`;
|
||||||
|
const COMMON_PREFIX_OTHERS = `/drive/items/`;
|
||||||
|
if (`${x.parentReference.path}/${x.name}`.startsWith(COMMON_PREFIX)) {
|
||||||
|
key = `${x.parentReference.path}/${x.name}`.substring(
|
||||||
|
COMMON_PREFIX.length + 1
|
||||||
|
);
|
||||||
|
} else if (x.parentReference.path.startsWith(COMMON_PREFIX_OTHERS)) {
|
||||||
|
// it's something like
|
||||||
|
// /drive/items/<some_id>!<another_id>:/${vaultName}/<subfolder>
|
||||||
|
// with uri encoded!
|
||||||
|
const parPath = decodeURIComponent(x.parentReference.path);
|
||||||
|
key = parPath.substring(parPath.indexOf(":") + 1);
|
||||||
|
if (key.startsWith(`/${vaultName}/`)) {
|
||||||
|
key = key.substring(`/${vaultName}/`.length);
|
||||||
|
key = `${key}/${x.name}`;
|
||||||
|
} else if (key === `/${vaultName}`) {
|
||||||
|
key = x.name;
|
||||||
|
} else {
|
||||||
|
throw Error(
|
||||||
|
`we meet file/folder and do not know how to deal with it:\n${JSON.stringify(
|
||||||
|
x
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Error(
|
||||||
|
`we meet file/folder and do not know how to deal with it:\n${JSON.stringify(
|
||||||
|
x
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFolder = "folder" in x;
|
||||||
|
if (isFolder) {
|
||||||
|
key = `${key}/`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: key,
|
||||||
|
lastModified: Date.parse(x.fileSystemInfo.lastModifiedDateTime),
|
||||||
|
size: isFolder ? 0 : x.size,
|
||||||
|
remoteType: "onedrive",
|
||||||
|
etag: x.eTag || x.cTag || "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// to adapt to the required interface
|
||||||
|
class MyAuthProvider implements AuthenticationProvider {
|
||||||
|
onedriveConfig: OnedriveConfig;
|
||||||
|
saveUpdatedConfigFunc: () => Promise<any>;
|
||||||
|
constructor(
|
||||||
|
onedriveConfig: OnedriveConfig,
|
||||||
|
saveUpdatedConfigFunc: () => Promise<any>
|
||||||
|
) {
|
||||||
|
this.onedriveConfig = onedriveConfig;
|
||||||
|
this.saveUpdatedConfigFunc = saveUpdatedConfigFunc;
|
||||||
|
}
|
||||||
|
getAccessToken = async () => {
|
||||||
|
if (
|
||||||
|
this.onedriveConfig.accessToken === "" ||
|
||||||
|
this.onedriveConfig.refreshToken === ""
|
||||||
|
) {
|
||||||
|
throw Error("The user has not manually auth yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTs = Date.now();
|
||||||
|
if (this.onedriveConfig.accessTokenExpiresAtTime > currentTs) {
|
||||||
|
return this.onedriveConfig.accessToken;
|
||||||
|
} else {
|
||||||
|
// use refreshToken to refresh
|
||||||
|
const r = await sendRefreshTokenReq(
|
||||||
|
this.onedriveConfig.clientID,
|
||||||
|
this.onedriveConfig.authority,
|
||||||
|
this.onedriveConfig.refreshToken
|
||||||
|
);
|
||||||
|
if ((r as any).error !== undefined) {
|
||||||
|
const r2 = r as AccessCodeResponseFailedType;
|
||||||
|
throw Error(
|
||||||
|
`Error while refreshing accessToken: ${r2.error}, ${r2.error_codes}: ${r2.error_description}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const r2 = r as AccessCodeResponseSuccessfulType;
|
||||||
|
this.onedriveConfig.accessToken = r2.access_token;
|
||||||
|
this.onedriveConfig.refreshToken = r2.refresh_token;
|
||||||
|
this.onedriveConfig.accessTokenExpiresInSeconds = r2.expires_in;
|
||||||
|
this.onedriveConfig.accessTokenExpiresAtTime =
|
||||||
|
currentTs + r2.expires_in * 1000 - 60 * 2 * 1000;
|
||||||
|
await this.saveUpdatedConfigFunc();
|
||||||
|
console.log("Onedrive accessToken updated");
|
||||||
|
return this.onedriveConfig.accessToken;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WrappedOnedriveClient {
|
||||||
|
onedriveConfig: OnedriveConfig;
|
||||||
|
vaultName: string;
|
||||||
|
client: Client;
|
||||||
|
vaultFolderExists: boolean;
|
||||||
|
saveUpdatedConfigFunc: () => Promise<any>;
|
||||||
|
constructor(
|
||||||
|
onedriveConfig: OnedriveConfig,
|
||||||
|
vaultName: string,
|
||||||
|
saveUpdatedConfigFunc: () => Promise<any>
|
||||||
|
) {
|
||||||
|
this.onedriveConfig = onedriveConfig;
|
||||||
|
this.vaultName = vaultName;
|
||||||
|
this.vaultFolderExists = false;
|
||||||
|
this.saveUpdatedConfigFunc = saveUpdatedConfigFunc;
|
||||||
|
this.client = Client.initWithMiddleware({
|
||||||
|
authProvider: new MyAuthProvider(onedriveConfig, saveUpdatedConfigFunc),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
init = async () => {
|
||||||
|
// check token
|
||||||
|
if (
|
||||||
|
this.onedriveConfig.accessToken === "" ||
|
||||||
|
this.onedriveConfig.refreshToken === ""
|
||||||
|
) {
|
||||||
|
throw Error("The user has not manually auth yet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// check vault folder
|
||||||
|
// console.log(`checking remote has folder /${this.vaultName}`);
|
||||||
|
if (this.vaultFolderExists) {
|
||||||
|
// console.log(`already checked, /${this.vaultName} exist before`)
|
||||||
|
} else {
|
||||||
|
const k = await this.client.api("/drive/special/approot/children").get();
|
||||||
|
// console.log(k);
|
||||||
|
this.vaultFolderExists =
|
||||||
|
(k.value as DriveItem[]).filter((x) => x.name === this.vaultName)
|
||||||
|
.length > 0;
|
||||||
|
if (!this.vaultFolderExists) {
|
||||||
|
console.log(`remote does not have folder /${this.vaultName}`);
|
||||||
|
await this.client.api("/drive/special/approot/children").post({
|
||||||
|
name: `${this.vaultName}`,
|
||||||
|
folder: {},
|
||||||
|
"@microsoft.graph.conflictBehavior": "replace",
|
||||||
|
});
|
||||||
|
console.log(`remote folder /${this.vaultName} created`);
|
||||||
|
this.vaultFolderExists = true;
|
||||||
|
} else {
|
||||||
|
// console.log(`remote folder /${this.vaultName} exists`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getOnedriveClient = (
|
||||||
|
onedriveConfig: OnedriveConfig,
|
||||||
|
vaultName: string,
|
||||||
|
saveUpdatedConfigFunc: () => Promise<any>
|
||||||
|
) => {
|
||||||
|
return new WrappedOnedriveClient(
|
||||||
|
onedriveConfig,
|
||||||
|
vaultName,
|
||||||
|
saveUpdatedConfigFunc
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use delta api to list all files and folders
|
||||||
|
* https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delta?view=odsp-graph-online
|
||||||
|
* @param client
|
||||||
|
* @param prefix
|
||||||
|
*/
|
||||||
|
export const listFromRemote = async (
|
||||||
|
client: WrappedOnedriveClient,
|
||||||
|
prefix?: string
|
||||||
|
) => {
|
||||||
|
if (prefix !== undefined) {
|
||||||
|
throw Error("prefix not supported (yet)");
|
||||||
|
}
|
||||||
|
await client.init();
|
||||||
|
|
||||||
|
const NEXT_LINK_KEY = "@odata.nextLink";
|
||||||
|
const DELTA_LINK_KEY = "@odata.deltaLink";
|
||||||
|
let res = await client.client
|
||||||
|
.api(`/drive/special/approot:/${client.vaultName}:/delta`)
|
||||||
|
.get();
|
||||||
|
const driveItems = res.value as DriveItem[];
|
||||||
|
|
||||||
|
while (NEXT_LINK_KEY in res) {
|
||||||
|
res = await client.client.api(res[NEXT_LINK_KEY]).get();
|
||||||
|
driveItems.push(...JSON.parse(JSON.stringify(res.value as DriveItem[])));
|
||||||
|
}
|
||||||
|
|
||||||
|
// lastly we should have delta link?
|
||||||
|
if (DELTA_LINK_KEY in res) {
|
||||||
|
client.onedriveConfig.deltaLink = res[DELTA_LINK_KEY];
|
||||||
|
await client.saveUpdatedConfigFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
// unify everything to RemoteItem
|
||||||
|
const unifiedContents = driveItems
|
||||||
|
.map((x) => fromDriveItemToRemoteItem(x, client.vaultName))
|
||||||
|
.filter((x) => x.key !== "/");
|
||||||
|
return {
|
||||||
|
Contents: unifiedContents,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRemoteMeta = async (
|
||||||
|
client: WrappedOnedriveClient,
|
||||||
|
fileOrFolderPath: string
|
||||||
|
) => {
|
||||||
|
await client.init();
|
||||||
|
const remotePath = getOnedrivePath(fileOrFolderPath, client.vaultName);
|
||||||
|
// console.log(`remotePath=${remotePath}`);
|
||||||
|
const rsp = await client.client
|
||||||
|
.api(remotePath)
|
||||||
|
.select("cTag,eTag,fileSystemInfo,folder,file,name,parentReference,size")
|
||||||
|
.get();
|
||||||
|
// console.log(rsp);
|
||||||
|
const driveItem = rsp as DriveItem;
|
||||||
|
const res = fromDriveItemToRemoteItem(driveItem, client.vaultName);
|
||||||
|
// console.log(res);
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const uploadToRemote = async (
|
||||||
|
client: WrappedOnedriveClient,
|
||||||
|
fileOrFolderPath: string,
|
||||||
|
vault: Vault,
|
||||||
|
isRecursively: boolean = false,
|
||||||
|
password: string = "",
|
||||||
|
remoteEncryptedKey: string = "",
|
||||||
|
foldersCreatedBefore: Set<string> | undefined = undefined
|
||||||
|
) => {
|
||||||
|
await client.init();
|
||||||
|
|
||||||
|
let uploadFile = fileOrFolderPath;
|
||||||
|
if (password !== "") {
|
||||||
|
uploadFile = remoteEncryptedKey;
|
||||||
|
}
|
||||||
|
uploadFile = getOnedrivePath(uploadFile, client.vaultName);
|
||||||
|
// console.log(`uploadFile=${uploadFile}`);
|
||||||
|
|
||||||
|
const isFolder = fileOrFolderPath.endsWith("/");
|
||||||
|
|
||||||
|
if (isFolder && isRecursively) {
|
||||||
|
throw Error("upload function doesn't implement recursive function yet!");
|
||||||
|
} else if (isFolder && !isRecursively) {
|
||||||
|
// folder
|
||||||
|
if (password === "") {
|
||||||
|
// if not encrypted, mkdir a remote folder
|
||||||
|
if (foldersCreatedBefore?.has(uploadFile)) {
|
||||||
|
// created, pass
|
||||||
|
} else {
|
||||||
|
// https://stackoverflow.com/questions/56479865/creating-nested-folders-in-one-go-onedrive-api
|
||||||
|
// use PATCH to create folder recursively!!!
|
||||||
|
await client.client.api(uploadFile).patch({
|
||||||
|
folder: {},
|
||||||
|
"@microsoft.graph.conflictBehavior": "replace",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const res = await getRemoteMeta(client, uploadFile);
|
||||||
|
return res;
|
||||||
|
} else {
|
||||||
|
// if encrypted,
|
||||||
|
// upload a fake, random-size file
|
||||||
|
// with the encrypted file name
|
||||||
|
const byteLengthRandom = getRandomIntInclusive(
|
||||||
|
1,
|
||||||
|
65536 /* max allowed */
|
||||||
|
);
|
||||||
|
const arrBufRandom = await encryptArrayBuffer(
|
||||||
|
getRandomArrayBuffer(byteLengthRandom),
|
||||||
|
password
|
||||||
|
);
|
||||||
|
|
||||||
|
const uploadSession: LargeFileUploadSession =
|
||||||
|
await LargeFileUploadTask.createUploadSession(
|
||||||
|
client.client,
|
||||||
|
`https://graph.microsoft.com/v1.0/me${encodeURIComponent(
|
||||||
|
uploadFile
|
||||||
|
)}:/createUploadSession`,
|
||||||
|
{
|
||||||
|
item: {
|
||||||
|
"@microsoft.graph.conflictBehavior": "replace",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const task = new LargeFileUploadTask(
|
||||||
|
client.client,
|
||||||
|
new FileUpload(
|
||||||
|
arrBufRandom,
|
||||||
|
path.posix.basename(uploadFile),
|
||||||
|
arrBufRandom.byteLength
|
||||||
|
),
|
||||||
|
uploadSession,
|
||||||
|
{
|
||||||
|
rangeSize: 1024 * 1024,
|
||||||
|
uploadEventHandlers: {
|
||||||
|
progress: (range?: Range) => {
|
||||||
|
// Handle progress event
|
||||||
|
// console.log(
|
||||||
|
// `uploading ${range.minValue}-${range.maxValue} of ${fileOrFolderPath}`
|
||||||
|
// );
|
||||||
|
},
|
||||||
|
} as UploadEventHandlers,
|
||||||
|
} as LargeFileUploadTaskOptions
|
||||||
|
);
|
||||||
|
const uploadResult: UploadResult = await task.upload();
|
||||||
|
// console.log(uploadResult)
|
||||||
|
const res = await getRemoteMeta(client, uploadFile);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// file
|
||||||
|
// we ignore isRecursively parameter here
|
||||||
|
const localContent = await vault.adapter.readBinary(fileOrFolderPath);
|
||||||
|
let remoteContent = localContent;
|
||||||
|
if (password !== "") {
|
||||||
|
remoteContent = await encryptArrayBuffer(localContent, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no need to create parent folders firstly, cool!
|
||||||
|
|
||||||
|
// we need to customize the special root folder,
|
||||||
|
// so use LargeFileUploadTask instead of OneDriveLargeFileUploadTask
|
||||||
|
const progress = (range?: Range) => {
|
||||||
|
// Handle progress event
|
||||||
|
// console.log(
|
||||||
|
// `uploading ${range.minValue}-${range.maxValue} of ${fileOrFolderPath}`
|
||||||
|
// );
|
||||||
|
};
|
||||||
|
const uploadEventHandlers: UploadEventHandlers = {
|
||||||
|
progress: progress,
|
||||||
|
};
|
||||||
|
const options: LargeFileUploadTaskOptions = {
|
||||||
|
rangeSize: 1024 * 1024,
|
||||||
|
uploadEventHandlers: uploadEventHandlers,
|
||||||
|
};
|
||||||
|
const payload = {
|
||||||
|
item: {
|
||||||
|
"@microsoft.graph.conflictBehavior": "replace",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// uploadFile already starts with /drive/special/approot:/${vaultName}
|
||||||
|
const uploadSession: LargeFileUploadSession =
|
||||||
|
await LargeFileUploadTask.createUploadSession(
|
||||||
|
client.client,
|
||||||
|
`https://graph.microsoft.com/v1.0/me${encodeURIComponent(
|
||||||
|
uploadFile
|
||||||
|
)}:/createUploadSession`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
const fileObject = new FileUpload(
|
||||||
|
remoteContent,
|
||||||
|
path.posix.basename(uploadFile),
|
||||||
|
remoteContent.byteLength
|
||||||
|
);
|
||||||
|
const task = new LargeFileUploadTask(
|
||||||
|
client.client,
|
||||||
|
fileObject,
|
||||||
|
uploadSession,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
const uploadResult: UploadResult = await task.upload();
|
||||||
|
// console.log(uploadResult)
|
||||||
|
const res = await getRemoteMeta(client, uploadFile);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadFromRemoteRaw = async (
|
||||||
|
client: WrappedOnedriveClient,
|
||||||
|
fileOrFolderPath: string
|
||||||
|
): Promise<ArrayBuffer> => {
|
||||||
|
await client.init();
|
||||||
|
const key = getOnedrivePath(fileOrFolderPath, client.vaultName);
|
||||||
|
const rsp = await client.client
|
||||||
|
.api(key)
|
||||||
|
.select("@microsoft.graph.downloadUrl")
|
||||||
|
.get();
|
||||||
|
const downloadUrl: string = rsp["@microsoft.graph.downloadUrl"];
|
||||||
|
const content = await (await fetch(downloadUrl)).arrayBuffer();
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const downloadFromRemote = async (
|
||||||
|
client: WrappedOnedriveClient,
|
||||||
|
fileOrFolderPath: string,
|
||||||
|
vault: Vault,
|
||||||
|
mtime: number,
|
||||||
|
password: string = "",
|
||||||
|
remoteEncryptedKey: string = ""
|
||||||
|
) => {
|
||||||
|
await client.init();
|
||||||
|
|
||||||
|
const isFolder = fileOrFolderPath.endsWith("/");
|
||||||
|
|
||||||
|
await mkdirpInVault(fileOrFolderPath, vault);
|
||||||
|
|
||||||
|
if (isFolder) {
|
||||||
|
// mkdirp locally is enough
|
||||||
|
// do nothing here
|
||||||
|
} else {
|
||||||
|
let downloadFile = fileOrFolderPath;
|
||||||
|
if (password !== "") {
|
||||||
|
downloadFile = remoteEncryptedKey;
|
||||||
|
}
|
||||||
|
downloadFile = getOnedrivePath(downloadFile, client.vaultName);
|
||||||
|
const remoteContent = await downloadFromRemoteRaw(client, downloadFile);
|
||||||
|
let localContent = remoteContent;
|
||||||
|
if (password !== "") {
|
||||||
|
localContent = await decryptArrayBuffer(remoteContent, password);
|
||||||
|
}
|
||||||
|
await vault.adapter.writeBinary(fileOrFolderPath, localContent, {
|
||||||
|
mtime: mtime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteFromRemote = async (
|
||||||
|
client: WrappedOnedriveClient,
|
||||||
|
fileOrFolderPath: string,
|
||||||
|
password: string = "",
|
||||||
|
remoteEncryptedKey: string = ""
|
||||||
|
) => {
|
||||||
|
if (fileOrFolderPath === "/") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let remoteFileName = fileOrFolderPath;
|
||||||
|
if (password !== "") {
|
||||||
|
remoteFileName = remoteEncryptedKey;
|
||||||
|
}
|
||||||
|
remoteFileName = getOnedrivePath(remoteFileName, client.vaultName);
|
||||||
|
|
||||||
|
await client.init();
|
||||||
|
await client.client.api(remoteFileName).delete();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const checkConnectivity = async (client: WrappedOnedriveClient) => {
|
||||||
|
try {
|
||||||
|
const k = await getUserDisplayName(client);
|
||||||
|
return k !== "<unknown display name>";
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUserDisplayName = async (client: WrappedOnedriveClient) => {
|
||||||
|
await client.init();
|
||||||
|
const res: User = await client.client.api("/me").select("displayName").get();
|
||||||
|
return res.displayName || "<unknown display name>";
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc#send-a-sign-out-request
|
||||||
|
* https://docs.microsoft.com/en-us/graph/api/user-revokesigninsessions
|
||||||
|
* https://docs.microsoft.com/en-us/graph/api/user-invalidateallrefreshtokens
|
||||||
|
* @param client
|
||||||
|
*/
|
||||||
|
// export const revokeAuth = async (client: WrappedOnedriveClient) => {
|
||||||
|
// await client.init();
|
||||||
|
// await client.client.api('/me/revokeSignInSessions').post(undefined);
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const getRevokeAddr = async () => {
|
||||||
|
return "https://account.live.com/consent/Manage";
|
||||||
|
};
|
||||||
15
styles.css
15
styles.css
@ -30,6 +30,21 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onedrive-disclaimer {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.onedrive-hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onedrive-auth-button-hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onedrive-revoke-auth-button-hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.webdav-disclaimer {
|
.webdav-disclaimer {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,8 @@ const webpack = require("webpack");
|
|||||||
const TerserPlugin = require("terser-webpack-plugin");
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
|
|
||||||
const DEFAULT_DROPBOX_APP_KEY = process.env.DROPBOX_APP_KEY || "";
|
const DEFAULT_DROPBOX_APP_KEY = process.env.DROPBOX_APP_KEY || "";
|
||||||
|
const DEFAULT_ONEDRIVE_CLIENT_ID = process.env.ONEDRIVE_CLIENT_ID || "";
|
||||||
|
const DEFAULT_ONEDRIVE_AUTHORITY = process.env.ONEDRIVE_AUTHORITY || "";
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: "./src/main.ts",
|
entry: "./src/main.ts",
|
||||||
@ -16,6 +18,8 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
"process.env.DEFAULT_DROPBOX_APP_KEY": `"${DEFAULT_DROPBOX_APP_KEY}"`,
|
"process.env.DEFAULT_DROPBOX_APP_KEY": `"${DEFAULT_DROPBOX_APP_KEY}"`,
|
||||||
|
"process.env.DEFAULT_ONEDRIVE_CLIENT_ID": `"${DEFAULT_ONEDRIVE_CLIENT_ID}"`,
|
||||||
|
"process.env.DEFAULT_ONEDRIVE_AUTHORITY": `"${DEFAULT_ONEDRIVE_AUTHORITY}"`,
|
||||||
}),
|
}),
|
||||||
// Work around for Buffer is undefined:
|
// Work around for Buffer is undefined:
|
||||||
// https://github.com/webpack/changelog-v5/issues/10
|
// https://github.com/webpack/changelog-v5/issues/10
|
||||||
@ -47,8 +51,8 @@ module.exports = {
|
|||||||
// buffer: require.resolve("buffer/"),
|
// buffer: require.resolve("buffer/"),
|
||||||
// console: require.resolve("console-browserify"),
|
// console: require.resolve("console-browserify"),
|
||||||
// constants: require.resolve("constants-browserify"),
|
// constants: require.resolve("constants-browserify"),
|
||||||
// crypto: require.resolve("crypto-browserify"),
|
crypto: require.resolve("crypto-browserify"),
|
||||||
crypto: false,
|
// crypto: false,
|
||||||
// domain: require.resolve("domain-browser"),
|
// domain: require.resolve("domain-browser"),
|
||||||
// events: require.resolve("events"),
|
// events: require.resolve("events"),
|
||||||
// http: require.resolve("stream-http"),
|
// http: require.resolve("stream-http"),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user