signal-bridge 1.0.8 → 1.0.10
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 +252 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -242,6 +242,258 @@ Dirancang untuk kebutuhan komunikasi microfrontend / iframe integration dengan f
|
|
|
242
242
|
|
|
243
243
|
---
|
|
244
244
|
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## 🐘 Tutorial: Integrasi dengan Laravel (sebagai Remote)
|
|
248
|
+
|
|
249
|
+
Posisi Laravel di sini adalah **Remote** — halaman Laravel berjalan di dalam iframe yang di-embed oleh aplikasi Host lain.
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
[ Host (parent window) ] ---postMessage---> [ Laravel (iframe / remote) ]
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
### Instalasi
|
|
258
|
+
|
|
259
|
+
Via CDN di Blade:
|
|
260
|
+
|
|
261
|
+
```html
|
|
262
|
+
<script src="https://cdn.jsdelivr.net/npm/signal-bridge/dist/index.global.js"></script>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Atau via NPM (Laravel Vite):
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
npm install signal-bridge
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
### Setup Dasar
|
|
274
|
+
|
|
275
|
+
**`resources/views/remote.blade.php`**
|
|
276
|
+
|
|
277
|
+
```html
|
|
278
|
+
<!DOCTYPE html>
|
|
279
|
+
<html>
|
|
280
|
+
<head>
|
|
281
|
+
<title>Remote App</title>
|
|
282
|
+
<script src="https://cdn.jsdelivr.net/npm/signal-bridge/dist/index.global.js"></script>
|
|
283
|
+
</head>
|
|
284
|
+
<body>
|
|
285
|
+
<div id="app"></div>
|
|
286
|
+
|
|
287
|
+
<script>
|
|
288
|
+
const bridge = window.signalBridge.init('my-laravel-app', {
|
|
289
|
+
allowedHostOrigins: ['https://host-app.example.com'],
|
|
290
|
+
debug: true,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
bridge.listen(function (message) {
|
|
294
|
+
|
|
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
|
+
*/
|
|
311
|
+
if (message.type === 'connection') {
|
|
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);
|
|
316
|
+
|
|
317
|
+
console.log('Decrypted Token:', decryptedToken);
|
|
318
|
+
console.log('Ticket:', ticket);
|
|
319
|
+
console.log('Path:', path);
|
|
320
|
+
console.log('idChangeRole:', idChangeRole);
|
|
321
|
+
|
|
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
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Host meminta ganti role
|
|
330
|
+
if (message.type === 'change-role') {
|
|
331
|
+
const { data, txn } = message.payload;
|
|
332
|
+
const role = window.signalBridge.decrypt(data, txn);
|
|
333
|
+
|
|
334
|
+
console.log('Decrypted Role selected:', role);
|
|
335
|
+
// Lakukan reload atau update state sesuai role baru
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Emit navigate saat halaman pertama kali dimuat
|
|
341
|
+
bridge.emit('navigate', { path: window.location.pathname });
|
|
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>
|
|
356
|
+
</body>
|
|
357
|
+
</html>
|
|
358
|
+
```
|
|
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
|
+
|
|
372
|
+
**Route:**
|
|
373
|
+
|
|
374
|
+
```php
|
|
375
|
+
// routes/web.php
|
|
376
|
+
Route::get('/remote', function () {
|
|
377
|
+
return view('remote');
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
### Menggunakan NPM + Vite
|
|
384
|
+
|
|
385
|
+
**`resources/js/bridge.js`**
|
|
386
|
+
|
|
387
|
+
```js
|
|
388
|
+
import { initBridge } from 'signal-bridge';
|
|
389
|
+
|
|
390
|
+
const bridge = initBridge('my-laravel-app', {
|
|
391
|
+
allowedHostOrigins: [import.meta.env.VITE_HOST_ORIGIN],
|
|
392
|
+
debug: import.meta.env.DEV,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
bridge.listen((message) => {
|
|
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);
|
|
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
|
+
|
|
415
|
+
});
|
|
416
|
+
|
|
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 }));
|
|
423
|
+
|
|
424
|
+
export { bridge };
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**`.env`**
|
|
428
|
+
|
|
429
|
+
```
|
|
430
|
+
VITE_HOST_ORIGIN=https://host-app.example.com
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**`resources/views/remote.blade.php`**
|
|
434
|
+
|
|
435
|
+
```html
|
|
436
|
+
<!DOCTYPE html>
|
|
437
|
+
<html>
|
|
438
|
+
<head>
|
|
439
|
+
@vite(['resources/js/app.js', 'resources/js/bridge.js'])
|
|
440
|
+
</head>
|
|
441
|
+
<body>
|
|
442
|
+
<div id="app"></div>
|
|
443
|
+
</body>
|
|
444
|
+
</html>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
### Akses Instance dari File Lain
|
|
450
|
+
|
|
451
|
+
```js
|
|
452
|
+
import { signalBridge } from 'signal-bridge';
|
|
453
|
+
|
|
454
|
+
// Ambil instance yang sudah dibuat tanpa init ulang
|
|
455
|
+
const bridge = signalBridge();
|
|
456
|
+
bridge.emit('form-submit', { id: 123 });
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
### Catatan Keamanan
|
|
462
|
+
|
|
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:
|
|
466
|
+
|
|
467
|
+
```php
|
|
468
|
+
// Middleware atau kernel
|
|
469
|
+
$response->headers->set(
|
|
470
|
+
'Content-Security-Policy',
|
|
471
|
+
"frame-ancestors 'self' https://host-app.example.com"
|
|
472
|
+
);
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
### Catatan Penting untuk Laravel
|
|
478
|
+
|
|
479
|
+
- Selalu set `allowedHostOrigins` secara eksplisit, jangan gunakan wildcard.
|
|
480
|
+
- Simpan `BRIDGE_KEY` di `.env`, bukan hardcode di Blade atau JS.
|
|
481
|
+
- `config('app.bridge_key')` yang di-render ke Blade tetap tampak di source HTML — hanya gunakan untuk data non-kritis.
|
|
482
|
+
- Untuk transfer token/kredensial sensitif, tetap gunakan endpoint API Laravel dengan autentikasi Sanctum/session, bukan lewat bridge.
|
|
483
|
+
- Pastikan header `X-Frame-Options` / `Content-Security-Policy: frame-ancestors` dikonfigurasi benar di Laravel agar hanya host yang diizinkan yang bisa embed halaman Anda.
|
|
484
|
+
|
|
485
|
+
**Contoh CSP di `app/Http/Middleware/`:**
|
|
486
|
+
|
|
487
|
+
```php
|
|
488
|
+
// Di middleware atau kernel
|
|
489
|
+
$response->headers->set(
|
|
490
|
+
'Content-Security-Policy',
|
|
491
|
+
"frame-ancestors 'self' https://host-app.example.com"
|
|
492
|
+
);
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
245
497
|
## 📜 License
|
|
246
498
|
|
|
247
499
|
MIT License © 2026 - EkaHersada
|