azure-devopsazure-devops-rest-apiazure-devops-extensions

How do I interact between two custom controls / contributions?


I am creating an extension that works mostly fine. Right now I'm at some custom controls that will be in the same Work Item Form. (Every custom control has its own contribution)

The problem I'm having is that I want to invoke some events from one custom control to another, but I don't know how to do this.

I tried using the SDK.register() and I'm able to check the function/object has been registered, but how do I reference it from the other custom control?

List of things I've tried

1st try

// Receiver:
let ch = XDM.channelManager.addChannel(window.parent);
ch.getObjectRegistry().register("id", ()=>{})

// Invoker:
let ch = XDM.channelManager.addChannel(window.parent);
ch.invokeRemoteMethod("method","id")

2nd try:

// Receiver
SDK.register("id",()=>{})

// Invoker
let ch = XDM.channelManager.addChannel(window.parent);
ch.invokeRemoteMethod("method","id")

3rd try:

// receiver:
XDM.globalObjectRegistry.register("id", ()=>{})

// invoker:
let msg: XDM.IJsonRpcMessage = {
            id:1,
            instanceId:"id",
            methodName:"method"
        }
window.parent.postMessage(JSON.stringify(msg),"*");

Mixed up all the options too.

Always same error

Error: No handler found on any channel for message...

Also tried to add an event listener but got blocked.

Debugged the XDM object to see the private channels of the channelManager, even the channels created with the init have different handShakeId so I guess they are different channels for each custom control

I've checked the SDK library and it creates and interacts with an XDM channel by itself in order to interact with the Host Frame for fired events like onLoaded and stuff, but how do I start one of those events?

Any help would be appreciated, thanks!


Solution

  • As I Found, I haven't been able to connect iframes using XDM neither the SDK.Register, so I ended up using addEventListener("message", ()=>{}) and creating a simple object in order to register functions and call them via window.postMessage()

    Will leave the solution cuz maybe it helps someone in the future. Not the best solution of the world but it works. Could be improved a lot (Error handling? I think we don't do that lol)

    export class CustomControlComunication implements ICustomControlComunication {
        
        private Window : Window;
    
        readonly Functions : Record<string,Function> = { }
    
        constructor(window : Window){
            this.Window = window;
        }    
    
        InvokeRemoteFunction = (Message : IMessage) => {
            
            for(let i = 0; i < this.Window?.parent.frames.length; i++){            
                let CurrentTarget = this.Window?.parent.frames[i];
                try{
                    if(CurrentTarget.location.href.includes(Message.Target.ContributionID) || CurrentTarget.location.href.includes("localhost"))
                        CurrentTarget.postMessage(Message);
                }catch{
                    // Empty catch made for Azure frames wich will 100% fail
                }
            }
        }
    
        RegisterListener = (Listener : IListener)=>{      
    
            if(Object.values(this.Functions).filter(prop => typeof prop === "function").length == 0){
                this.Window.addEventListener("message", (msg)=>{
                    try{
                        let message : IMessage = msg.data;
                        if(message.Target.ContributionID == Listener.Target.ContributionID){
                            this.Functions[message.Target.FunctionName](message.Payload)
                        }
                    }catch (ex : any){
                        if(!ex.toString().includes("TypeError: message.Target is undefined")){ 
                            // We will be listening every message. We do not want to log every error that we will be causing.
                            console.warn("Error at execution of message: " + ex)
                            console.warn(ex);
                        }                
                    }
                })
            }       
    
            this.Functions[Listener.Target.FunctionName] = Listener.Function;
        }    
    }