Giter Site home page Giter Site logo

jcubic / sysend.js Goto Github PK

View Code? Open in Web Editor NEW
1.1K 22.0 70.0 12.46 MB

Web application synchronization between different tabs

License: MIT License

HTML 26.06% JavaScript 72.22% TypeScript 1.73%
callback notifications events browser tabs javascript proxy messages pubsub communication

sysend.js's Introduction

Sysend.js logo

npm bower downloads jsdelivr

sysend.js is a small library that allows to send messages between pages that are open in the same browser. It also supports Cross-Domain communication (Cross-Origin). The library doesn't have any dependencies and uses the HTML5 LocalStorage API or BroadcastChannel API. If your browser don't support BroadcastChannel (see Can I Use) then you can send any object that can be serialized to JSON. With BroadcastChannel you can send any object (it will not be serialized to string but the values are limited to the ones that can be copied by the structured cloning algorithm). You can also send empty notifications.

Tested on:

GNU/Linux: in Chromium 34, Firefox 29, Opera 12.16 (64bit)
Windows 10 64bit: in IE11 and Edge 38, Chrome 56, Firefox 51
MacOS X El Captain: Safari 9, Chrome 56, Firefox 51

Note about Safari 7+ and Cross-Domain communication

All cross-domain communication is disabled by default with Safari 7+. Because of a feature that blocks 3rd party tracking for iframe, and any iframe used for cross-domain communication runs in a sandboxed environment. That's why this library like any other solution for cross-domain communication, don't work on Safari.

Note about Chrome 115+ and different domains

Since version 115 Google Chrome introduced Third-party storage partitioning. Because of this feature, Cross-domain communication only works on subdomains. There will probably be a way to share the context using some kind of permission API, that in the future may also land in Safari (hopefully). More information about this can be found in #54. Information about the API can also be found in Google Chrome documentation: Storage Partitioning

There is a new API: Storage Access API. It's available when you register for the Origin Trial.

You can register two and more domains, you will have a token that you need to add to the HTML files (in the head tag):

<meta http-equiv="origin-trial" content="<TOKEN>" />

You can also use the HTTP header:

Origin-Trial: <TOKEN>

Right now the API only works with localStorage fallback (when inside iframes).

Installation

Include sysend.js file in your html, you can grab the file from npm:

npm install sysend

or bower

bower install sysend

you can also get it from unpkg.com CDN:

https://unpkg.com/sysend

or jsDelivr:

https://cdn.jsdelivr.net/npm/sysend

jsDelivr will minify the file. From my testing it's faster than unpkg.com.

Usage

window.onload = function() {
    sysend.on('foo', function(data) {
        console.log(data.message);
    });
    var input = document.getElementsByTagName('input')[0];
    document.getElementsByTagName('button')[0].onclick = function() {
        sysend.broadcast('foo', { message: input.value });
    };
};

Windows/tabs tracking

Tracking is high level API build on top of on() and broadcast(), that allows to manage windows/tabs. You can sent message directly to other windows/tabs:

sysend.track('message', ({data, origin}) => {
    console.log(`${origin} send message "${data}"`);
});
sysend.post('<ID>', 'Hello other window/tab');

and listen to events like:

sysend.track('open', (data) => {
    console.log(`${data.id} window/tab just opened`);
});

Other tracking events includes: close/primary/secondary executed when window/tab is closed or become primary or secondary. Track method was added in version 1.6.0. Another required event is ready (added in 1.10.0) that should be used when you want to get list of windows/tabs:

sysend.track('ready', () => {
    sysend.list().then(tabs => {
        console.log(tabs);
    });
});

with list() method and open/close events you can implement dynamic list of windows/tab. That will change when new window/tab is open or close.

let list = [];

sysend.track('open', data => {
    if (data.id !== sysend.id) {
        list.push(data);
        populate_list(list);
    }
});

sysend.track('close', data => {
    list = list.filter(tab => data.id !== tab.id);
    populate_list(list);
});

sysend.track('ready', () => {
    sysend.list().then(tabs => {
        list = tabs;
        populate_list(list);
    });
});

function populate_list() {
    select.innerHTML = '';
    list.forEach(tab => {
        const option = document.createElement('option');
        option.value = tab.id;
        option.innerText = tab.id;
        select.appendChild(option);
    });
}

In version 1.16.0 this code was abstracted into:

sysend.track('update', (list) => {
   populate_list(list);
});

This can be simplified with point free style:

sysend.track('update', populate_list);

RPC mechanism

