You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-12-02 22:49:09 +02:00
Started fixing ReactNative app
This commit is contained in:
271
ReactNativeClient/lib/onedrive-api.js
Normal file
271
ReactNativeClient/lib/onedrive-api.js
Normal file
@@ -0,0 +1,271 @@
|
||||
const fetch = require('node-fetch');
|
||||
const tcpPortUsed = require('tcp-port-used');
|
||||
const http = require("http");
|
||||
const urlParser = require("url");
|
||||
const FormData = require('form-data');
|
||||
const enableServerDestroy = require('server-destroy');
|
||||
import { stringify } from 'query-string';
|
||||
|
||||
class OneDriveApi {
|
||||
|
||||
constructor(clientId, clientSecret) {
|
||||
this.clientId_ = clientId;
|
||||
this.clientSecret_ = clientSecret;
|
||||
this.auth_ = null;
|
||||
this.listeners_ = {
|
||||
'authRefreshed': [],
|
||||
};
|
||||
}
|
||||
|
||||
dispatch(eventName, param) {
|
||||
let ls = this.listeners_[eventName];
|
||||
for (let i = 0; i < ls.length; i++) {
|
||||
ls[i](param);
|
||||
}
|
||||
}
|
||||
|
||||
on(eventName, callback) {
|
||||
this.listeners_[eventName].push(callback);
|
||||
}
|
||||
|
||||
tokenBaseUrl() {
|
||||
return 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
|
||||
}
|
||||
|
||||
setAuth(auth) {
|
||||
this.auth_ = auth;
|
||||
}
|
||||
|
||||
token() {
|
||||
return this.auth_ ? this.auth_.access_token : null;
|
||||
}
|
||||
|
||||
clientId() {
|
||||
return this.clientId_;
|
||||
}
|
||||
|
||||
clientSecret() {
|
||||
return this.clientSecret_;
|
||||
}
|
||||
|
||||
possibleOAuthDancePorts() {
|
||||
return [1917, 9917, 8917];
|
||||
}
|
||||
|
||||
async appDirectory() {
|
||||
let r = await this.execJson('GET', '/drive/special/approot');
|
||||
return r.parentReference.path + '/' + r.name;
|
||||
}
|
||||
|
||||
authCodeUrl(redirectUri) {
|
||||
let query = {
|
||||
client_id: this.clientId_,
|
||||
scope: 'files.readwrite offline_access',
|
||||
response_type: 'code',
|
||||
redirect_uri: redirectUri,
|
||||
};
|
||||
return 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize?' + stringify(query);
|
||||
}
|
||||
|
||||
oneDriveErrorResponseToError(errorResponse) {
|
||||
if (!errorResponse) return new Error('Undefined error');
|
||||
|
||||
if (errorResponse.error) {
|
||||
let e = errorResponse.error;
|
||||
let output = new Error(e.message);
|
||||
if (e.code) output.code = e.code;
|
||||
if (e.innerError) output.innerError = e.innerError;
|
||||
return output;
|
||||
} else {
|
||||
return new Error(JSON.stringify(errorResponse));
|
||||
}
|
||||
}
|
||||
|
||||
async exec(method, path, query = null, data = null, options = null) {
|
||||
method = method.toUpperCase();
|
||||
|
||||
if (!options) options = {};
|
||||
if (!options.headers) options.headers = {};
|
||||
|
||||
if (method != 'GET') {
|
||||
options.method = method;
|
||||
}
|
||||
|
||||
if (method == 'PATCH' || method == 'POST') {
|
||||
options.headers['Content-Type'] = 'application/json';
|
||||
if (data) data = JSON.stringify(data);
|
||||
}
|
||||
|
||||
let url = path;
|
||||
|
||||
// In general, `path` contains a path relative to the base URL, but in some
|
||||
// cases the full URL is provided (for example, when it's a URL that was
|
||||
// retrieved from the API).
|
||||
if (url.indexOf('https://') !== 0) url = 'https://graph.microsoft.com/v1.0' + path;
|
||||
|
||||
if (query) {
|
||||
url += url.indexOf('?') < 0 ? '?' : '&';
|
||||
url += stringify(query);
|
||||
}
|
||||
|
||||
if (data) options.body = data;
|
||||
|
||||
// Rare error (one Google hit) - maybe repeat the request when it happens?
|
||||
|
||||
// { error:
|
||||
// { code: 'generalException',
|
||||
// message: 'An error occurred in the data store.',
|
||||
// innerError:
|
||||
// { 'request-id': 'b4310552-c18a-45b1-bde1-68e2c2345eef',
|
||||
// date: '2017-06-29T00:15:50' } } }
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
options.headers['Authorization'] = 'bearer ' + this.token();
|
||||
|
||||
let response = await fetch(url, options);
|
||||
if (!response.ok) {
|
||||
let errorResponse = await response.json();
|
||||
let error = this.oneDriveErrorResponseToError(errorResponse);
|
||||
|
||||
if (error.code == 'InvalidAuthenticationToken') {
|
||||
await this.refreshAccessToken();
|
||||
continue;
|
||||
} else {
|
||||
error.request = method + ' ' + url + ' ' + JSON.stringify(query) + ' ' + JSON.stringify(data) + ' ' + JSON.stringify(options);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
throw new Error('Could not execute request after multiple attempts: ' + method + ' ' + url);
|
||||
}
|
||||
|
||||
async execJson(method, path, query, data) {
|
||||
let response = await this.exec(method, path, query, data);
|
||||
let output = await response.json();
|
||||
return output;
|
||||
}
|
||||
|
||||
async execText(method, path, query, data) {
|
||||
let response = await this.exec(method, path, query, data);
|
||||
let output = await response.text();
|
||||
return output;
|
||||
}
|
||||
|
||||
async refreshAccessToken() {
|
||||
if (!this.auth_) throw new Error('Cannot refresh token: authentication data is missing');
|
||||
|
||||
let body = new FormData();
|
||||
body.append('client_id', this.clientId());
|
||||
body.append('client_secret', this.clientSecret());
|
||||
body.append('refresh_token', this.auth_.refresh_token);
|
||||
body.append('redirect_uri', 'http://localhost:1917');
|
||||
body.append('grant_type', 'refresh_token');
|
||||
|
||||
let options = {
|
||||
method: 'POST',
|
||||
body: body,
|
||||
};
|
||||
|
||||
this.auth_ = null;
|
||||
|
||||
let response = await fetch(this.tokenBaseUrl(), options);
|
||||
if (!response.ok) {
|
||||
let msg = await response.text();
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
this.auth_ = await response.json();
|
||||
|
||||
this.dispatch('authRefreshed', this.auth_);
|
||||
}
|
||||
|
||||
async oauthDance(targetConsole = null) {
|
||||
if (targetConsole === null) targetConsole = console;
|
||||
|
||||
this.auth_ = null;
|
||||
|
||||
let ports = this.possibleOAuthDancePorts();
|
||||
let port = null;
|
||||
for (let i = 0; i < ports.length; i++) {
|
||||
let inUse = await tcpPortUsed.check(ports[i]);
|
||||
if (!inUse) {
|
||||
port = ports[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!port) throw new Error('All potential ports are in use - please report the issue at https://github.com/laurent22/joplin');
|
||||
|
||||
let authCodeUrl = this.authCodeUrl('http://localhost:' + port);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let server = http.createServer();
|
||||
let errorMessage = null;
|
||||
|
||||
server.on('request', (request, response) => {
|
||||
const query = urlParser.parse(request.url, true).query;
|
||||
|
||||
function writeResponse(code, message) {
|
||||
response.writeHead(code, {"Content-Type": "text/html"});
|
||||
response.write(message);
|
||||
response.end();
|
||||
}
|
||||
|
||||
if (!query.code) return writeResponse(400, '"code" query parameter is missing');
|
||||
|
||||
let body = new FormData();
|
||||
body.append('client_id', this.clientId());
|
||||
body.append('client_secret', this.clientSecret());
|
||||
body.append('code', query.code ? query.code : '');
|
||||
body.append('redirect_uri', 'http://localhost:' + port.toString());
|
||||
body.append('grant_type', 'authorization_code');
|
||||
|
||||
let options = {
|
||||
method: 'POST',
|
||||
body: body,
|
||||
};
|
||||
|
||||
fetch(this.tokenBaseUrl(), options).then((r) => {
|
||||
if (!r.ok) {
|
||||
errorMessage = 'Could not retrieve auth code: ' + r.status + ': ' + r.statusText;
|
||||
writeResponse(400, errorMessage);
|
||||
targetConsole.log('');
|
||||
targetConsole.log(errorMessage);
|
||||
server.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
return r.json().then((json) => {
|
||||
this.auth_ = json;
|
||||
writeResponse(200, 'The application has been authorised - you may now close this browser tab.');
|
||||
targetConsole.log('');
|
||||
targetConsole.log('The application has been successfully authorised.');
|
||||
server.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
server.on('close', () => {
|
||||
if (errorMessage) {
|
||||
reject(new Error(errorMessage));
|
||||
} else {
|
||||
resolve(this.auth_);
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(port);
|
||||
|
||||
enableServerDestroy(server);
|
||||
|
||||
targetConsole.log('Please open this URL in your browser to authentify the application:');
|
||||
targetConsole.log('');
|
||||
targetConsole.log(authCodeUrl);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { OneDriveApi };
|
||||
Reference in New Issue
Block a user