Enables the OnlyOffice Connector API (a Developer Edition feature) in the free Community Edition via a patched Docker image. Two runtime modifications: 1. Remove advancedApi license gate from minified sdk-all-min.js files 2. Append createConnector() monkey-patch to api.js.tpl This allows the RedatorContencioso frontend to use createConnector(), executeMethod, attachEvent, and addContextMenuItem without a paid license.pull/3593/head
parent
4aa313fcbd
commit
aa4c2554f1
@ -0,0 +1,8 @@ |
||||
FROM onlyoffice/documentserver:latest |
||||
|
||||
# --- Patch 1: Remove advancedApi license gate --- |
||||
# Uses perl for multi-line regex (available in Ubuntu base image). |
||||
# Removes the if(advancedApi)return; check from ALL JS files under sdkjs/. |
||||
COPY patch.sh /tmp/patch.sh |
||||
COPY createConnector.js /tmp/createConnector.js |
||||
RUN chmod +x /tmp/patch.sh && /tmp/patch.sh && rm /tmp/patch.sh /tmp/createConnector.js |
||||
@ -0,0 +1,199 @@ |
||||
|
||||
/* ===== createConnector patch (appended) ===== */ |
||||
(function() { |
||||
console.log('[ConnectorPatch] Initializing createConnector patch...'); |
||||
if (!window.DocsAPI || !window.DocsAPI.DocEditor) { |
||||
console.warn('[ConnectorPatch] DocsAPI.DocEditor not found, skipping patch'); |
||||
return; |
||||
} |
||||
|
||||
var _origDocEditor = window.DocsAPI.DocEditor; |
||||
console.log('[ConnectorPatch] Wrapping DocsAPI.DocEditor'); |
||||
|
||||
window.DocsAPI.DocEditor = function(placeholderId, config) { |
||||
console.log('[ConnectorPatch] DocsAPI.DocEditor called with id:', placeholderId); |
||||
// Save parent before the original replaces the placeholder div with an iframe
|
||||
var targetDiv = document.getElementById(placeholderId); |
||||
var parentEl = targetDiv ? targetDiv.parentNode : null; |
||||
console.log('[ConnectorPatch] targetDiv:', !!targetDiv, 'parentEl:', !!parentEl); |
||||
|
||||
// Call the original constructor
|
||||
var instance = _origDocEditor.apply(this, arguments); |
||||
console.log('[ConnectorPatch] Original constructor returned:', typeof instance, instance ? Object.keys(instance).slice(0, 5) : 'null'); |
||||
|
||||
// Find the editor iframe that replaced the placeholder
|
||||
var editorIframe = null; |
||||
if (parentEl) { |
||||
editorIframe = parentEl.querySelector('iframe[name="frameEditor"]'); |
||||
} |
||||
if (!editorIframe) { |
||||
// Fallback: find any frameEditor iframe
|
||||
editorIframe = document.querySelector('iframe[name="frameEditor"]'); |
||||
} |
||||
console.log('[ConnectorPatch] editorIframe found:', !!editorIframe); |
||||
|
||||
// Add createConnector to the instance
|
||||
if (instance && editorIframe) { |
||||
console.log('[ConnectorPatch] Adding createConnector to instance'); |
||||
instance.createConnector = function() { |
||||
console.log('[ConnectorPatch] createConnector() called'); |
||||
var iframe = editorIframe; |
||||
var guid = 'asc.{' + 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { |
||||
var r = Math.random() * 16 | 0; |
||||
var v = c === 'x' ? r : (r & 0x3 | 0x8); |
||||
return v.toString(16); |
||||
}) + '}'; |
||||
|
||||
var methodCallbacks = []; |
||||
var commandCallbacks = []; |
||||
var eventHandlers = {}; |
||||
var contextMenuClickHandlers = {}; |
||||
var contextMenuIdCounter = 0; |
||||
|
||||
function sendToIframe(data) { |
||||
if (iframe && iframe.contentWindow) { |
||||
iframe.contentWindow.postMessage(JSON.stringify({ |
||||
type: "onExternalPluginMessage", |
||||
subType: "connector", |
||||
data: data |
||||
}), "*"); |
||||
} |
||||
} |
||||
|
||||
function onMessageHandler(event) { |
||||
var parsed; |
||||
try { |
||||
parsed = typeof event.data === 'string' ? JSON.parse(event.data) : event.data; |
||||
} catch(e) { |
||||
return; |
||||
} |
||||
if (!parsed) return; |
||||
|
||||
// Unwrap the callback envelope
|
||||
if (parsed.type === "onExternalPluginMessageCallback") { |
||||
parsed = parsed.data; |
||||
if (!parsed) return; |
||||
} |
||||
|
||||
if (parsed.guid !== guid) return; |
||||
|
||||
switch (parsed.type) { |
||||
case "onMethodReturn": { |
||||
var cb = methodCallbacks.shift(); |
||||
if (cb) cb(parsed.methodReturnData); |
||||
break; |
||||
} |
||||
case "onCommandCallback": { |
||||
var cb = commandCallbacks.shift(); |
||||
if (cb) cb(parsed.commandReturnData); |
||||
break; |
||||
} |
||||
case "onEvent": { |
||||
var evtName = parsed.eventName; |
||||
if (evtName === "onContextMenuClick") { |
||||
// The plugin system appends "_oo_sep_" + extra data to the item ID.
|
||||
// Strip it to match our stored handler keys.
|
||||
var clickData = parsed.eventData || ""; |
||||
var sepIdx = clickData.indexOf("_oo_sep_"); |
||||
var itemId = sepIdx !== -1 ? clickData.substring(0, sepIdx) : clickData; |
||||
var clickHandler = contextMenuClickHandlers[itemId]; |
||||
if (clickHandler) clickHandler(); |
||||
} |
||||
var handler = eventHandlers[evtName]; |
||||
if (handler) handler(parsed.eventData); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
window.addEventListener('message', onMessageHandler); |
||||
|
||||
// Register connector with the editor's plugin system
|
||||
sendToIframe({ type: "register", guid: guid }); |
||||
|
||||
var connectorApi = { |
||||
executeMethod: function(name, params, callback) { |
||||
if (callback) methodCallbacks.push(callback); |
||||
sendToIframe({ |
||||
type: "method", |
||||
guid: guid, |
||||
methodName: name, |
||||
data: params || [] |
||||
}); |
||||
}, |
||||
callCommand: function(func, isClose, isCalc, callback) { |
||||
if (callback) commandCallbacks.push(callback); |
||||
sendToIframe({ |
||||
type: "command", |
||||
guid: guid, |
||||
data: typeof func === 'function' ? '(' + func.toString() + ')()' : func, |
||||
recalculate: isCalc !== false, |
||||
resize: isClose !== false |
||||
}); |
||||
}, |
||||
attachEvent: function(name, callback) { |
||||
eventHandlers[name] = callback; |
||||
sendToIframe({ type: "attachEvent", guid: guid, name: name }); |
||||
}, |
||||
detachEvent: function(name) { |
||||
delete eventHandlers[name]; |
||||
sendToIframe({ type: "detachEvent", guid: guid, name: name }); |
||||
}, |
||||
addContextMenuItem: function(items) { |
||||
if (!eventHandlers["onContextMenuClick"]) { |
||||
connectorApi.attachEvent("onContextMenuClick", function() {}); |
||||
} |
||||
var protocolItems = []; |
||||
for (var i = 0; i < items.length; i++) { |
||||
var item = items[i]; |
||||
var itemId = "connector_item_" + (++contextMenuIdCounter); |
||||
if (item.onClick) { |
||||
contextMenuClickHandlers[itemId] = item.onClick; |
||||
} |
||||
var protoItem = { |
||||
id: itemId, |
||||
text: item.text || "", |
||||
data: item.data || "", |
||||
disabled: !!item.disabled, |
||||
icons: item.icons || "" |
||||
}; |
||||
// Only include items if there are actual sub-items.
|
||||
// An empty array causes the web app to render a submenu
|
||||
// arrow, preventing click events from firing.
|
||||
if (item.items && item.items.length > 0) { |
||||
protoItem.items = item.items; |
||||
} |
||||
protocolItems.push(protoItem); |
||||
} |
||||
connectorApi.executeMethod("AddContextMenuItem", [{ |
||||
guid: guid, |
||||
items: protocolItems |
||||
}]); |
||||
}, |
||||
disconnect: function() { |
||||
window.removeEventListener('message', onMessageHandler); |
||||
sendToIframe({ type: "unregister", guid: guid }); |
||||
methodCallbacks = []; |
||||
commandCallbacks = []; |
||||
eventHandlers = {}; |
||||
contextMenuClickHandlers = {}; |
||||
} |
||||
}; |
||||
|
||||
return connectorApi; |
||||
}; |
||||
} |
||||
|
||||
return instance; |
||||
}; |
||||
|
||||
// Preserve static properties and methods
|
||||
window.DocsAPI.DocEditor.defaultConfig = _origDocEditor.defaultConfig; |
||||
window.DocsAPI.DocEditor.version = _origDocEditor.version; |
||||
if (_origDocEditor.warmUp) window.DocsAPI.DocEditor.warmUp = _origDocEditor.warmUp; |
||||
for (var prop in _origDocEditor) { |
||||
if (_origDocEditor.hasOwnProperty(prop) && !window.DocsAPI.DocEditor[prop]) { |
||||
window.DocsAPI.DocEditor[prop] = _origDocEditor[prop]; |
||||
} |
||||
} |
||||
})(); |
||||
@ -0,0 +1,63 @@ |
||||
#!/bin/bash |
||||
set -e |
||||
|
||||
ROOT="/var/www/onlyoffice/documentserver" |
||||
|
||||
echo "=== Patch 1: Remove advancedApi license gate ===" |
||||
|
||||
PATCHED=0 |
||||
for file in $(grep -rl 'advancedApi' "$ROOT" --include='*.js' 2>/dev/null); do |
||||
echo "Found advancedApi in: $file" |
||||
# The minified code has: <obfuscated>.advancedApi&&("connector"===... |
||||
# Replace .advancedApi&& with && to bypass the license check. |
||||
# This turns X.Y.advancedApi&&(...) into X.Y&&(...), which just |
||||
# checks that licenseResult exists without requiring advancedApi flag. |
||||
sed -i 's/\.advancedApi&&/\&\&/g' "$file" |
||||
# Verify |
||||
if grep -q '\.advancedApi' "$file"; then |
||||
echo " -> WARNING: .advancedApi still present after sed!" |
||||
else |
||||
echo " -> Verified: advancedApi gate removed!" |
||||
fi |
||||
PATCHED=$((PATCHED + 1)) |
||||
done |
||||
|
||||
if [ "$PATCHED" -eq 0 ]; then |
||||
echo "WARNING: No files with advancedApi pattern found!" |
||||
else |
||||
echo "Patched $PATCHED file(s)" |
||||
fi |
||||
|
||||
echo "" |
||||
echo "=== Patch 2: Add createConnector() to api.js.tpl ===" |
||||
|
||||
# The actual api.js is generated at container startup from api.js.tpl |
||||
TPL_FILE="$ROOT/web-apps/apps/api/documents/api.js.tpl" |
||||
|
||||
if [ ! -f "$TPL_FILE" ]; then |
||||
echo "ERROR: Template not found at $TPL_FILE" |
||||
echo "Searching for api.js.tpl..." |
||||
find /var/www -name 'api.js.tpl' -type f 2>/dev/null |
||||
exit 1 |
||||
fi |
||||
|
||||
echo "Found template: $TPL_FILE" |
||||
echo "File size: $(wc -c < "$TPL_FILE") bytes" |
||||
|
||||
if grep -q 'createConnector' "$TPL_FILE"; then |
||||
echo "Already patched, skipping." |
||||
else |
||||
# Append the createConnector monkey-patch to the template |
||||
cat /tmp/createConnector.js >> "$TPL_FILE" |
||||
echo "Appended createConnector to api.js.tpl" |
||||
fi |
||||
|
||||
if grep -q 'createConnector' "$TPL_FILE"; then |
||||
echo "SUCCESS: createConnector is present in $TPL_FILE" |
||||
else |
||||
echo "ERROR: createConnector not found after patching!" |
||||
exit 1 |
||||
fi |
||||
|
||||
echo "" |
||||
echo "=== All patches applied successfully ===" |
||||
Loading…
Reference in new issue