In version 1.15.0 new API was added called rpc() (build on top of tracking mechanism) that allow to use RPC (Remote Procedure Call) between open windows/tabs.

const rpc = sysend.rpc({
    get_message() {
        return document.querySelector('input').value;
    }
});

button.addEventListener('click', () => {
    rpc.get_message('<ID>').then(message => {
        console.log(`Message from other tab is "${message}"`);
    }).catch(e => {
        console.log(`get_message (ERROR) ${e.message}`);
    });
});

Cross-Domain communication

If you want to add support for Cross-Domain communication, you need to call proxy method with url on target domain that have proxy.html file.

sysend.proxy('https://jcubic.pl');
sysend.proxy('https://terminal.jcubic.pl');

on Firefox you need to add CORS for the proxy.html that will be loaded into iframe (see Cross-Domain LocalStorage).

Serialization

if you want to send custom data you can use serializer (new in 1.4.0) this API was created for localStorage that needs serialization.

Example serializer can be json-dry:

sysend.serializer(function(data) {
    return Dry.stringify(data);
}, function(string) {
    return Dry.parse(string);
});

or JSON5:

sysend.serializer(function(data) {
    return JSON5.stringify(string);
}, function(string) {
    return JSON5.parse(string);
});

Security protection

Since version 1.10.0 as a security mesure Cross-Domain communication has been limited to only those domains that are allowed. To allow domain to listen to sysend communication you need to specify channel inside iframe. You need add your origins to the sysend.channel() function (origin is combination of protocol domain and optional port).

Demos

Screen capture of Operating System Windows dragging and moving around animation

Screen capture of multiple browser windows and interactive circle that follow the mouse

API

sysend object:

function description arguments Version
on(name, callback) add event handler for specified name name - string - The name of the event
callback - function (object, name) => void
1.0.0
off(name [, callback]) remove event handler for given name, if callback is not specified it will remove all callbacks for given name name - string - The name of the event
callback - optional function (object, name) => void
1.0.0
broadcast(name [, object]) send any object and fire all events with specified name (in different pages that register callback using on). You can also just send notification without an object name - string - The name of the event
object - optional any data
1.0.0
proxy(<urls>) create iframe proxy for different domain, the target domain/URL should have proxy.html
file. If url domain is the same as page domain, it's ignored. So you can put both proxy calls on both
url - string 1.3.0
serializer(to_string, from_string) add serializer and deserializer functions both arguments are functions (data: any) => string 1.4.0
emit(name, [, object]) same as broadcast() but also invoke the even on same page name - string - The name of the event
object - optional any data
1.5.0
post(<window_id>, [, object]) send any data to other window window_id - string of the target window (use 'primary' to send to primary window)
object - any data
1.6.0 / 'primary' target 1.14.0
list() returns a Promise of objects {id:<UUID>, primary} for other windows, you can use those to send a message with post() NA 1.6.0
track(event, callback) track inter window communication events event - any of the strings: "open", "close", "primary",
"secondary", "message", "update"
callback - different function depend on the event:
* "message" - {data, origin} - where data is anything the post() sends, and origin is id of the sender.
* "open" - {count, primary, id} when new window/tab is opened
* "close" - {count, primary, id, self} when window/tab is closed
* "primary" and "secondary" function has no arguments and is called when window/tab become secondary or primary.
* "ready" - event when tracking is ready.
1.6.0 except ready - 1.10.0 and update - 1.16.0
untrack(event [,callback]) remove single event listener all listeners for a given event event - any of the strings 'open', 'close', 'primary', 'secondary', 'message', or 'update'. 1.6.0
isPrimary() function returns true if window is primary (first open or last that remain) NA 1.6.0
channel() function restrict cross domain communication to only allowed domains. You need to call this function on proxy iframe to limit number of domains (origins) that can listen and send events. any number of origins (e.g. 'http://localhost:8080' or 'https://jcubic.github.io') you can also use valid URL. 1.10.0
useLocalStorage([toggle]) Function set or toggle localStorage mode. argument is optional and can be true or false. 1.14.0
rpc(object): Promise<fn(id, ...args): Promise> Function create RPC async functions which accept first additional argument that is ID of window/tab that it should sent request to. The other window/tab call the function and return value resolve original promise. The function accept an object with methods and return a Promise that resolve to object with same methods but async. 1.15.0

To see details of using the API, see demo.html source code or TypeScript definition file.

Story

