signal-bridge 1.0.9 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -181
- package/dist/index.global.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -244,88 +244,33 @@ Dirancang untuk kebutuhan komunikasi microfrontend / iframe integration dengan f
|
|
|
244
244
|
|
|
245
245
|
---
|
|
246
246
|
|
|
247
|
-
## 🐘 Tutorial: Integrasi dengan Laravel
|
|
247
|
+
## 🐘 Tutorial: Integrasi dengan Laravel (sebagai Remote)
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
Posisi Laravel di sini adalah **Remote** — halaman Laravel berjalan di dalam iframe yang di-embed oleh aplikasi Host lain.
|
|
250
250
|
|
|
251
251
|
```
|
|
252
|
-
[
|
|
252
|
+
[ Host (parent window) ] ---postMessage---> [ Laravel (iframe / remote) ]
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
-
Ada dua posisi Laravel dalam integrasi ini:
|
|
256
|
-
|
|
257
|
-
| Posisi | Deskripsi |
|
|
258
|
-
|---|---|
|
|
259
|
-
| **Host** | Laravel menampilkan halaman yang memuat iframe aplikasi lain |
|
|
260
|
-
| **Remote** | Halaman Laravel di-embed sebagai iframe oleh aplikasi lain |
|
|
261
|
-
|
|
262
255
|
---
|
|
263
256
|
|
|
264
|
-
###
|
|
265
|
-
|
|
266
|
-
Laravel membuka halaman yang memuat iframe dari SPA lain (React, Vue, dsb).
|
|
257
|
+
### Instalasi
|
|
267
258
|
|
|
268
|
-
|
|
259
|
+
Via CDN di Blade:
|
|
269
260
|
|
|
270
261
|
```html
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
<head>
|
|
274
|
-
<title>Host App</title>
|
|
275
|
-
<!-- Sertakan library untuk fungsi encrypt jika dibutuhkan -->
|
|
276
|
-
<script src="https://cdn.jsdelivr.net/npm/signal-bridge/dist/index.global.js"></script>
|
|
277
|
-
</head>
|
|
278
|
-
<body>
|
|
279
|
-
|
|
280
|
-
<iframe
|
|
281
|
-
id="remote-frame"
|
|
282
|
-
src="https://app-remote.example.com"
|
|
283
|
-
width="100%"
|
|
284
|
-
height="600"
|
|
285
|
-
></iframe>
|
|
286
|
-
|
|
287
|
-
<script>
|
|
288
|
-
const iframe = document.getElementById('remote-frame');
|
|
289
|
-
const REMOTE_ORIGIN = 'https://app-remote.example.com';
|
|
290
|
-
|
|
291
|
-
// Terima pesan dari iframe
|
|
292
|
-
window.addEventListener('message', function (event) {
|
|
293
|
-
if (event.origin !== REMOTE_ORIGIN) return;
|
|
294
|
-
if (event.data.__source !== 'remote') return;
|
|
295
|
-
|
|
296
|
-
const { type, payload } = event.data;
|
|
297
|
-
|
|
298
|
-
if (type === 'ready') {
|
|
299
|
-
console.log('Remote siap:', payload);
|
|
300
|
-
|
|
301
|
-
// Kirim data ke remote setelah ia siap
|
|
302
|
-
iframe.contentWindow.postMessage({
|
|
303
|
-
type: 'user-data',
|
|
304
|
-
payload: {
|
|
305
|
-
name: '{{ auth()->user()->name }}',
|
|
306
|
-
role: '{{ auth()->user()->role }}',
|
|
307
|
-
},
|
|
308
|
-
txn: 'txn-001',
|
|
309
|
-
__source: 'host',
|
|
310
|
-
}, REMOTE_ORIGIN);
|
|
311
|
-
}
|
|
262
|
+
<script src="https://cdn.jsdelivr.net/npm/signal-bridge/dist/index.global.js"></script>
|
|
263
|
+
```
|
|
312
264
|
|
|
313
|
-
|
|
314
|
-
// Remote minta logout di host juga
|
|
315
|
-
window.location.href = '/logout';
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
</script>
|
|
265
|
+
Atau via NPM (Laravel Vite):
|
|
319
266
|
|
|
320
|
-
|
|
321
|
-
|
|
267
|
+
```bash
|
|
268
|
+
npm install signal-bridge
|
|
322
269
|
```
|
|
323
270
|
|
|
324
271
|
---
|
|
325
272
|
|
|
326
|
-
###
|
|
327
|
-
|
|
328
|
-
Halaman Laravel di-embed sebagai iframe oleh aplikasi lain.
|
|
273
|
+
### Setup Dasar
|
|
329
274
|
|
|
330
275
|
**`resources/views/remote.blade.php`**
|
|
331
276
|
|
|
@@ -337,159 +282,145 @@ Halaman Laravel di-embed sebagai iframe oleh aplikasi lain.
|
|
|
337
282
|
<script src="https://cdn.jsdelivr.net/npm/signal-bridge/dist/index.global.js"></script>
|
|
338
283
|
</head>
|
|
339
284
|
<body>
|
|
340
|
-
|
|
341
|
-
<div id="app">
|
|
342
|
-
<p id="user-name">Memuat data...</p>
|
|
343
|
-
</div>
|
|
285
|
+
<div id="app"></div>
|
|
344
286
|
|
|
345
287
|
<script>
|
|
346
|
-
|
|
347
|
-
const bridge = window.signalBridge.init('my-laravel-app', {
|
|
288
|
+
const bridge = window.signalBridge.initBridge('my-laravel-app', {
|
|
348
289
|
allowedHostOrigins: ['https://host-app.example.com'],
|
|
349
|
-
debug: true,
|
|
290
|
+
debug: true,
|
|
350
291
|
});
|
|
351
292
|
|
|
352
|
-
// Dengarkan pesan dari host
|
|
353
293
|
bridge.listen(function (message) {
|
|
354
294
|
|
|
355
|
-
|
|
356
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Handshake dari host.
|
|
297
|
+
* Payload yang diterima:
|
|
298
|
+
* {
|
|
299
|
+
* "type": "connection",
|
|
300
|
+
* "payload": {
|
|
301
|
+
* "token": "xxxx",
|
|
302
|
+
* "txn": "2mFMfAtLD",
|
|
303
|
+
* "idChangeRole": null,
|
|
304
|
+
* "ticket": "ST-1780470879-VGVVGUVY0L",
|
|
305
|
+
* "path": "/dashboard"
|
|
306
|
+
* },
|
|
307
|
+
* "__source": "host",
|
|
308
|
+
* "__origin": "https://host-app.example.com"
|
|
309
|
+
* }
|
|
310
|
+
*/
|
|
357
311
|
if (message.type === 'connection') {
|
|
358
|
-
const
|
|
359
|
-
|
|
312
|
+
const { token, txn, ticket, path, idChangeRole } = message.payload;
|
|
313
|
+
|
|
314
|
+
// Dekripsi token menggunakan txn sebagai key per-sesi
|
|
315
|
+
const decryptedToken = window.signalBridge.decrypt(token, txn);
|
|
360
316
|
|
|
361
|
-
console.log('Decrypted Token:',
|
|
362
|
-
console.log('
|
|
317
|
+
console.log('Decrypted Token:', decryptedToken);
|
|
318
|
+
console.log('Ticket:', ticket);
|
|
319
|
+
console.log('Path:', path);
|
|
320
|
+
console.log('idChangeRole:', idChangeRole);
|
|
363
321
|
|
|
364
|
-
// Simpan ke state
|
|
365
|
-
|
|
322
|
+
// Simpan token ke state / localStorage sesuai kebutuhan
|
|
323
|
+
// Arahkan ke path yang dikirim host
|
|
324
|
+
if (path && path !== window.location.pathname) {
|
|
325
|
+
window.location.href = path;
|
|
326
|
+
}
|
|
366
327
|
}
|
|
367
328
|
|
|
368
|
-
// Host
|
|
329
|
+
// Host meminta ganti role
|
|
369
330
|
if (message.type === 'change-role') {
|
|
370
|
-
const
|
|
371
|
-
|
|
331
|
+
const { data, txn } = message.payload;
|
|
332
|
+
const role = window.signalBridge.decrypt(data, txn);
|
|
372
333
|
|
|
373
|
-
|
|
334
|
+
console.log('Decrypted Role selected:', role);
|
|
335
|
+
// Lakukan reload atau update state sesuai role baru
|
|
374
336
|
}
|
|
375
337
|
|
|
376
338
|
});
|
|
377
339
|
|
|
378
|
-
//
|
|
379
|
-
bridge.emit('
|
|
380
|
-
</script>
|
|
340
|
+
// Emit navigate saat halaman pertama kali dimuat
|
|
341
|
+
bridge.emit('navigate', { path: window.location.pathname });
|
|
381
342
|
|
|
343
|
+
// Emit navigate setiap perubahan URL (back/forward browser)
|
|
344
|
+
window.addEventListener('popstate', function () {
|
|
345
|
+
bridge.emit('navigate', { path: window.location.pathname });
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Emit navigate setiap perubahan hash (untuk hash-based routing)
|
|
349
|
+
window.addEventListener('hashchange', function () {
|
|
350
|
+
const path = location.hash.startsWith('#/')
|
|
351
|
+
? location.hash.slice(1)
|
|
352
|
+
: location.pathname;
|
|
353
|
+
bridge.emit('navigate', { path });
|
|
354
|
+
});
|
|
355
|
+
</script>
|
|
382
356
|
</body>
|
|
383
357
|
</html>
|
|
384
358
|
```
|
|
385
359
|
|
|
360
|
+
> Pesan yang dikirim ke host akan berbentuk:
|
|
361
|
+
> ```json
|
|
362
|
+
> {
|
|
363
|
+
> "app_id": "my-laravel-app",
|
|
364
|
+
> "type": "navigate",
|
|
365
|
+
> "payload": { "path": "/u/dashboard" },
|
|
366
|
+
> "txn": "",
|
|
367
|
+
> "__origin": "https://your-laravel-app.com",
|
|
368
|
+
> "__source": "remote"
|
|
369
|
+
> }
|
|
370
|
+
> ```
|
|
371
|
+
|
|
386
372
|
**Route:**
|
|
387
373
|
|
|
388
374
|
```php
|
|
389
375
|
// routes/web.php
|
|
390
376
|
Route::get('/remote', function () {
|
|
391
377
|
return view('remote');
|
|
392
|
-
})
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
---
|
|
396
|
-
|
|
397
|
-
### Skenario C — Dengan Enkripsi AES
|
|
398
|
-
|
|
399
|
-
Gunakan `cryptoKey` agar payload dienkripsi saat dikirim lewat postMessage.
|
|
400
|
-
|
|
401
|
-
**`.env`**
|
|
402
|
-
|
|
403
|
-
```
|
|
404
|
-
BRIDGE_KEY=Xk9mN3pQrT2wAbCd
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
**`config/app.php`**
|
|
408
|
-
|
|
409
|
-
```php
|
|
410
|
-
'bridge_key' => env('BRIDGE_KEY'),
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
**Sisi Host (mengirim payload terenkripsi):**
|
|
414
|
-
|
|
415
|
-
```html
|
|
416
|
-
<script src="https://cdn.jsdelivr.net/npm/signal-bridge/dist/index.global.js"></script>
|
|
417
|
-
<script>
|
|
418
|
-
const SECRET_KEY = '{{ config("app.bridge_key") }}';
|
|
419
|
-
const REMOTE_ORIGIN = 'https://app-remote.example.com';
|
|
420
|
-
|
|
421
|
-
window.addEventListener('message', function (event) {
|
|
422
|
-
if (event.origin !== REMOTE_ORIGIN) return;
|
|
423
|
-
if (event.data.__source !== 'remote') return;
|
|
424
|
-
|
|
425
|
-
if (event.data.type === 'ready') {
|
|
426
|
-
// Enkripsi payload sebelum dikirim
|
|
427
|
-
const encryptedPayload = window.signalBridge.encrypt(
|
|
428
|
-
{ token: '{{ session("api_token") }}', userId: {{ auth()->id() }} },
|
|
429
|
-
SECRET_KEY
|
|
430
|
-
);
|
|
431
|
-
|
|
432
|
-
document.getElementById('remote-frame').contentWindow.postMessage({
|
|
433
|
-
type: 'auth',
|
|
434
|
-
payload: encryptedPayload,
|
|
435
|
-
txn: 'txn-auth-001',
|
|
436
|
-
__source: 'host',
|
|
437
|
-
}, REMOTE_ORIGIN);
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
</script>
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
**Sisi Remote (blade dalam iframe — payload otomatis di-decrypt):**
|
|
444
|
-
|
|
445
|
-
```html
|
|
446
|
-
<script src="https://cdn.jsdelivr.net/npm/signal-bridge/dist/index.global.js"></script>
|
|
447
|
-
<script>
|
|
448
|
-
const bridge = window.signalBridge.init('my-laravel-app', {
|
|
449
|
-
allowedHostOrigins: ['https://host-app.example.com'],
|
|
450
|
-
cryptoKey: '{{ config("app.bridge_key") }}', // key yang sama
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
bridge.listen(function (message) {
|
|
454
|
-
// payload sudah otomatis di-decrypt oleh library
|
|
455
|
-
if (message.type === 'auth') {
|
|
456
|
-
console.log('User ID:', message.payload.userId);
|
|
457
|
-
console.log('Token:', message.payload.token);
|
|
458
|
-
}
|
|
459
|
-
});
|
|
460
|
-
</script>
|
|
378
|
+
});
|
|
461
379
|
```
|
|
462
380
|
|
|
463
381
|
---
|
|
464
382
|
|
|
465
|
-
###
|
|
466
|
-
|
|
467
|
-
**Install:**
|
|
468
|
-
|
|
469
|
-
```bash
|
|
470
|
-
npm install signal-bridge
|
|
471
|
-
```
|
|
383
|
+
### Menggunakan NPM + Vite
|
|
472
384
|
|
|
473
385
|
**`resources/js/bridge.js`**
|
|
474
386
|
|
|
475
387
|
```js
|
|
476
|
-
import { initBridge
|
|
388
|
+
import { initBridge } from 'signal-bridge';
|
|
477
389
|
|
|
478
|
-
const bridge = initBridge('my-app', {
|
|
390
|
+
const bridge = initBridge('my-laravel-app', {
|
|
479
391
|
allowedHostOrigins: [import.meta.env.VITE_HOST_ORIGIN],
|
|
480
|
-
cryptoKey: import.meta.env.VITE_BRIDGE_KEY,
|
|
481
392
|
debug: import.meta.env.DEV,
|
|
482
393
|
});
|
|
483
394
|
|
|
484
395
|
bridge.listen((message) => {
|
|
485
|
-
|
|
486
|
-
|
|
396
|
+
|
|
397
|
+
if (message.type === 'connection') {
|
|
398
|
+
const { token, txn, ticket, path, idChangeRole } = message.payload;
|
|
399
|
+
|
|
400
|
+
const decryptedToken = window.signalBridge.decrypt(token, txn);
|
|
401
|
+
|
|
402
|
+
console.log('Decrypted Token:', decryptedToken);
|
|
403
|
+
console.log('Ticket:', ticket);
|
|
404
|
+
console.log('Path:', path);
|
|
405
|
+
console.log('idChangeRole:', idChangeRole);
|
|
487
406
|
}
|
|
407
|
+
|
|
408
|
+
if (message.type === 'change-role') {
|
|
409
|
+
const { data, txn } = message.payload;
|
|
410
|
+
const role = window.signalBridge.decrypt(data, txn);
|
|
411
|
+
|
|
412
|
+
console.log('Decrypted Role selected:', role);
|
|
413
|
+
}
|
|
414
|
+
|
|
488
415
|
});
|
|
489
416
|
|
|
490
|
-
|
|
417
|
+
// Emit navigate saat halaman pertama kali dimuat
|
|
418
|
+
bridge.emit('navigate', { path: window.location.pathname });
|
|
419
|
+
|
|
420
|
+
// Untuk Inertia.js — hook ke router event
|
|
421
|
+
// import { router } from '@inertiajs/vue3';
|
|
422
|
+
// router.on('navigate', () => bridge.emit('navigate', { path: window.location.pathname }));
|
|
491
423
|
|
|
492
|
-
// Export untuk dipakai di file lain
|
|
493
424
|
export { bridge };
|
|
494
425
|
```
|
|
495
426
|
|
|
@@ -497,7 +428,6 @@ export { bridge };
|
|
|
497
428
|
|
|
498
429
|
```
|
|
499
430
|
VITE_HOST_ORIGIN=https://host-app.example.com
|
|
500
|
-
VITE_BRIDGE_KEY=Xk9mN3pQrT2wAbCd
|
|
501
431
|
```
|
|
502
432
|
|
|
503
433
|
**`resources/views/remote.blade.php`**
|
|
@@ -514,26 +444,32 @@ VITE_BRIDGE_KEY=Xk9mN3pQrT2wAbCd
|
|
|
514
444
|
</html>
|
|
515
445
|
```
|
|
516
446
|
|
|
517
|
-
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
### Akses Instance dari File Lain
|
|
518
450
|
|
|
519
451
|
```js
|
|
520
452
|
import { signalBridge } from 'signal-bridge';
|
|
521
453
|
|
|
522
|
-
// Ambil instance yang sudah dibuat
|
|
454
|
+
// Ambil instance yang sudah dibuat tanpa init ulang
|
|
523
455
|
const bridge = signalBridge();
|
|
524
456
|
bridge.emit('form-submit', { id: 123 });
|
|
525
457
|
```
|
|
526
458
|
|
|
527
459
|
---
|
|
528
460
|
|
|
529
|
-
###
|
|
461
|
+
### Catatan Keamanan
|
|
530
462
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
import { generateKey } from 'signal-bridge';
|
|
463
|
+
- Selalu set `allowedHostOrigins` secara eksplisit, jangan pakai wildcard.
|
|
464
|
+
- `txn` adalah key dekripsi per-sesi yang dikirim Host — jangan simpan permanen.
|
|
465
|
+
- Konfigurasi `Content-Security-Policy: frame-ancestors` agar hanya Host yang diizinkan bisa embed halaman Laravel:
|
|
535
466
|
|
|
536
|
-
|
|
467
|
+
```php
|
|
468
|
+
// Middleware atau kernel
|
|
469
|
+
$response->headers->set(
|
|
470
|
+
'Content-Security-Policy',
|
|
471
|
+
"frame-ancestors 'self' https://host-app.example.com"
|
|
472
|
+
);
|
|
537
473
|
```
|
|
538
474
|
|
|
539
475
|
---
|
package/dist/index.global.js
CHANGED