【问题标题】:TamperMonkey - message between scripts on different subdomainsTamperMonkey - 不同子域上的脚本之间的消息
【发布时间】:2017-04-27 22:49:03
【问题描述】:

我有两个脚本。每个都在我们公司的不同子域上运行"Example.com".

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

同一个域,不同的子域。

house.example.com 上出现特定元素时,我需要向bob.fred.example.com 上运行的脚本发送一条消息

由于 Google 扩展程序可以在扩展程序之间交换消息,因此 TamperMonkey 必须有一种方法可以在同一扩展程序内、脚本之间交换消息 - 特别是如果它们在同一个二级域上运行。

谁能指出我正确的方向?一两个例子就值金子了。


更新:虽然 Gothdo 引用了 Javascript communication between browser tabs/windows 作为对这个问题的回答,但他没有考虑到所涉及的跨域策略。 该引用问题中的任何答案都没有为跨域浏览器标签通信提供明确的答案,这是这个问题的重点。我现在已经研究并解决了这个问题问题,从许多 SO 和非 SO 来源获得想法。如果这个问题被重新打开,我会发布我的解决方案。

【问题讨论】:

  • 这对我来说不是重复的。 OP 询问 TamperMonkey 中的消息传递,类似于 Chrome extensions' messaging
  • @nicovank 在 Tampermonkey 中也是如此。 Tampermonkey 脚本没有 Chrome 扩展这样的权限;它们与常规脚本非常相似。
  • @Gothdo 不过,我认为合适的做法是提供一个答案,说没有这样的 API,但是可以使用常规的网络消息传递,并提供一个链接。这不是重复的,即使答案是相似的。 -- 特别是因为如果脚本在同一页面上运行,还可以通过演示如何使用GM_setValue 来增强答案。
  • @gibberish 这个论坛帖子展示了如何使用 GM_setValue 和 GM_getValue 在选项卡之间发送消息以实现选项卡级锁定。 forum.tampermonkey.net/viewtopic.php?f=21&t=1000#p3655
  • @Gothdo,你上面的断言是错误的; Tampermonkey 脚本可以做一些常规脚本做不到的事情,并且该副本不适用于 OP 的场景。虽然这个问题可能是一些 Greasemonkey 问题的重复(该主题肯定已经在 GM/TM 场景中涵盖),但我目前没有时间搜索更多...

标签: javascript jquery greasemonkey tampermonkey


【解决方案1】:

您可以使用GM_getValueGM_setValueGM_addValueChangeListener实现跨表用户脚本通信。

在您的用户脚本标题中添加以下行。

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

以下几行粗略的代码将简化交叉表用户脚本通信。

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));
}

因此,您只需执行以下操作即可发送和接收消息。

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

注意如果发送的消息与之前相同,发送消息可能不会触发您的回调。这是因为 GM_addValueChangeListener 没有触发,因为值没有改变,即即使调用了 GM_setValue,值也与以前相同。

【讨论】:

  • 对该脚本的快速测试向我证实,使用这些方法进行幕后通信确实可以跨选项卡/窗口甚至跨不同顶级域工作当您使用适当的元标记时,例如 //@match https://*/*
【解决方案2】:

我最终用于同一域上的子域之间的选项卡到选项卡通信的方法是通过 javascript cookie 传递信息。 (我也尝试过使用 localStorage,但这在子域之间不起作用。)

场景:子域 A 上的选项卡 A 将向子域 B 上的选项卡 B 发送消息:

代码如下所示:

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()

subDomainB 上的 Tab B 会接收来自 subDomain A 上 TabA 的消息:

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

TabA 会像这样发送消息到 Tab B:

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

对于任何想知道的人,是的,子域可以相互发送消息 - 这不仅仅是一种单向通信。只需在另一个方向上复制相同的场景即可。

当然,在两个选项卡上,消息传递系统都在一个 javascript 循环中,如下所示:

(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

两个选项卡上的 TM 标题如下所示:

// ==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==

对延迟发布此解决方案的所有人表示歉意。这个问题被错误地标记为重复后,花了很长时间才重新打开,生活还在继续。 . .

【讨论】:

    【解决方案3】:

    使用@grant 启用沙盒,这有时会在尝试与 Greasemonkey 上的复杂页面对象交互时导致困难。

    如果您想要使用@grant 启用沙箱,另一种选择是让用户脚本为另一个域创建一个iframe,然后向它发布消息。在另一个域的 iframe 中,侦听消息。收到消息后,使用BroadcastChannel 将消息发送到该其他域上的所有其他选项卡,并且运行用户脚本的其他选项卡可以打开相同的 BroadcastChannel 并侦听消息。

    例如,要在stackoverflow.com 上创建一个用户脚本,它可以向在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);
    }
    

    这会导致:

    如果您想要双向通信,而不仅仅是单向通信,请让两个父页面都为单个目标域(例如,example.com)创建一个子 iframe。要与其他选项卡通信,请向子 iframe 发布消息。让子 iframe 侦听消息,并在看到时发布 BroadcastChannel 消息以与所有其他 iframe 进行通信。当 iframe 接收到 BroadcastChannel 消息时,使用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);
      }
    }
    

    在上面的代码中,Stack Overflow 上的选项卡向 Stack Exchange 上的选项卡发送消息。结果截图:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-10-06
      • 2013-11-30
      • 1970-01-01
      • 1970-01-01
      • 2011-07-05
      • 1970-01-01
      • 2018-09-25
      相关资源
      最近更新 更多