Giao tiếp giữa các tab hoặc cửa sổ


175

Tôi đã tìm kiếm cách giao tiếp giữa nhiều tab hoặc cửa sổ trong một trình duyệt (trên cùng một tên miền, không phải CORS) mà không để lại dấu vết. Có một số giải pháp:

  1. sử dụng đối tượng cửa sổ
  2. bài viết
  3. bánh quy
  4. lưu trữ cục bộ

Đầu tiên có lẽ là giải pháp tồi tệ nhất - bạn cần mở một cửa sổ từ cửa sổ hiện tại của mình và sau đó bạn chỉ có thể giao tiếp miễn là bạn giữ cho các cửa sổ mở. Nếu bạn tải lại trang trong bất kỳ cửa sổ nào, rất có thể bạn đã mất liên lạc.

Cách tiếp cận thứ hai, sử dụng postMessage, có thể cho phép giao tiếp nguồn gốc chéo, nhưng gặp vấn đề tương tự như cách tiếp cận đầu tiên. Bạn cần duy trì một đối tượng cửa sổ.

Cách thứ ba, sử dụng cookie, lưu trữ một số dữ liệu trong trình duyệt, có thể trông giống như gửi tin nhắn đến tất cả các cửa sổ trên cùng một tên miền, nhưng vấn đề là bạn không bao giờ có thể biết liệu tất cả các tab đã đọc "tin nhắn" hay chưa dọn dẹp. Bạn phải thực hiện một số loại thời gian chờ để đọc cookie định kỳ. Hơn nữa, bạn bị giới hạn bởi chiều dài cookie tối đa, đó là 4KB.

Giải pháp thứ tư, sử dụng localStorage, dường như khắc phục những hạn chế của cookie và thậm chí có thể lắng nghe bằng cách sử dụng các sự kiện. Làm thế nào để sử dụng nó được mô tả trong câu trả lời được chấp nhận.

Chỉnh sửa 2018: câu trả lời được chấp nhận vẫn hoạt động, nhưng có một giải pháp mới hơn cho các trình duyệt hiện đại, sử dụng BroadcastChannel. Xem câu trả lời khác để biết ví dụ đơn giản mô tả cách dễ dàng truyền tin nhắn giữa các tab bằng cách sử dụng BroadcastChannel.


4
Tại sao câu hỏi này được đóng lại là "quá rộng" khi khá nhiều câu hỏi tương tự đã được mở trong nhiều năm? Gửi tin nhắn đến tất cả các cửa sổ / tab đang mở bằng JavaScript , stackoverflow.com/questions/2236828/ , Bạn giao tiếp giữa 2 tab / cửa sổ trình duyệt như thế nào? và một vài điều nữa
Dan Dascalescu

Tôi đã tạo một thư viện qua localStorage và sessionStorage để quản lý lưu trữ dữ liệu phía máy khách. Bạn có thể thực hiện các công cụ như StorageManager.saveP PermanentData ('data', 'key'); hoặc StorageManager.saveSyncedSessionData ('dữ liệu', 'khóa'); dựa trên cách bạn muốn dữ liệu của bạn hoạt động. Nó thực sự đơn giản hóa quá trình. Bài viết đầy đủ tại đây: ebenmonney.com/blog/ từ
adentum


2
Tôi đã tạo thư viện sysend.js vài năm trước, trong phiên bản mới nhất, nó sử dụng BroadcastChannel. Bạn có thể kiểm tra thư viện bằng cách mở trang này hai lần jcubic.pl/sysend.php , nó cũng hoạt động với nguồn gốc khác nhau nếu bạn cung cấp proxy iframe.
jcubic

Tôi có coi tên miền phụ là cùng một nguồn gốc không? Ý tôi là, tôi có dưới ba tên miền, họ có giao tiếp qua kênh phát sóng không? alpha.firstdomain.com, beta.firstdomain.com, gama.firstdomain.com
Tejas Patel

Câu trả lời:


142

Chỉnh sửa 2018: Bạn có thể sử dụng BroadcastChannel tốt hơn cho mục đích này, xem các câu trả lời khác bên dưới. Tuy nhiên, nếu bạn vẫn thích sử dụng bộ lưu trữ cục bộ để liên lạc giữa các tab, hãy thực hiện theo cách này:

Để nhận được thông báo khi một tab gửi tin nhắn đến các tab khác, bạn chỉ cần liên kết với sự kiện 'lưu trữ'. Trong tất cả các tab, làm điều này:

$(window).on('storage', message_receive);