The story of this library came from my question on StackOverflow from 2014: Sending notifications between instances of the page in the same browser, with hint from user called Niet the Dark Absol, I was able to create a PoC of the solution using localStorage. I quickly created a library from my solution. I've also explained how to have Cross-Domain LocalStorage. The blog post have steady number of visitors (actually it's most viewed post on that blog).

And the name of the library is just random word "sy" and "send" suffix. But it can be an backronym for Synchronizing Send as in synchronizing application between browser tabs.

Articles

Press

The library was featured in:

License

Copyright (C) 2014 Jakub T. Jankiewicz
Released under the MIT license

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

sysend.js's People

Contributors

aligent-phil avatar ambujsahu81 avatar jcubic avatar marclaporte avatar s-gerber avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sysend.js's Issues

Browser blocks script loading on demo page

Go to https://jcubic.pl/sysend.php and open console. You will see the next errors:

sysend.php:1 Mixed Content: The page at 'https://jcubic.pl/sysend.php' was loaded over HTTPS, but requested an insecure script 'http://jcubic.pl/sysend.js'. This request has been blocked; the content must be served over HTTPS.

Mixed Content: The page at 'https://jcubic.pl/sysend.php' was loaded over HTTPS, but requested an insecure resource 'http://terminal.jcubic.pl/iframe.html'. This request has been blocked; the content must be served over HTTPS.

Use localstorage option in sysend

Hi, great library.

It would be nice if we can tell sysend to use localStorage instead of BroadcastChannel throgh options.

The use case is offline web application that run from filesystem.

Thanks

Does sysend.js implement the BroadcastChannel API?

I wish I could use sysend.js to make the BroadcastChannel API work across domains, like this:

var bc = new sysend.BroadcastChannel('test_channel');

bc.postMessage('This is a test message.'); /* post a message to every domain */

bc.onmessage = function (ev) { console.log(ev); } /* receive a message from any domain */

Does sysend.js have such a feature?

Unexpected arrow function

Hello! The source code includes unexpected arrow function. As long as node_modules dir is not transpiled by default it causes runtime error in browsers that don't support arrow functions such as IE11. It would be nice to replace it with regular function

iframe.addEventListener('load', () => {
    resolve();
}, true);

Thanks in advance!

Cross-domain communication broken in latest Chrome (115+)

This is mostly a thread to share my experiences with the issues with my cross-domain setup using sysend.js, which stopped working several months ago, but I only now was able to dig in to it and figure out what was happening.

When I tested your built-in cross-domain demo (https://jcubic.pl/sysend-demo/) it still worked, so at first, I thought the issue was due to something with my setup (I only load one iframe since that seemed to be enough for me to get full functionality, see #29 & #37). So I first updated to the latest version of sysend, and when I still had issues, next changed my setup to be configured with dual iframes, but ultimately had the same issues. Lastly, I tried toggling the enforcement of using the localStorage feature via sysend.useLocalStorage(), but no differences.

Switching to evaluating the roots of the problem, broadcastChannel.postMessage would never propagate from an iframe window to a tab with the same domain when that iframe was rendered under a page with a different domain. The same holds true for any localStorage events (all of the localStorage data was completely different and isolated, even though the tab and iframe were from the same origin).

In my case my two sites are different domain origins entirely (a.subdomain1.com vs b.subdomain2.com), where as your cross-domain demo are both under the same domain, just one is a subdomain (jcubic.pl vs terminal.jcubic.pl) which is why I suspect the demo still works, although I couldn't find any documentation anywhere for that exception. I did try the setup for my domains with different subdomains but under same main domain, and it did work, but unfortunately I can't make that change in production since it'll break a lot of users access (on both sites) to various resources (too many hardcoded URL's in emails).

Ultimately, the issue is due to a new feature coming down the pipe in browsers, storage/state propagation. I investigated Chrome specifically since that's what we work with internally, so the situation for Firefox or Edge could be different. For Chrome, this feature started in Chrome Beta 113, but was officially available in the production release of Chrome 115 (I'm on 117 atm), which was released back in July '23 I think, which kind of lines up with when I suspect this issue started to occur for me. FYI, I believe that they are rolling out this feature to users slowly, so not all users on latest Chrome version may be affected by it.

For Chrome, there is a way to register in an Origin Trial and submit a form which essentially will generate you a code that you then embed in your site (as a meta tag, or a http header in some cases). You have to provide it your top-level site domain and specify some other settings (i.e. subdomain wildcard, third-party script injection, usage count, etc). Ultimately, this allowed me to restore this functionality to my site in the latest chrome browser version without needing all users to disable a chrome feature flag or anything like that, HOWEVER, this code/trial will expire (supposedly) by Sept 2024 (link), which if anything, at least gives me a year to figure out an alternative (or actually migrate to using same domain origin). I'm hoping that they enable some sort of way to enable a whitelist of allowed domains to have unpartioned cross-origin access to these resources before the trial expiration date.

