Skip to content

Commit d874771

Browse files
committed
Add rpc method #48
1 parent aceed2e commit d874771

File tree

4 files changed

+119
-23
lines changed

4 files changed

+119
-23
lines changed

README.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
<img src="https://github.com/jcubic/sysend.js/blob/master/assets/logo.svg?raw=true" alt="Sysend.js logo"/>
33
</p>
44

5-
[![npm](https://img.shields.io/badge/npm-1.14.3-blue.svg)](https://www.npmjs.com/package/sysend)
6-
![bower](https://img.shields.io/badge/bower-1.14.3-yellow.svg)
5+
[![npm](https://img.shields.io/badge/npm-1.15.0-blue.svg)](https://www.npmjs.com/package/sysend)
6+
![bower](https://img.shields.io/badge/bower-1.15.0-yellow.svg)
77
![downloads](https://img.shields.io/npm/dt/sysend.svg)
88
[![jsdelivr](https://img.shields.io/jsdelivr/npm/hm/sysend)](https://www.jsdelivr.com/package/npm/sysend)
99

@@ -152,23 +152,23 @@ sysend object:
152152
| `post(<window_id>, [, object])` | send any data to other window | window_id - string of the target window (use `'primary'` to send to primary window)<br>object - any data | 1.6.0 / `'primary'` target 1.14.0 |
153153
| `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 |
154154
| `track(event, callback)` | track inter window communication events | event - any of the strings: `"open"`, `"close"`, `"primary"`, <br>`"secondary"`, `"message"`<br>callback - different function depend on the event:<br>* `"message"` - `{data, origin}` - where data is anything the `post()` sends, and origin is `id` of the sender.<br>* `"open"` - `{count, primary, id}` when new window/tab is opened<br>* `"close"` - `{count, primary, id, self}` when window/tab is closed<br>* `"primary"` and `"secondary"` function has no arguments and is called when window/tab become secondary or primary.<br>* `"ready"` - event when tracking is ready. | 1.6.0 except `ready` - 1.10.0 |
155-
| `untrack(event [,callback])` | remove sigle event listener all all listeners for a given event | event - any of the strings `'open'`, `'close'`, `'primary'`, `'secondary'`, or `'message'`. | 1.6.0 |
155+
| `untrack(event [,callback])` | remove single event listener all listeners for a given event | event - any of the strings `'open'`, `'close'`, `'primary'`, `'secondary'`, or `'message'`. | 1.6.0 |
156156
| `isPrimary()` | function returns true if window is primary (first open or last that remain) | NA | 1.6.0 |
157157
| `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 |
158158
| `useLocalStorage([toggle])` | Function set or toggle localStorage mode. | argument is optional and can be `true` or `false`. | 1.14.0 |
159-
159+
| `rpc(object): Promise<fn(id, ...args): Promise>` | Function accept an object with methods and return a Promise that resolve to object with same methods but async that return a Promise. The result function accept first additional argument that is ID of window/tab that it should sent request. The other window/tab call the function with result. | 1.15.0 |
160160
161161
To see details of using the API, see [demo.html source code](https://github.com/jcubic/sysend.js/blob/master/demo.html) or [TypeScript definition file](https://github.com/jcubic/sysend.js/blob/master/sysend.d.ts).
162162
163163
## Story
164164
165165
The story of this library came from my question on StackOverflow from 2014: [Sending notifications between instances of the page in the same browser](https://stackoverflow.com/q/24182409/387194), 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](https://jcubic.wordpress.com/2014/06/20/cross-domain-localstorage/). The blog post have steady number of visitors (actually it's most viewed post on that blog).
166166
167-
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 sychronizing application between browser tabs.
167+
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.
168168
169169
## License
170170
171-
Copyright (C) 2014-2022 [Jakub T. Jankiewicz](https://jcubic.pl/me)<br/>
171+
Copyright (C) 2014-2023 [Jakub T. Jankiewicz](https://jcubic.pl/me)<br/>
172172
Released under the [MIT license](https://opensource.org/licenses/MIT)
173173
174174
This is free software; you are free to change and redistribute it.<br/>

sysend.d.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**@license
2-
* sysend.js - send messages between browser windows/tabs version 1.14.3
2+
* sysend.js - send messages between browser windows/tabs version 1.15.0
33
*
4-
* Copyright (C) 2014-2022 Jakub T. Jankiewicz <https://jcubic.pl/me>
4+
* Copyright (C) 2014-2023 Jakub T. Jankiewicz <https://jcubic.pl/me>
55
* Released under the MIT license
66
*
77
*/
@@ -25,8 +25,14 @@ interface Sysend {
2525
post(target: string, data?: any): void;
2626
channel(...domains: string[]): void;
2727
isPrimary(): boolean;
28+
rpc<T extends Array<unknown>, U>(object: Record<string, (...args: T) => U>): Promise<Record<string, (id: string, ...args: T) => Promise<U>>>
2829
}
2930

31+
//Promise<Record<string, (id: string, ...args: T) => Promise<U>>;
32+
33+
//type RPC<args extend Array
34+
35+
3036
declare const sysend: Sysend;
3137

3238
export default sysend;

sysend.js

+91-15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**@license
2-
* sysend.js - send messages between browser windows/tabs version 1.14.3
2+
* sysend.js - send messages between browser windows/tabs version 1.15.0
33
*
4-
* Copyright (C) 2014-2022 Jakub T. Jankiewicz <https://jcubic.pl/me>
4+
* Copyright (C) 2014-2023 Jakub T. Jankiewicz <https://jcubic.pl/me>
55
* Released under the MIT license
66
*
77
* The idea for localStorage implementation came from this StackOverflow question:
@@ -88,17 +88,17 @@
8888
serializer.from = from;
8989
return sysend;
9090
},
91-
proxy: function() {
92-
[].slice.call(arguments).forEach(function(url) {
91+
proxy: function(...args) {
92+
args.forEach(function(url) {
9393
if (typeof url === 'string' && host(url) !== window.location.host) {
9494
domains = domains || [];
9595
domains.push(origin(url));
96-
var iframe = document.createElement('iframe');
96+
const iframe = document.createElement('iframe');
9797
iframe.style.width = iframe.style.height = 0;
9898
iframe.style.position = 'absolute';
9999
iframe.style.top = iframe.style.left = '-9999px';
100100
iframe.style.border = 'none';
101-
var proxy_url = url;
101+
let proxy_url = url;
102102
if (!url.match(/\.html$/)) {
103103
proxy_url = url.replace(/\/$/, '') + '/proxy.html';
104104
}
@@ -110,7 +110,7 @@
110110
iframe.removeEventListener('error', handler);
111111
});
112112
iframe.addEventListener('load', function handler() {
113-
var win;
113+
let win;
114114
// fix for Safari
115115
// https://stackoverflow.com/q/42632188/387194
116116
try {
@@ -177,11 +177,11 @@
177177
});
178178
},
179179
list: function() {
180-
var id = list_id++;
181-
var marker = { target: target_id, id: id };
182-
var timer = delay(sysend.timeout);
180+
const id = list_id++;
181+
const marker = { target: target_id, id: id };
182+
const timer = delay(sysend.timeout);
183183
return new Promise(function(resolve) {
184-
var ids = [];
184+
const ids = [];
185185
sysend.on('__window_ack__', function(data) {
186186
if (data.origin.target === target_id && data.origin.id === id) {
187187
ids.push({
@@ -196,8 +196,8 @@
196196
});
197197
});
198198
},
199-
channel: function() {
200-
domains = [].slice.apply(arguments).map(origin);
199+
channel: function(...args) {
200+
domains = args.map(origin);
201201
return sysend;
202202
},
203203
isPrimary: function() {
@@ -210,6 +210,65 @@
210210
force_ls = true;
211211
}
212212
},
213+
rpc: function(object) {
214+
const prefix = ++rpc_prefix;
215+
const req = `__${prefix}_rpc_request__`;
216+
const res = `__${prefix}_rpc_response__`;
217+
let request_index = 0;
218+
const timeout = 1000;
219+
function request(id, method, args = []) {
220+
const req_id = ++request_index;
221+
return new Promise((resolve, reject) => {
222+
sysend.track('message', function handler({data, origin}) {
223+
if (data.type === res) {
224+
const { result, error, id: res_id } = data;
225+
if (origin === id && req_id === res_id) {
226+
if (error) {
227+
reject(error);
228+
} else {
229+
resolve(result);
230+
}
231+
clearTimeout(timer);
232+
sysend.untrack('message', handler);
233+
}
234+
}
235+
});
236+
sysend.post(id, { method, id: req_id, type: req, args });
237+
const timer = setTimeout(() => {
238+
reject(new Error('Timeout error'));
239+
}, timeout);
240+
});
241+
}
242+
243+
sysend.track('message', async function handler({ data, origin }) {
244+
if (data.type == req) {
245+
const { method, args, id } = data;
246+
const type = res;
247+
if (Object.hasOwn(object, method)) {
248+
try {
249+
unpromise(object[method](...args), function(result) {
250+
sysend.post(origin, { result, id, type });
251+
}, function(error) {
252+
sysend.post(origin, { error: error.message, id, type });
253+
});
254+
} catch(e) {
255+
sysend.post(origin, { error: e.message, id, type });
256+
}
257+
} else {
258+
sysend.post(origin, { error: 'Method not found', id, type });
259+
260+
}
261+
}
262+
});
263+
return Object.fromEntries(Object.keys(object).map(name => {
264+
return [name, (id, ...args) => {
265+
if (!id) {
266+
throw new Error('You need to specify the target window/tab');
267+
}
268+
return request(id, name, args);
269+
}];
270+
}));
271+
}
213272
};
214273
// -------------------------------------------------------------------------
215274
Object.defineProperty(sysend, 'timeout', {
@@ -244,6 +303,24 @@
244303
};
245304
})();
246305
// -------------------------------------------------------------------------
306+
function is_promise(obj) {
307+
return obj && typeof object == 'object' &&
308+
typeof object.then === 'function';
309+
}
310+
// -------------------------------------------------------------------------
311+
function unpromise(obj, callback, error = null) {
312+
if (is_promise(obj)) {
313+
const ret = obj.then(callback);
314+
if (error === null) {
315+
return ret;
316+
} else {
317+
return ret.catch(error);
318+
}
319+
} else {
320+
return callback(obj);
321+
}
322+
}
323+
// -------------------------------------------------------------------------
247324
function delay(time) {
248325
return function() {
249326
return new Promise(function(resolve) {
@@ -345,8 +422,7 @@
345422
});
346423
}
347424
// -------------------------------------------------------------------------
348-
function trigger(arr) {
349-
var args = [].slice.call(arguments, 1);
425+
function trigger(arr, ...args) {
350426
arr.forEach(function(fn) {
351427
fn.apply(null, args);
352428
});

test.ts

+14
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,17 @@ sysend.serializer(function(data) {
2727
}, function(string) {
2828
return JSON.parse(string);
2929
});
30+
31+
32+
const rpc = sysend.rpc({
33+
hello(str: string) {
34+
return parseInt(str, 10);
35+
}
36+
});
37+
38+
rpc.then(service => {
39+
service.hello("<ID>", "10").then(n => {
40+
const x: number = n + 10;
41+
console.log(x);
42+
});
43+
});

0 commit comments

Comments
 (0)