Hàm message_receivesẽ được gọi mỗi khi bạn đặt bất kỳ giá trị nào của localStorage trong bất kỳ tab nào khác. Trình lắng nghe sự kiện cũng chứa dữ liệu mới được đặt thành localStorage, do đó bạn thậm chí không cần phải phân tích cú pháp đối tượng localStorage. Điều này rất tiện lợi vì bạn có thể đặt lại giá trị ngay sau khi được đặt, để dọn sạch mọi dấu vết. Dưới đây là các chức năng để nhắn tin:

// use local storage for messaging. Set message in local storage and clear it right away
// This is a safe way how to communicate with other tabs while not leaving any traces
//
function message_broadcast(message)
{
    localStorage.setItem('message',JSON.stringify(message));
    localStorage.removeItem('message');
}


// receive message
//
function message_receive(ev)
{
    if (ev.originalEvent.key!='message') return; // ignore other keys
    var message=JSON.parse(ev.originalEvent.newValue);
    if (!message) return; // ignore empty msg or msg reset

    // here you act on messages.
    // you can send objects like { 'command': 'doit', 'data': 'abcd' }
    if (message.command == 'doit') alert(message.data);

    // etc.
}

Vì vậy, bây giờ khi các tab của bạn liên kết với sự kiện onst Storage và bạn đã thực hiện hai chức năng này, bạn có thể chỉ cần truyền một tin nhắn đến các tab khác đang gọi, ví dụ:

message_broadcast({'command':'reset'})

Hãy nhớ rằng việc gửi cùng một tin nhắn chính xác hai lần sẽ chỉ được truyền đi một lần, vì vậy nếu bạn cần lặp lại tin nhắn, hãy thêm một số định danh duy nhất cho chúng, như

message_broadcast({'command':'reset', 'uid': (new Date).getTime()+Math.random()})

Cũng nên nhớ rằng tab hiện tại phát tin nhắn không thực sự nhận được nó, chỉ các tab hoặc cửa sổ khác trên cùng một tên miền.

Bạn có thể hỏi điều gì xảy ra nếu người dùng tải một trang web khác hoặc đóng tab của mình ngay sau lệnh gọi setItem () trước lệnh removeItem (). Chà, từ thử nghiệm của riêng tôi, trình duyệt sẽ tạm dừng tải cho đến khi toàn bộ chức năng message_broadcast()kết thúc. Tôi đã thử nghiệm để đặt một khoảng thời gian rất dài cho chu kỳ () và nó vẫn đợi cho chu trình kết thúc trước khi đóng. Nếu người dùng giết tab chỉ ở giữa, thì trình duyệt sẽ không có đủ thời gian để lưu thư vào đĩa, do đó, cách tiếp cận này đối với tôi giống như cách an toàn để gửi tin nhắn mà không có bất kỳ dấu vết nào. Bình luận hoan nghênh.


1
bạn có thể bỏ qua sự kiện xóa trước khi gọi JSON.parse () không?
dandavis

1
Hãy ghi nhớ giới hạn dữ liệu sự kiện, bao gồm dữ liệu localStorage có sẵn. có thể tốt hơn / an toàn hơn khi sử dụng các sự kiện lưu trữ chỉ để nhắn tin thay vì vận chuyển. giống như khi bạn nhận được một tấm bưu thiếp bảo bạn lấy một gói hàng tại bưu điện ... cũng vậy, bộ lưu trữ cục bộ đi vào ổ cứng, vì vậy nó có thể để lại bộ nhớ đệm vô tình và ảnh hưởng đến nhật ký, đó là một lý do khác để xem xét một cơ chế vận chuyển khác cho dữ liệu thực tế.
dandavis

1
tôi đã làm một cái gì đó liên quan một lúc trước: danml.com/js/localst Storageevents.js , rằng người ta có một cơ sở trình giả lập sự kiện và "tiếng vang cục bộ" để bạn có thể sử dụng EE cho mọi thứ ở mọi nơi.
dandavis

7
Safari không hỗ trợ BroadcastChannel - caniuse.com/#feat=broadcastchannel
Srikanth

1
Chỉ cần ngẩng cao đầu, bạn cũng có thể sử dụng các nhân viên được chia sẻ: developer.mozilla.org/en-US/docs/Web/API/SharedWorker (có hỗ trợ tốt hơn trên các trình duyệt)
Seblor

115

Có một API hiện đại dành riêng cho mục đích này - Kênh phát sóng

Nó dễ dàng như:

var bc = new BroadcastChannel('test_channel');

bc.postMessage('This is a test message.'); /* send */

bc.onmessage = function (ev) { console.log(ev); } /* receive */