If anyone has any other suggestions or knows more about this, I'm all ears. But until then, I hope this helps someone else.

Other useful links:

https://developer.chrome.com/en/docs/web-platform/origin-trials/#take-part-in-an-origin-trial
https://developer.chrome.com/docs/web-platform/origin-trial-troubleshooting/
https://developer.chrome.com/docs/web-platform/third-party-origin-trials/
https://chromeenterprise.google/policies/atomic-groups/#ThirdPartyStoragePartitioningSettings

Credit to @yangon99's comment which was my first hint of the source of the issue being related to the new storage partitioning feature.

Cross-Domain in React

Where do I need to add the proxy.html in a React Project. Or can I just add the script tag to the index.html file?

Fix on IE causes other browsers to ignore sysend events on other tabs on the same page

So when you open at least two tabs with the same url (say localhost:8080/en-us) and broadcasted an event on one of the tabs nothing happens. But if you put the other tab on another page (say localhost:8080/ja-jp) it works.

https://github.com/jcubic/sysend.js/blob/master/sysend.js#L54

This is working (different urls).
sysend-1

This is not (same urls).
sysend-2

EDIT: I created a pull request that fixes this issue but I still can't figure how to make IE not fire the event on the tab where that event was made when they have the same URL. Maybe this is an an IE bug. You may have better ideas.

Can we Inject javascript in new tab ?

Hey Awesome library,
i want to open a new url and want to inject some JS into it ( can’t use iframe in my case )
Plan is to use this library for communication
But first i have to inject js, Atleast this library into that website, is it possible ?
Because i don't have access to that website
So options are, either i manually inject js or i use extension to do it automatically
Do we have another option which don't requires manual work and can be handled programatically ?

Release v1.3.5 on npm

Hi @jcubic,

Could you please publish the version 1.3.5 on npm? For now, it's still 1.3.4. The version 1.3.5 is crucial because it fixes broken JSON.

I am looking forward to your release. Thank you.

Response and Timeout?

Hi,

I'm really pleased that I found sysend.js - it's helping to solve a problem of communication between multiple tabs in the browser.

But can you suggest a way to use sysend.js so that the broadcaster of a request can wait for a response from a receiver, or timeout if no response is received?

Thanks for any suggestions.

Kevin

Feature: primary window selection, last exit event

One thing that would be nice to see as an additional set of features would be window/tab detection as a primary selection.

.track("primary", () => {
  // 
  // create persistent message connection to server ...
})
.track("primaryClose", (last /* boolean, true if this is the last connected window/tab */) => { 
  // window close event
  if (last) {
   // let server know user is going offline - no more connection(s) in this session.
  }
  // cleanup connection
})

Possible security issue using postMessage with "*" as target

Hi,
I've been playing around with your library and was curious about the code. I have noticed that when you use postMessage for cross domain comunication you specify "*" as target (lines 406 and 516). This might be a securiy issue because from what I understand malicious sites could receive the message

From

https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#security_concerns

Always specify an exact target origin, not *, when you use postMessage to send data to other windows. A malicious site can change the location of the window without your knowledge, and therefore it can intercept the data sent using postMessage.

Since the domain where to send the event is known when postMessage is called, wouldn't it be better to just specify it as postMessage parameter?

proxy parameter in typescript not allows domains population

Hi,
I'm using the version 1.12.1 with typescript and the function "proxy" doesn't work as expected or maybe I'm using it in the wrong way but I think that problem is due to the definition of type in the interface Sysend.d.ts

proxy(args?: string[]): void;

that have an array of string as param but in the library the function proxy need a string as url

`proxy: function() {

        [].slice.call(arguments).forEach(function(url) {
            if (typeof url === 'string' && host(url) !== window.location.host) {
                domains = domains || [];
                domains.push(origin(url));

....
}`

so if I use proxy in this way
sysend.proxy(["url1","url2"])
the
[].slice.call(arguments)
give me an array as foreach element and the if condition is never true.

It's no possible to pass other params to the proxy function to have the urls in arguments because of typescript definition so the only workarond that I have found is to use

sysend.proxy([]).bind(null,"url1","url2")()

to have a true condition but you know is not the best one :)

thanks for help ;)

Proxy's iframe element takes place in DOM

When calling proxy method, the injected iframe element still takes place in DOM, I see the current hiding method is using the next props for the iframe's style -

