Skip to content

Commit 4ded191

Browse files
committed
feat: infra to register and trigger lp function from phoenixLiveDevProtocol.triggerLPFn
1 parent 2584b26 commit 4ded191

File tree

3 files changed

+110
-7
lines changed

3 files changed

+110
-7
lines changed

src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -341,11 +341,9 @@
341341
* @param {string} msgStr The protocol message as stringified JSON.
342342
*/
343343
message: function (msgStr) {
344-
var msg;
345-
try {
346-
msg = JSON.parse(msgStr);
347-
} catch (e) {
348-
console.error("[Brackets LiveDev] Malformed message received: ", msgStr);
344+
const msg = JSON.parse(msgStr);
345+
if(msg && typeof msg === "object" && msg.method === "PhoenixComm.execLPFn") {
346+
_onLPFnTrigger(msg.fnName, msg.params);
349347
return;
350348
}
351349
// delegates handling/routing to MessageBroker.
@@ -363,6 +361,37 @@
363361

364362
ProtocolManager.setProtocolHandler(ProtocolHandler);
365363

364+
const registeredPhoenixCommFns = {};
365+
366+
// never rejects
367+
function _onLPFnTrigger(fnName, paramObj) {
368+
const lpFn = registeredPhoenixCommFns[fnName];
369+
if(!lpFn) {
370+
console.error(`PhoenixComm: No such LP function ${fnName}`);
371+
}
372+
try {
373+
const response = lpFn(paramObj);
374+
if(response instanceof Promise) {
375+
response.catch(err => {
376+
console.error(`PhoenixComm: Error executing LP function ${fnName}`, err);
377+
});
378+
}
379+
} catch (e) {
380+
console.error(`PhoenixComm: Error executing LP function ${fnName}`, e);
381+
}
382+
}
383+
384+
const PhoenixComm = {
385+
registerLpFn: function (fnName, fn) {
386+
if(registeredPhoenixCommFns[fnName]){
387+
throw new Error(`Function "${fnName}" already registered with PhoenixComm`);
388+
}
389+
registeredPhoenixCommFns[fnName] = fn;
390+
}
391+
};
392+
393+
global._Brackets_LiveDev_PhoenixComm = PhoenixComm;
394+
366395
window.addEventListener('load', function () {
367396
ProtocolManager.enable();
368397
});

src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ function RemoteFunctions(config = {}) {
1515
HIGHLIGHT_CLASSNAME: "__brackets-ld-highlight" // CSS class name used for highlighting elements in live preview
1616
};
1717

18+
// this is for bidirectional communication between phoenix and live preview
19+
const PhoenixComm = window._Brackets_LiveDev_PhoenixComm;
20+
PhoenixComm.registerLpFn("PH_Hello", function(param) {
21+
// this is just a test function here to check if live preview. fn call is working correctly.
22+
console.log("Hello World", param);
23+
});
24+
1825
const SHARED_STATE = {
1926
__description: "Use this to keep shared state for Live Preview Edit instead of window.*"
2027
};

src/LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol.js

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ define(function (require, exports, module) {
6161
const EVENT_LIVE_PREVIEW_CLICKED = "livePreviewClicked",
6262
EVENT_LIVE_PREVIEW_RELOAD = "livePreviewReload";
6363

64+
const MAX_PENDING_LP_CALLS_1000 = 1000;
65+
6466
/**
6567
* @private
6668
* Active connections.
@@ -205,7 +207,7 @@ define(function (require, exports, module) {
205207
}
206208

207209
const processedMessageIDs = new Phoenix.libs.LRUCache({
208-
max: 1000
210+
max: MAX_PENDING_LP_CALLS_1000
209211
// we dont need to set a ttl here as message ids are unique throughout lifetime. And old ids will
210212
// start getting evited from the cache. the message ids are only an issue within a fraction of a seconds when
211213
// a series of messages are sent in quick succession. Eg. user click on a div and there are 3 tabs and due to
@@ -294,7 +296,7 @@ define(function (require, exports, module) {
294296
// broadcast if there are no specific clients
295297
clients = clients || getConnectionIds();
296298
msg.id = id;
297-
_responseDeferreds[id] = result;
299+
_responseDeferreds[id] = result; // todo responses deffered if size larger than 100k enttries raise metric and warn in console once every 10 seconds long only
298300
_transport.send(clients, JSON.stringify(msg));
299301
return result.promise();
300302
}
@@ -484,6 +486,68 @@ define(function (require, exports, module) {
484486
);
485487
}
486488

489+
const registeredFunctions = {};
490+
function registerPhoenixFn(fnName, fn) {
491+
if(registeredFunctions[fnName]){
492+
throw new Error(`Function "${fnName}" already registered with LPComm`);
493+
}
494+
registeredFunctions[fnName] = fn;
495+
}
496+
497+
/**
498+
* Triggers a named API function in the Live Preview for one or more clients
499+
* in a **fire-and-forget** manner.
500+
*
501+
* This API intentionally does **not** return a value or Promise.
502+
*
503+
* Live Preview connections are considered unreliable:
504+
* - Multiple Live Preview clients may exist, or none at all.
505+
* - Clients may be geographically distant and slow to respond.
506+
* - If a Live Preview disconnects unexpectedly, Phoenix may take up to ~10 seconds
507+
* to detect the disconnection, during which calls would otherwise block.
508+
*
509+
* Because of these constraints, this function does **not** wait for acknowledgements
510+
* or responses, and callers should **not** rely on timely execution or delivery.
511+
*
512+
* Use this method only for best-effort notifications or side effects in Live Preview.
513+
*
514+
* If a response or guaranteed delivery is required, invoke this function using
515+
* `triggerLPFn()` and have the corresponding Live Preview handler explicitly
516+
* send the result back to Phoenix via `PhoenixComm.execFn`.
517+
*
518+
* @param {string} fnName
519+
* Name of the Live Preview API function to invoke.
520+
*
521+
* @param {*} fnArgs
522+
* Arguments to pass to the Live Preview function(object/string). Must be JSON-serializable.
523+
*
524+
* @param {number|number[]=} clientIdOrArray
525+
* Optional client ID or array of client IDs obtained from getConnectionIds().
526+
* If omitted, the function is executed for all active Live Preview connections.
527+
*/
528+
function triggerLPFn(fnName, fnArgs, clientIdOrArray) {
529+
let clientIds;
530+
531+
if (clientIdOrArray === undefined || clientIdOrArray === null) {
532+
clientIds = getConnectionIds();
533+
} else if (Array.isArray(clientIdOrArray)) {
534+
clientIds = clientIdOrArray;
535+
} else {
536+
clientIds = [clientIdOrArray];
537+
}
538+
539+
clientIds.map(clientId => {
540+
_transport.send([clientId], JSON.stringify({
541+
method: "PhoenixComm.execLPFn",
542+
fnName,
543+
params: fnArgs
544+
}));
545+
});
546+
}
547+
548+
549+
window.ee= triggerLPFn; // todo remove this once all usages are migrated to execLPFn
550+
487551
/**
488552
* Closes the connection to the given client. Proxies to the transport.
489553
* @param {number} clientId
@@ -514,6 +578,9 @@ define(function (require, exports, module) {
514578
exports.closeAllConnections = closeAllConnections;
515579
exports.setLivePreviewMessageHandler = setLivePreviewMessageHandler;
516580
exports.setCustomRemoteFunctionProvider = setCustomRemoteFunctionProvider;
581+
// lp communication functions
582+
exports.registerPhoenixFn = registerPhoenixFn;
583+
exports.triggerLPFn = triggerLPFn;
517584
exports.LIVE_DEV_REMOTE_SCRIPTS_FILE_NAME = LIVE_DEV_REMOTE_SCRIPTS_FILE_NAME;
518585
exports.LIVE_DEV_REMOTE_WORKER_SCRIPTS_FILE_NAME = LIVE_DEV_REMOTE_WORKER_SCRIPTS_FILE_NAME;
519586
exports.EVENT_LIVE_PREVIEW_CLICKED = EVENT_LIVE_PREVIEW_CLICKED;

0 commit comments

Comments
 (0)