Không cần tin nhắn chỉ là một DOMString, bất kỳ loại đối tượng nào cũng có thể được gửi.

Có lẽ, ngoài tính sạch của API, đó là lợi ích chính của API này - không có chuỗi đối tượng.

Hiện chỉ được hỗ trợ trong Chrome và Firefox, nhưng bạn có thể tìm thấy một polyfill sử dụng localStorage.


3
Đợi đã, làm thế nào để bạn biết tin nhắn đến từ đâu? Nó có bỏ qua các tin nhắn đến từ cùng một tab không?
AturSams

2
@zehelvion: Người gửi sẽ không nhận được nó, ví dụ như tổng quan tốt đẹp này . Bên cạnh đó, bạn có thể đưa vào tin nhắn bất cứ điều gì bạn muốn, bao gồm. một số ID của người gửi, nếu cần.
Sz.

6
Có một dự án tuyệt vời kết thúc tính năng này trong thư viện trình duyệt chéo tại đây: github.com/pubkey/broadcast-channel
james2doyle

Đã có bất kỳ tín hiệu công khai nào từ Safari về việc có hỗ trợ API này hay không trong trình duyệt đó?
Casey

@AturSams Bạn kiểm tra MessageEvent.origin, MessageEvent.source hoặc MessageEvent.ports là những gì bạn muốn. Như mọi khi tài liệu là nơi tốt nhất để bắt đầu: developer.mozilla.org/en-US/docs/Web/API/MessageEvent
Stefan Mihai Stanescu

40

Đối với những người tìm kiếm giải pháp không dựa trên jQuery, đây là phiên bản JavaScript đơn giản của giải pháp được cung cấp bởi Thomas M:

window.addEventListener("storage", message_receive);

function message_broadcast(message) {
    localStorage.setItem('message',JSON.stringify(message));
}

function message_receive(ev) {
    if (ev.key == 'message') {
        var message=JSON.parse(ev.newValue);
    }
}

1
Tại sao bạn bỏ qua cuộc gọi removeItem?
Tomas M

2
Tôi chỉ tập trung vào sự khác biệt giữa jQuery và JavaScript.
Nacho Coloma

tôi luôn luôn sử dụng lib vì tính năng polyfill và khả năng không được hỗ trợ!
Amin Rahimi

20

Checkout AcrossTabs - Giao tiếp dễ dàng giữa các tab trình duyệt có nguồn gốc chéo. Nó sử dụng kết hợp API postMessagesessionStorage để giúp giao tiếp dễ dàng và đáng tin cậy hơn nhiều.


