Skip to content

Commit

Permalink
feat: move sharepoint edit info call from service worker to tab (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
rofe authored Apr 18, 2024
1 parent a740d1c commit d342faf
Show file tree
Hide file tree
Showing 11 changed files with 347 additions and 222 deletions.
11 changes: 6 additions & 5 deletions src/extension/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ async function updateAuthToken({
* Adds or removes a project based on the tab's URL
* @param {chrome.tabs.Tab} tab The tab
*/
async function addRemoveProject({ id, url }) {
const config = await getProjectFromUrl(url);
async function addRemoveProject(tab) {
const config = await getProjectFromUrl(tab);
if (isValidProject(config)) {
const { owner, repo } = config;
const project = await getProject(config);
Expand All @@ -63,16 +63,17 @@ async function addRemoveProject({ id, url }) {
} else {
await deleteProject(`${owner}/${repo}`);
}
await chrome.tabs.reload(id, { bypassCache: true });
await chrome.tabs.reload(tab.id, { bypassCache: true });
}
}

/**
* Enables or disables a project based on the tab's URL
* @param {chrome.tabs.Tab} tab The tab
*/
async function enableDisableProject({ id, url }) {
const cfg = await getProjectFromUrl(url);
async function enableDisableProject(tab) {
const { id } = tab;
const cfg = await getProjectFromUrl(tab);
if (await toggleProject(cfg)) {
await chrome.tabs.reload(id, { bypassCache: true });
}
Expand Down
15 changes: 8 additions & 7 deletions src/extension/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,11 @@ function getShareSettings(shareurl) {

/**
* Tries to retrieve a project config from a URL.
* @param {string} url The URL of the tab
* @param {chrome.tabs.Tab} [tab] The tab
* @returns {Promise<Object>} The project config
*/
export async function getProjectFromUrl(url) {
export async function getProjectFromUrl(tab) {
const { url } = tab || {};
if (!url) {
return {};
}
Expand All @@ -167,7 +168,7 @@ export async function getProjectFromUrl(url) {
};
} else {
// check if url is known in url cache
const { owner, repo } = (await urlCache.get(url))
const { owner, repo } = (await urlCache.get(tab))
.find((r) => r.originalRepository) || {};
if (owner && repo) {
return {
Expand Down Expand Up @@ -404,13 +405,13 @@ function getConfigDetails(host) {
/**
* Returns matches from configured projects for a given tab URL.
* @param {Object[]} configs The project configurations
* @param {string} tabUrl The tab URL
* @param {chrome.tabs.Tab} tab The tab
* @returns {Promise<Object[]>} The matches
*/
export async function getProjectMatches(configs, tabUrl) {
export async function getProjectMatches(configs, tab) {
const {
host: checkHost,
} = new URL(tabUrl);
} = new URL(tab.url);
// exclude disabled configs
const matches = configs
.filter((cfg) => !cfg.disabled)
Expand All @@ -428,7 +429,7 @@ export async function getProjectMatches(configs, tabUrl) {
|| isValidHost(checkHost, owner, repo); // inner or outer
});
// check url cache if no matches
const cachedResults = await urlCache.get(tabUrl);
const cachedResults = await urlCache.get(tab);
cachedResults.forEach((e) => {
// add matches from url cache
matches.push(...configs.filter(({ owner, repo }) => e.owner === owner && e.repo === repo));
Expand Down
6 changes: 3 additions & 3 deletions src/extension/tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,13 @@ export default async function checkTab(id) {
url = await getProxyUrl(tab);
}
// fill url cache
await urlCache.set(url, projects);
await urlCache.set(tab, projects);

// todo: if share url, inject install helper

const matches = await getProjectMatches(projects, url);
const matches = await getProjectMatches(projects, tab);

const config = matches.length === 1 ? matches[0] : await getProjectFromUrl(url);
const config = matches.length === 1 ? matches[0] : await getProjectFromUrl(tab);

if (matches.length > 0) {
// inject content script and send matches to tab
Expand Down
161 changes: 95 additions & 66 deletions src/extension/url-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,78 +43,106 @@ export function isGoogleDriveHost(url) {
return /^(docs|drive)\.google\.com$/.test(host);
}

/**
* Encodes the URL to be used in the `/shares/` MSGraph API call
* @param {string} sharingUrl The sharing URL
* @returns {string} The encoded sharing URL
*/
function encodeSharingUrl(sharingUrl) {
const base64 = btoa(sharingUrl)
.replace(/=/, '')
.replace(/\//, '_')
.replace(/\+/, '-');
return `u!${base64}`;
}

/**
* Fetches the edit info from Microsoft SharePoint.
* @todo also use fstab information to figure out the resource path etc.
* @param {string} url The URL
* @param {chrome.tabs.Tab} tab The tab
* @returns {Promise<Object>} The edit info
*/
async function fetchSharePointEditInfo(url) {
const spUrl = new URL(url);
// sometimes sharepoint redirects to an url with a search param `RootFolder` instead of `id`
// and then the sharelink can't be resolved. so we convert it to `id`
const rootFolder = spUrl.searchParams.get('RootFolder');
if (rootFolder) {
spUrl.searchParams.set('id', rootFolder);
spUrl.searchParams.delete('RootFolder');
}
const shareLink = encodeSharingUrl(spUrl.href);
spUrl.pathname = `/_api/v2.0/shares/${shareLink}/driveItem`;
spUrl.search = '';
let resp = await fetch(spUrl);
if (!resp.ok) {
log.debug('unable to resolve edit url ', resp.status, await resp.text());
return null;
}
const data = await resp.json();

// get root item
spUrl.pathname = `/_api/v2.0/drives/${data.parentReference.driveId}`;
resp = await fetch(spUrl);
if (!resp.ok) {
log.debug('unable to load root url: ', resp.status, await resp.text());
return null;
}
const rootData = await resp.json();
async function fetchSharePointEditInfo(tab) {
return new Promise((resolve) => {
// inject edit info retriever
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: async (tabUrl) => {
const encodeSharingUrl = (sharingUrl) => {
const base64 = btoa(sharingUrl)
.replace(/=/, '')
.replace(/\//, '_')
.replace(/\+/, '-');
return `u!${base64}`;
};

const info = {
status: 200,
name: data.name,
sourceLocation: `onedrive:/drives/${data.parentReference.driveId}/items/${data.id}`,
lastModified: data.lastModifiedDateTime,
};
if (data.folder) {
info.url = data.webUrl;
info.contentType = 'application/folder';
info.childCount = data.folder.childCount;
} else {
const folder = data.parentReference.path.split(':').pop();
info.url = `${rootData.webUrl}${folder}/${data.name}`;
info.contentType = data.file.mimeType;
}
return info;
const fetchEditInfo = async (urlFromTab) => {
// convert `RootFolder` to `id` if present
const spUrl = new URL(urlFromTab);
const rootFolder = spUrl.searchParams.get('RootFolder');
if (rootFolder) {
spUrl.searchParams.set('id', rootFolder);
spUrl.searchParams.delete('RootFolder');
}
const shareLink = encodeSharingUrl(spUrl.href);
spUrl.pathname = `/_api/v2.0/shares/${shareLink}/driveItem`;
spUrl.search = '';
let resp = await fetch(spUrl, { credentials: 'include' });
if (!resp.ok) {
log.warn('unable to resolve edit url: ', resp.status, await resp.text());
return null;
}
const data = await resp.json();

// get root item
spUrl.pathname = `/_api/v2.0/drives/${data.parentReference.driveId}`;
resp = await fetch(spUrl, { credentials: 'include' });
if (!resp.ok) {
log.warn('unable to load root url: ', resp.status, await resp.text());
return null;
}
const rootData = await resp.json();

const info = {
status: 200,
name: data.name,
sourceLocation: `onedrive:/drives/${data.parentReference.driveId}/items/${data.id}`,
lastModified: data.lastModifiedDateTime,
};
if (data.folder) {
info.url = data.webUrl;
info.contentType = 'application/folder';
info.childCount = data.folder.childCount;
} else {
const folder = data.parentReference.path.split(':').pop();
info.url = `${rootData.webUrl}${folder}/${data.name}`;
info.contentType = data.file.mimeType;
}
return info;
};
const spEditInfo = await fetchEditInfo(tabUrl);

chrome.runtime.sendMessage({ spEditInfo });
},
args: [tab.url],
}).catch((e) => {
log.warn('fetchSharePointEditInfo: failed to inject script', e);
resolve(null);
});

// listen for edit info from tab
const listener = ({ spEditInfo }, { tab: msgTab }) => {
// check if message contains edit info and is sent from right tab
if (typeof spEditInfo !== 'undefined' && tab && tab.id === msgTab.id) {
chrome.runtime.onMessage.removeListener(listener);
resolve(spEditInfo);
}
};
chrome.runtime.onMessage.addListener(listener);

// resolve with null after 1s
setTimeout(() => {
log.debug('fetchSharePointEditInfo: timed out');
chrome.runtime.onMessage.removeListener(listener);
resolve(null);
}, 1000);
});
}

/**
* Fetches the edit info from Google Drive.
* @todo implement
* @param {string} url The URL
* @param {chrome.tabs.Tab} tab The tab
* @returns {Promise<Object>} The edit info
*/
async function fetchGoogleDriveEditInfo(url) {
async function fetchGoogleDriveEditInfo({ url }) {
// todo: implement
return {
url,
Expand All @@ -137,10 +165,10 @@ async function fetchGoogleDriveEditInfo(url) {
class UrlCache {
/**
* Looks up a URL and returns its associated projects, or an empty array.
* @param {string} url The URL
* @param {chrome.tabs.Tab} tab The tab
* @returns {Promise<Object[]>} The project results from the cached entry
*/
async get(url) {
async get({ url }) {
const urlCache = await getConfig('session', 'urlCache') || [];
const entry = urlCache.find((e) => e.url === url);
if (entry && (!entry.expiry || entry.expiry > Date.now())) {
Expand All @@ -153,11 +181,12 @@ class UrlCache {

/**
* Updates the URL cache for the duration of the browser session.
* @param {string} url The URL
* @param {chrome.tabs.Tab} tab The tab
* @param {SidekickConfig|SidekickConfig[]} [config] The project config(s)
* @returns {Promise<void>}
*/
async set(url, config = {}) {
async set(tab, config = {}) {
const { url } = tab;
// @ts-ignore
const { owner, repo } = config;
const createCacheEntry = (cacheUrl, results, expiry = 0) => {
Expand Down Expand Up @@ -194,10 +223,10 @@ class UrlCache {
if (!isSPHost && !isGoogleDriveHost(url)) {
return;
}
if ((await this.get(url)).length === 0) {
if ((await this.get(tab)).length === 0) {
const info = isSPHost
? await fetchSharePointEditInfo(url)
: await fetchGoogleDriveEditInfo(url);
? await fetchSharePointEditInfo(tab)
: await fetchGoogleDriveEditInfo(tab);
log.debug('resource edit info', info);

let results = [];
Expand Down
Loading

0 comments on commit d342faf

Please sign in to comment.