最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How can I achieve cross-origin userscript communication? - Stack Overflow

programmeradmin1浏览0评论

I have two scripts. Each runs on a different subdomain of our company "Example".

Script #1 -- house.example
Script #2 -- bob.fred.example

Same domain, different subdomains.

When a particular element appears on house.example, I need to send a message over to the script running on bob.fred.example

Since Google extensions can exchange messages between extensions, there must be a way with Tampermonkey to exchange messages within the same extension, between scripts – especially if they run on the same second-level domain.

Can anyone point me in the right direction?

I have two scripts. Each runs on a different subdomain of our company "Example.com".

Script #1 -- house.example.com
Script #2 -- bob.fred.example.com

Same domain, different subdomains.

When a particular element appears on house.example.com, I need to send a message over to the script running on bob.fred.example.com

Since Google extensions can exchange messages between extensions, there must be a way with Tampermonkey to exchange messages within the same extension, between scripts – especially if they run on the same second-level domain.

Can anyone point me in the right direction?

Share Improve this question edited Mar 27, 2023 at 14:38 double-beep 5,50319 gold badges40 silver badges48 bronze badges asked Dec 12, 2016 at 23:47 cssyphuscssyphus 40k20 gold badges103 silver badges122 bronze badges 5
  • 4 That is not a duplicate to me. OP is asking about messaging in TamperMonkey, similarly to Chrome extensions' messaging. – user2345 Commented Dec 12, 2016 at 23:52
  • 1 @nicovank It's the same in Tampermonkey. Tampermonkey scripts don't have such privileges as Chrome extensions; they are very similar to regular scripts. – Michał Perłakowski Commented Dec 12, 2016 at 23:59
  • 2 @Gothdo Still, I think then the appropriate thing to do is to provide an answer saying there is no such API, but one can use regular web messaging, and provide a link. This is not a duplicate, even though the answer is similar. -- Especially because the answer could be enhanced by also presenting how one could use GM_setValue if the scripts were run on the same page. – user2345 Commented Dec 13, 2016 at 0:02
  • 3 @gibberish This forum post shows how to use GM_setValue and GM_getValue to send messages between tabs to implement a tab-level lock. forum.tampermonkey.net/viewtopic.php?f=21&t=1000#p3655 – derjanb Commented Dec 13, 2016 at 14:13
  • 1 @Gothdo, your assertion above is false; Tampermonkey scripts can do a fair bit that regular scripts can't and that duplicate does not apply to the OP's scenario. Although this question may be a duplicate of some Greasemonkey questions (The topic has definitely been covered in a GM/TM scenario), but I don't have time to search more at the moment... – Brock Adams Commented Dec 17, 2016 at 2:58
Add a comment  | 

3 Answers 3

Reset to default 12

You can use GM_getValue, GM_setValue & GM_addValueChangeListener to achieve cross-tab user script communication.

Add the following lines in your user script header.

// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addValueChangeListener

The following lines of rough code will simplify the cross-tab user script communication.

function GM_onMessage(label, callback) {
  GM_addValueChangeListener(label, function() {
    callback.apply(undefined, arguments[2]);
  });
}

function GM_sendMessage(label) {
  GM_setValue(label, Array.from(arguments).slice(1));
}

So all you'll need to do is the following to send and receive messages.

GM_onMessage('_.unique.name.greetings', function(src, message) {
  console.log('[onMessage]', src, '=>', message);
});
GM_sendMessage('_.unique.name.greetings', 'hello', window.location.href);

NOTE Sending messages may not trigger your callback if the message sent is the same as before. This is due to GM_addValueChangeListener not firing because the value has not changed, i.e. same value as before even though GM_setValue is called.

Using @grant enables the sandbox, which can sometimes result in difficulties when trying to interact with complicated page objects on Greasemonkey.

If you do not want to enable the sandbox with @grant, another option is to have the userscript create an iframe to the other domain, and then post a message to it. On the other domain, in the iframe, listen for messages. When a message is received, use BroadcastChannel to send the message to every other tab on that other domain, and your other tabs with the userscript running can have the same BroadcastChannel open and listen for messages.

For example, to create a userscript on stackoverflow.com that can send a message to a userscript running in a different tab on example.com:

// ==UserScript==
// @name             0 Cross-tab example
// @include          /^https://example\.com\/$/
// @include          /^https://stackoverflow\.com\/$/
// @grant            none
// ==/UserScript==