Có nhiều cách tiếp cận khác nhau và mỗi cách đều có ưu điểm và nhược điểm riêng. Hãy thảo luận về mỗi:

  1. Lưu trữ cục bộ

    Ưu điểm :

    1. Lưu trữ web có thể được xem một cách đơn giản như một sự cải tiến về cookie, cung cấp dung lượng lưu trữ lớn hơn nhiều. Nếu bạn nhìn vào mã nguồn Mozilla chúng ta có thể thấy rằng 5120KB ( 5MB bằng với 2,5 triệu ký tự trên Chrome) là kích thước lưu trữ mặc định cho toàn bộ miền. Điều này cung cấp cho bạn nhiều không gian để làm việc hơn so với cookie 4KB thông thường.
    2. Dữ liệu không được gửi trở lại máy chủ cho mọi yêu cầu HTTP (HTML, hình ảnh, JavaScript, CSS, v.v.) - giảm lượng lưu lượng giữa máy khách và máy chủ.
    3. Dữ liệu được lưu trữ trong localStorage vẫn tồn tại cho đến khi bị xóa rõ ràng. Những thay đổi được thực hiện được lưu và có sẵn cho tất cả các lượt truy cập hiện tại và tương lai vào trang web.

    Nhược điểm :

    1. Nó hoạt động trên chính sách cùng nguồn gốc . Vì vậy, dữ liệu được lưu trữ sẽ chỉ có thể có sẵn trên cùng một nguồn gốc.
  2. Bánh quy

    Ưu điểm:

    1. So với những người khác, không có gì AFAIK.

    Nhược điểm:

    1. Giới hạn 4K dành cho toàn bộ cookie, bao gồm tên, giá trị, ngày hết hạn, v.v. Để hỗ trợ hầu hết các trình duyệt, hãy giữ tên dưới 4000 byte và kích thước cookie chung dưới 4093 byte.
    2. Dữ liệu được gửi trở lại máy chủ cho mọi yêu cầu HTTP (HTML, hình ảnh, JavaScript, CSS, v.v.) - tăng lượng lưu lượng giữa máy khách và máy chủ.

      Thông thường, những điều sau đây được cho phép:

      • Tổng cộng 300 cookie
      • 4096 byte mỗi cookie
      • 20 cookie cho mỗi tên miền
      • 81920 byte cho mỗi tên miền (Cho 20 cookie có kích thước tối đa 4096 = 81920 byte.)
  3. phiênStorage

    Ưu điểm:

    1. Nó tương tự như localStorage.
    2. Thay đổi chỉ khả dụng trên mỗi cửa sổ (hoặc tab trong các trình duyệt như Chrome và Firefox). Các thay đổi được thực hiện được lưu và có sẵn cho trang hiện tại, cũng như các lượt truy cập trong tương lai vào trang web trên cùng một cửa sổ. Khi cửa sổ được đóng lại, bộ nhớ sẽ bị xóa

    Nhược điểm:

    1. Dữ liệu chỉ có sẵn bên trong cửa sổ / tab được đặt.
    2. Dữ liệu không liên tục, tức là nó sẽ bị mất khi cửa sổ / tab được đóng lại.
    3. Giống như localStorage, tt hoạt động trên chính sách cùng nguồn gốc . Vì vậy, dữ liệu được lưu trữ sẽ chỉ có thể có sẵn trên cùng một nguồn gốc.
  4. PostMessage

    Ưu điểm:

    1. An toàn cho phép giao tiếp nguồn gốc chéo .
    2. Là một điểm dữ liệu, việc triển khai WebKit (được Safari và Chrome sử dụng) hiện không thực thi bất kỳ giới hạn nào (ngoài các giới hạn được áp đặt bằng cách hết bộ nhớ).

    Nhược điểm:

    1. Cần mở một cửa sổ từ cửa sổ hiện tại và sau đó chỉ có thể giao tiếp miễn là bạn giữ cho các cửa sổ mở.
    2. Mối quan tâm về bảo mật - Gửi chuỗi qua postMessage là bạn sẽ chọn các sự kiện postMessage khác được xuất bản bởi các plugin JavaScript khác, vì vậy hãy đảm bảo thực hiệntargetOriginvà kiểm tra độ tỉnh táo cho dữ liệu được truyền đến người nghe tin nhắn.
  5. Một sự kết hợp của PostMessage + SessionStorage

    Sử dụng postMessage để liên lạc giữa nhiều tab và đồng thời sử dụng sessionStorage trong tất cả các tab / cửa sổ mới mở để duy trì dữ liệu được truyền. Dữ liệu sẽ được duy trì miễn là các tab / cửa sổ vẫn mở. Vì vậy, ngay cả khi tab / cửa sổ mở bị đóng, các tab / cửa sổ đã mở sẽ có toàn bộ dữ liệu ngay cả sau khi được làm mới.

Tôi đã viết một thư viện JavaScript cho cái này, tên là AcrossTabs , sử dụng API postMessage để giao tiếp giữa các tab / cửa sổ có nguồn gốc chéo và sessionStorage để duy trì danh tính các tab / cửa sổ đã mở miễn là chúng tồn tại.


Sử dụng AcrossTabs, có thể mở một trang web khác trong tab khác và lấy dữ liệu từ trang đó sang tab cha không? Tôi sẽ có chi tiết xác thực cho trang web khác.
Madhur Bhaiya

1
Có, bạn có thể @MadhurBhaiya
softvar

Ưu điểm lớn nhất của cookie là cho phép xuất phát chéo cùng một tên miền, thường hữu ích khi bạn có một bộ nguồn gốc như "a.target.com", "b.target.com", v.v.
StarPinkER

7

Một phương pháp khác mà mọi người nên xem xét sử dụng là Công nhân được chia sẻ. Tôi biết đó là một khái niệm tiên tiến, nhưng bạn có thể tạo chuyển tiếp trên Công nhân được chia sẻ nhanh hơn so với lưu trữ cục bộ và không yêu cầu mối quan hệ giữa cửa sổ cha / con, miễn là bạn có cùng nguồn gốc.

Xem câu trả lời của tôi ở đây cho một số cuộc thảo luận tôi đã thực hiện về điều này.


7

Có một thành phần nguồn mở nhỏ để đồng bộ / giao tiếp giữa các tab / cửa sổ có cùng nguồn gốc (từ chối trách nhiệm - Tôi là một trong những người đóng góp!) Dựa trên localStorage.

TabUtils.BroadcastMessageToAllTabs("eventName", eventDataString);

