Skip to content

Commit

Permalink
BDE-226 fixing up designer live preview redirections
Browse files Browse the repository at this point in the history
  • Loading branch information
nxmatic committed Mar 14, 2024
1 parent 695612c commit a0c48d8
Show file tree
Hide file tree
Showing 8 changed files with 740 additions and 693 deletions.
2 changes: 1 addition & 1 deletion src/main/connect-locator.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class ConnectLocator {
});
}

url(value) {
withUrl(value) {
const method = this.worker.browserStore[value ? 'set' : 'get'];
const data = value ? { 'connect-url': value.toString() } : { 'connect-url': 'https://connect.nuxeo.com' };

Expand Down
85 changes: 51 additions & 34 deletions src/main/declarative-net-engine.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
/* eslint-disable max-classes-per-file */

function hashCode(s) {
let hash = 0;
if (s.length === 0) return hash;
for (let i = 0; i < s.length; i++) {
const char = s.charCodeAt(i);
// eslint-disable-next-line no-bitwise
hash = ((hash << 5) - hash) + char;
// eslint-disable-next-line no-bitwise
hash |= 0; // Convert to 32-bit integer
}
return Math.abs(hash); // Ensure the hash is always positive
}

class BaseRule {
constructor() {
this.id = -1;
Expand All @@ -16,15 +29,13 @@ class CookieHeaderRule extends BaseRule {

// eslint-disable-next-line class-methods-use-this
keyOf() {
return 'cookieHeader';
return `cookieHeader-${this.rootUrl}`;
}

toJson(id = 1, priority = 1) {
this.id = id;
this.priority = priority;
toJson(priority = 1) {
return {
id: this.id,
priority: this.priority,
id: hashCode(this.rootUrl),
priority,
condition: {
urls: [`${this.rootUrl}/*`],
},
Expand Down Expand Up @@ -53,17 +64,16 @@ class RedirectRule extends BaseRule {
return this.from;
}

toJson(id = 1, priority = 1) {
toJson(priority = 1) {
return {
id,
id: hashCode(this.from.toString()),
priority,
condition: {
urlFilter: this.from,
resourceTypes: ['main_frame', 'sub_frame'],
urlFilter: this.from.toString(),
},
action: {
type: 'redirect',
redirect: { url: this.to },
redirect: { url: this.to.toString() },
},
};
}
Expand All @@ -78,13 +88,6 @@ class DeclarativeNetEngine {
this.nextId = 1;

// Bind methods
this.clear = this.clear.bind(this);
this.push = this.push.bind(this);
this.pop = this.pop.bind(this);
this.flush = this.flush.bind(this);
this.flushed = this.flushed.bind(this);
this.pending = this.pending.bind(this);
this.reset = this.reset.bind(this);
Object.getOwnPropertyNames(Object.getPrototypeOf(this))
.filter((prop) => typeof this[prop] === 'function' && prop !== 'constructor')
.forEach((method) => {
Expand All @@ -100,14 +103,20 @@ class DeclarativeNetEngine {
this.rules[rule.keyOf()] = rule;
this.rulesToAdd.push(rule);
this.nextId += 1;
this.worker.developmentMode.asConsole().then((console) => console.log(`Pushed rule: ${JSON.stringify(rule.toJson())}`));
this.worker.developmentMode
.asConsole()
.then((console) => console
.log(`Pushed rule: ${JSON.stringify(rule.toJson())}`));
}

pop(key) {
const rule = this.rules[key];
delete this.rules[key];
this.rulesToRemove.push(rule);
this.worker.developmentMode.asConsole().then((console) => console.log(`Popped rule: ${JSON.stringify(rule.toJson())}`));
this.worker.developmentMode
.asConsole()
.then((console) => console
.log(`Popped rule: ${JSON.stringify(rule.toJson())}`));
return rule;
}

Expand All @@ -128,8 +137,10 @@ class DeclarativeNetEngine {
return Promise.resolve()
.then(() => chrome.declarativeNetRequest.getDynamicRules())
.then((rules) => {
this.worker.developmentMode.asConsole()
.then((console) => console.log(`Flushed rules: ${JSON.stringify(rules)}`));
this.worker.developmentMode
.asConsole()
.then((console) => console
.log(`Flushed rules: ${JSON.stringify(rules)}`));
return rules;
});
}
Expand All @@ -138,12 +149,13 @@ class DeclarativeNetEngine {
return Promise.resolve({
addRules: this.rulesToAdd.map((rule) => rule.toJson()),
removeRuleIds: this.rulesToRemove.map((rule) => rule.id),
})
.then((pending) => {
this.worker.developmentMode.asConsole()
.then((console) => console.log(`Pending rules: ${JSON.stringify(pending)}`));
return pending;
});
}).then((pending) => {
this.worker.developmentMode
.asConsole()
.then((console) => console
.log(`Pending rules: ${JSON.stringify(pending)}`));
return pending;
});
}

clear() {
Expand All @@ -157,12 +169,17 @@ class DeclarativeNetEngine {
this.rulesToAdd = [];
this.rulesToRemove = [];
this.nextId = 1;
return chrome.declarativeNetRequest.getDynamicRules().then((rules) => {
const ruleIds = rules.map((rule) => rule.id);
return chrome.declarativeNetRequest.updateDynamicRules({ removeRuleIds: ruleIds });
}).catch((error) => {
console.error('Failed to remove dynamic rules:', error);
});
return chrome.declarativeNetRequest
.getDynamicRules()
.then((rules) => {
const ruleIds = rules.map((rule) => rule.id);
return chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: ruleIds,
});
})
.catch((error) => {
console.error('Failed to remove dynamic rules:', error);
});
}
}

Expand Down
157 changes: 89 additions & 68 deletions src/main/designer-live-preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,25 @@ class DesignerLivePreview {
}

pushRedirectionsOf(rootUrl, json) {
const nuxeoBase = rootUrl.href.endsWith('/') ? rootUrl.href : `${rootUrl.href}/`;
Object.keys(json).forEach((basePath) => {
const nuxeoInstanceBasePath = basePath.replace(
const nuxeoBasePath = basePath.replace(
/^\/(nuxeo\.war\/?\/)?/,
''
);

if (!basePath || basePath.length === 0 || !json[basePath]) {
return;
}
const files = Object.keys(json[basePath]);
files.forEach((resource) => {
const connectResource = json[basePath][resource];
const nuxeoInstanceResource = `${rootUrl}/${nuxeoInstanceBasePath}/${resource}`;
this.worker.declarativeNetEngine.push(new RedirectRule(nuxeoInstanceResource, connectResource));
files.forEach((resourcePath) => {
const connectUrl = new URL(json[basePath][resourcePath]);
const nuxeoUrl = new URL(`${nuxeoBasePath}/${resourcePath}`, nuxeoBase);
this.pushRedirection(nuxeoUrl, connectUrl);
});
});
}

addNewRedirectionsOf(details) {
addRedirectionsOf(details) {
// Detects when Studio users save changes to a new resource in Designer
if (details.method !== 'POST') {
return;
Expand All @@ -65,24 +68,54 @@ class DesignerLivePreview {
const connectResource = `${details.url}${resourcePath}`;
resourcePath = resourcePath.replace(/^\/(nuxeo\.war\/?\/)?/, '');
const nuxeoResource = `${this.nuxeo.baseUrl}${resourcePath}`;
this.worker.declarativeNetEngine.push(new RedirectRule(nuxeoResource, connectResource));
});
this.worker.declarativeNetEngine.flush().catch((error) => {
console.error('Failed to add new redirection rules:', error);
this.pushRedirection(nuxeoResource, connectResource);
});
this.flushRedirections();
}

removeRedirectionsOf(details) {
// Detects when Studio users revert their customizations to default
if (details.method !== 'DELETE') {
return;
}
this.worker.declarativeNetEngine.pop(details.url);
this.popRedirection(details.url);
this.flushRedirections();
}

flushRedirections() {
this.worker.declarativeNetEngine.flush().catch((error) => {
console.error('Failed to remove redirection rules:', error);
});
}

pushRedirection(from, to) {
this.worker.declarativeNetEngine.push(new RedirectRule(from, to));
const modifiedFrom = this.modifyUrlForUIPath(from);
if (modifiedFrom !== from) {
this.worker.declarativeNetEngine.push(new RedirectRule(modifiedFrom, to));
}
}

popRedirection(from) {
this.worker.declarativeNetEngine.pop(from);
const modifiedFrom = this.modifyUrlForUIPath(from);
if (modifiedFrom !== from) {
this.worker.declarativeNetEngine.pop(modifiedFrom);
}
}

// eslint-disable-next-line class-methods-use-this
modifyUrlForUIPath(url) {
const fragments = url.pathname.split('/');
if (fragments[2] === 'ui') {
fragments.splice(3, 0, ''); // Insert an empty string after 'ui'
const newUrl = new URL(url);
newUrl.pathname = fragments.join('/');
return newUrl;
}
return url;
}

toggle(projectName) {
return this.isEnabled()
.then((enabled) => {
Expand All @@ -95,68 +128,56 @@ class DesignerLivePreview {
}

enable(projectName) {
const rulesPusher = (connectUrl, nuxeoUrl) =>
Promise.resolve(true)
// chrome.cookies
// .getAll({ domain: connectUrl.hostname })
// .then((cookies) =>
// cookies.map((x) => `${x.name}=${x.value}`).join('; ')
// )
// .then((cookieHeader) => {
// this.worker.declarativeNetEngine.push(new CookieHeaderRule(connectUrl, cookieHeader));
// })
.then(
() =>
new URL(
`/nuxeo/site/studio/v2/project/${projectName}/workspace/ws.resources`,
connectUrl
)
const rulesPusher = (connectUrl, nuxeoUrl) => Promise
.resolve(new URL(
`/nuxeo/site/studio/v2/project/${projectName}/workspace/ws.resources`,
connectUrl
))
.then((workspaceUrl) =>
fetch(
// fetch connect workspace to get redirected URLs
workspaceUrl,
{
credentials: 'include',
}
)
.then((workspaceUrl) =>
fetch(
// fetch connect workspace to get redirected URLs
workspaceUrl,
{
credentials: 'include',
}
)
.then((response) => {
// Check if the request was successful
if (!response.ok) {
// If the status code is 401, the user is not authenticated
if (response.status === 401) {
throw new Error('Not authenticated.');
}

// If the status code is anything else, there was another type of error
throw new Error(`Request failed with status ${response.status}`);
.then((response) => {
// Check if the request was successful
if (!response.ok) {
// If the status code is 401, the user is not authenticated
if (response.status === 401) {
throw new Error('Not authenticated.');
}

// Check if a redirect occurred
if (response.url.toString() !== workspaceUrl.toString()) {
throw new Error(`Redirected from ${workspaceUrl} to ${response.url}, possibly due to not being authenticated.`);
}
// If the status code is anything else, there was another type of error
throw new Error(`Request failed with status ${response.status}`);
}

const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new Error('Unexpected content type');
}
return response.json();
})
.then((jsonData) => this.pushRedirectionsOf(nuxeoUrl, jsonData))
.then(() => workspaceUrl)
)
.then((workspaceUrl) => this.worker.declarativeNetEngine.flush().then(() => [
connectUrl.toString(),
workspaceUrl.toString(),
nuxeoUrl,
]));
// Check if a redirect occurred
if (response.url.toString() !== workspaceUrl.toString()) {
throw new Error(`Redirected from ${workspaceUrl} to ${response.url}, possibly due to not being authenticated.`);
}

const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new Error('Unexpected content type');
}
return response.json();
})
.then((jsonData) => this.pushRedirectionsOf(nuxeoUrl, jsonData))
.then(() => workspaceUrl)
)
.then((workspaceUrl) => this.worker.declarativeNetEngine.flush().then(() => [
connectUrl.toString(),
workspaceUrl.toString(),
nuxeoUrl,
]));

return Promise
.all([
this.disable(), // Ensure we don't have multiple listeners
this.worker.connectLocator // Retrieve workspace location
.url()
.withUrl()
.then((connectUrl) =>
rulesPusher(connectUrl, this.worker.serverConnector.rootUrl)
),
Expand All @@ -169,15 +190,15 @@ class DesignerLivePreview {
])
.then(([connectLocation, workspaceLocation, nuxeoLocation]) => {
chrome.webRequest.onBeforeRequest.addListener(
(details) => this.addNewRedirectionsOf(details),
(details) => this.addRedirectionsOf(details),
{
urls: [workspaceLocation],
},
['requestBody']
);
this.cleanupFunctions.push(() =>
chrome.webRequest.onBeforeRequest.removeListener(
this.addNewResources
this.addRedirectionsOf
)
);
return [connectLocation, workspaceLocation, nuxeoLocation];
Expand All @@ -193,7 +214,7 @@ class DesignerLivePreview {
this.cleanupFunctions.push(() => chrome
.webRequest
.onCompleted
.removeListener(this.revertToDefault));
.removeListener(this.removeRedirectionsOf));
return [connectLocation, workspaceLocation, nuxeoLocation];
})
.then(() => this.cleanupFunctions.push(() => this.worker.declarativeNetEngine.clear()))
Expand Down
Loading

0 comments on commit a0c48d8

Please sign in to comment.