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.
Files changed (2) hide show
  1. package/README.md +252 -0
  2. 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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signal-bridge",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Secure iframe communication bridge",
5
5
  "author": "Ekahersada <ekahersada@gmail.com>",
6
6
  "main": "dist/index.js",