TabUtils.OnBroadcastMessage("eventName", function (eventDataString) {
    DoSomething();
});

TabUtils.CallOnce("lockname", function () {
    alert("I run only once across multiple tabs");
});

https://github.com/jitbit/TabUtils

PS Tôi đã tự do giới thiệu nó ở đây vì hầu hết các thành phần "khóa / mutex / sync" đều thất bại trên các kết nối websocket khi các sự kiện xảy ra gần như đồng thời


6

Tôi đã tạo một thư viện sysend.js , nó rất nhỏ, bạn có thể kiểm tra mã nguồn của nó. Thư viện không có bất kỳ sự phụ thuộc bên ngoài.

Bạn có thể sử dụng nó để liên lạc giữa các tab / cửa sổ trong cùng một trình duyệt và tên miền. Thư viện sử dụng BroadcastChannel, nếu được hỗ trợ hoặc lưu trữ sự kiện từ localStorage.

API rất đơn giản:

sysend.on('foo', function(message) {
    console.log(message);
});
sysend.broadcast('foo', {message: 'Hello'});
sysend.broadcast('foo', "hello");
sysend.broadcast('foo'); // empty notification

khi trình hỗ trợ của bạn hỗ trợ BroadcastChannel, nó đã gửi đối tượng theo nghĩa đen (nhưng thực tế nó được tự động nối tiếp bởi trình duyệt) và nếu không, nó sẽ được tuần tự hóa thành JSON trước và được giải tuần tự hóa ở đầu bên kia.

Phiên bản gần đây cũng có API trợ giúp để tạo proxy cho giao tiếp Tên miền chéo. (nó yêu cầu tệp html đơn trên miền đích).

Đây là bản demo .

CHỈNH SỬA :

Phiên bản mới cũng hỗ trợ liên lạc giữa các miền , nếu bạn bao gồm proxy.htmltệp đặc biệt trên miền đích và proxychức năng gọi từ miền nguồn:

sysend.proxy('https://target.com');

(proxy.html tệp html rất đơn giản, chỉ có một thẻ script với thư viện).

Nếu bạn muốn giao tiếp hai chiều, bạn cần thực hiện tương tự trên target.commiền.

LƯU Ý : Nếu bạn sẽ triển khai chức năng tương tự bằng localStorage, có vấn đề trong IE. Sự kiện lưu trữ được gửi đến cùng một cửa sổ, điều này đã kích hoạt sự kiện này và đối với các trình duyệt khác, nó chỉ được gọi cho các tab / cửa sổ khác.


2
Chỉ muốn cho bạn một số kudo cho việc này. Một bổ sung nhỏ ngọt ngào rất đơn giản và cho phép tôi giao tiếp giữa các tab của mình để giữ cho phần mềm cảnh báo đăng xuất không làm mọi người thất vọng. Công việc tốt. Tôi rất khuyến khích điều này nếu bạn muốn một giải pháp nhắn tin đơn giản để sử dụng.
BrownPony

4

Tôi đã tạo một mô-đun hoạt động tương đương với Broadcastchannel chính thức nhưng có các dự phòng dựa trên bộ lưu trữ cục bộ, indexeddb và unix-socket. Điều này đảm bảo nó luôn hoạt động ngay cả với Webworkers hoặc NodeJS. Xem pubkey: BroadcastChannel


1

Tôi đã viết một bài báo về vấn đề này trên blog của tôi: http://www.ebenmonney.com/blog/how-to-implement-remember-me-functionality-using-token-based-authentication-and-localstorage-in-a- ứng dụng web .

Sử dụng một thư viện tôi đã tạo, storageManagerbạn có thể đạt được điều này như sau:

storageManager.savePermanentData('data', 'key'): //saves permanent data
storageManager.saveSyncedSessionData('data', 'key'); //saves session data to all opened tabs
storageManager.saveSessionData('data', 'key'); //saves session data to current tab only
storageManager.getData('key'); //retrieves data

Ngoài ra còn có các phương pháp thuận tiện khác để xử lý các tình huống khác


0

Đây là một storagephần phát triển của câu trả lời của Tomas M cho Chrome. Chúng ta phải thêm người nghe

window.addEventListener("storage", (e)=> { console.log(e) } );

Tải / lưu mục trong bộ lưu trữ không chạy trong sự kiện này - chúng tôi PHẢI kích hoạt nó bằng tay

window.dispatchEvent( new Event('storage') ); // THIS IS IMPORTANT ON CHROME

và bây giờ, tất cả các tab đang mở sẽ nhận được sự kiện

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.