width: 0;
height: 0;
border: none;

(inside proxy function)
Screen Shot 2022-12-22 at 11 49 12

Using display: none; instead solves the problem for me,
but I'm still trying to figure out if it can mess up any other functionality and if so, is there another solution?

Build Error in Netlify

Tried testing this package and I got this build error when deploying to Netlify using gatsbyjs.

sysSendError

Add chaining

IT would be nice to allow to have:

sysend.off('foo').on('foo', () => {

}).proxy('https://jcubic.pl');

Add demo for single window/tab requests and response query

This can be a cool thing to create.

Something like this:

function request(id) {
   return new Promise(resolve => {
   });
}

it should work similarly to fetch. The other tab should listen to a single request and post it to the origin tab. The request should reject when it will timeout.

Create multiple windows animation demo

It would be cool and most likely possible to create something like a multi-desktop monitor setup but with browser windows that are displayed next to each other.

  • Create Windows connection.
  • Create UI where you drag and arrange windows.
  • Create interactive demo on multiple windows (moving circle).
  • Record video and share the link.
  • Write an article about the demo.

Serializer API

There should be a way to send array instead of just objects as message.

CommonJS support

I can't import it as a normal module in WebPack. It would be nice if we don't need to use something like exports-loader (if I'm not mistaken) in order to use this awesome library. Thoughts on this?

The iframe created by the proxy method will be created repeatedly.

My usage examples.
Perhaps using it this way is not considered correct.

System A
Trigger the following method execution by clicking the button.

sysend.proxy(`${ip}:8080/proxy/proxy.html`)
sysend.broadcast('msg', { 'register': 'test' })


System B
sysend.proxy(`${ip}:8090/proxy/proxy.html`)
sysend.on('msg', function (e) {
console.log(e)
})

I opened the console and found that many iframes with the same address were created.

For example, the following image.
Snipaste_2024-01-15_15-10-21

However, when I trigger a click event on the button in System A, I do not execute the "sysend.proxy" method.
So, System B cannot receive the value transmitted by System A through the broadcast method.
Creating multiple iframes seems to cause the on method to be triggered multiple times.

Can we create only one iframe with the same address to avoid issues from occurring.

Perhaps I should make a note that the version number I am using is 1.16.3.
I haven't tested the latest version, I'm not sure if this problem also exists.

I'm not quite sure if every time the broadcast method is used, the proxy method needs to be triggered once, or if the global only needs to initialize the proxy method once.
I tried triggering the proxy method only once globally and then using broadcast to pass values, but this feature seems to be ineffective.

Leaking of potentially sensitive user information on Cross-Domain communication

If users use cross-domain communication, attackers can create an iframe and listen to any messages sent from the app and also send messages to the app. This happens only in the same browser so this is low impact.

Example exploit, if you upload this file to any domain, you can intercept messages from http://jcubic.pl/sysend.php

sysend.proxy('http://jcubic.pl');
window.addEventListener('message', (e) => {
    console.log(e);
});

Support cross-domain in both directions with single proxy page

Currently not possible to have a single proxy page and communicate in both directions for cross-domain scenarios. For example, sending "broadcast" from the domain w/ the proxy page will be received on the page w/o a proxy. The reverse will not be received. Creating a PR to resolve this.

Library doesn't work when using iframe as normal app

So I've created this app:

sysend.on('input', text => {
    console.log({text});
    input.value = text;
});
input.addEventListener('input', () => {
    console.log(input.value);
    sysend.broadcast('input', input.value);
});

but it doesn't synchronize from page to iframes. It only works for iframes to the main page.
Probably because iframe works in proxy mode, not as an app.

It should work somehow, so you can demo the library.

No primary if primary is closed

There is no longer a primary page when all are closed.

Possible solution

  • broadcast an event and first that pick up will become primary
  • first window that got focus will become primary

Add API to send to primary window

This can be implemented like this:

function sendToPrimary(data) {
    return sysend.list().then(list => {
        return list.find(window => window.primary);
    }).then(primary => {
        sysend.post(primary.id, data);
    });
}

It can be handy to allow:

sysend.post('primary', data);

Vue or React Example

Could you demonstrate/document how to import and use your library in VUE or React pls. Thanks

Cross-domain communication without a proxy

sysend.js currently requires a proxy to send messages between two different domains, but it seems that it is possible to send messages between domains (without a proxy) using Window.postMessage().

Would it be possible to use Window.postMessage() for cross-domain communication, instead of using a proxy?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.