if (window.location.href === 'https://example.com/') {
  const broadcastChannel = new BroadcastChannel('exampleUserscript');
  if (window.top !== window) {
    // We're in the iframe:
    window.addEventListener('message', (e) => {
      if (e.origin === 'https://stackoverflow.com') {
        broadcastChannel.postMessage(e.data);
      }
    });
  } else {
    // We're on a top-level tab:
    broadcastChannel.addEventListener('message', (e) => {
      console.log('Got message', e.data);
    });
  }
} else {
  // We're on Stack Overflow:
  const iframe = document.body.appendChild(document.createElement('iframe'));
  iframe.style.display = 'none';
  iframe.src = 'https://example.com';

  setTimeout(() => {
    iframe.contentWindow.postMessage('Sending message from Stack Overflow', '*');
  }, 2000);
}

This results in:

If you want two-way communication, not just one-way communication, have both parent pages create a child iframe to a single target domain (say, to example.com). To communicate to other tabs, post a message to the child iframe. Have the child iframe listen for messages, and when seen, post a BroadcastChannel message to communicate with all other iframes. When an iframe receives a BroadcastChannel message, relay it to the parent window with postMessage.

// ==UserScript==
// @name             0 Cross-tab example
// @include          /^https://example\.com\/$/
// @include          /^https://(?:stackoverflow|stackexchange)\.com\/$/
// @grant            none
// ==/UserScript==

if (window.location.href === 'https://example.com/') {
  const broadcastChannel = new BroadcastChannel('exampleUserscript');
  if (window.top !== window) {
    // We're in an iframe:
    window.addEventListener('message', (e) => {
      console.log('iframe received message from top window');
      if (e.origin === 'https://stackoverflow.com' || e.origin === 'https://stackexchange.com') {
        broadcastChannel.postMessage(e.data);
      }
    });
    broadcastChannel.addEventListener('message', (e) => {
      console.log('iframe received message from BroadcastChannel');
      window.top.postMessage(e.data, '*');
    });
  }
} else {
  // We're on Stack Overflow or Stack Exchange
  const iframe = document.body.appendChild(document.createElement('iframe'));
  iframe.style.display = 'none';
  iframe.src = 'https://example.com';
  window.addEventListener('message', (e) => {
    if (e.origin === 'https://example.com') {
      console.log(`Top window ${window.origin} received message from iframe:`, e.data);
    }
  });
  if (window.location.href === 'https://stackoverflow.com/') {
    setTimeout(() => {
      console.log('stackoverflow posting message to iframe');
      iframe.contentWindow.postMessage('Message from stackoverflow', '*');
    }, 2000);
  }
}

In the above code, a tab on Stack Overflow sends a message to a tab on Stack Exchange. Result screenshot:

The method I ended up using for tab-to-tab communication between subdomains on the same domain was to pass information via javascript cookies. (I also tried using localStorage, but that didn't work between subdomains.)

Scenario: Tab A on SubDomain A will send messages to Tab B on SubDomain B:

Code looked like this:

function getCookie(cooVal) {
    var cname = cooVal+ '=';
    var ca = document.cookie.split(';');
    for (var i=0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(cname) === 0) {
            return c.substring(cname.length, c.length);
        }
    }
    return null;
} //END getcookie()

Tab B on subDomainB would RECEIVE messages from TabA on subDomain A:

function checkIncomingCIQ(){
    var acciq = getCookie('acciq');
    var jdlwc = getCookie('jdlwc');
}

TabA would SEND messages to Tab B like this:

document.cookie="acciq=5; domain=.example.com; path=/";
document.cookie="jdlwc=fubar; domain=.example.com; path=/";

For anyone wondering, yes, the subdomains can send messages to one another - it is not only a one-way communication. Just duplicate the same scenario in the other direction as well.

Of course, on both tabs the messaging system would be inside a javascript loop, like this:

(function foreverloop(i) {
    //Do all my stuff - send/receive the cookies, do stuff with the values, etc
    setTimeout(function() {
        foreverloop(++i);
    },2000);
}(0)); //END foreverloop

The TM headers on both tabs look like this:

// ==UserScript==
// @namespace    abcd.tops.example.com
// @match        *://abcd.tops.example.*/*
// @grant        none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// ==/UserScript==
and
// ==UserScript==
// @namespace    http://mysubdomain.example.com/callcenter/
// @match        *://*.example.com/callcenter/
// @grant        none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// ==/UserScript==

Apologies to all for the delay posting this solution. It took so long for the question to be re-opened after it was wrongly marked as duplicate that life went on . . .

发布评论

评论列表(0)

  1. 暂无评论