signal-bridge 1.0.8 → 1.0.9

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 +316 -0
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -242,6 +242,322 @@ Dirancang untuk kebutuhan komunikasi microfrontend / iframe integration dengan f
242
242
 
243
243
  ---
244
244
 
245
+ ---
246
+
247
+ ## 🐘 Tutorial: Integrasi dengan Laravel
248
+
249
+ ### Gambaran Skenario
250
+
251
+ ```
252
+ [ Laravel Host (parent) ] <---postMessage---> [ iframe (Remote) ]
253
+ ```
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
+ ---
263
+
264
+ ### Skenario A — Laravel sebagai Host (menampilkan iframe)
265
+
266
+ Laravel membuka halaman yang memuat iframe dari SPA lain (React, Vue, dsb).
267
+
268
+ **`resources/views/host.blade.php`**
269
+
270
+ ```html
271
+ <!DOCTYPE html>
272
+ <html>
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
+ }
312
+
313
+ if (type === 'logout') {
314
+ // Remote minta logout di host juga
315
+ window.location.href = '/logout';
316
+ }
317
+ });
318
+ </script>
319
+
320
+ </body>
321
+ </html>
322
+ ```
323
+
324
+ ---
325
+
326
+ ### Skenario B — Laravel sebagai Remote (berjalan di dalam iframe)
327
+
328
+ Halaman Laravel di-embed sebagai iframe oleh aplikasi lain.
329
+
330
+ **`resources/views/remote.blade.php`**
331
+
332
+ ```html
333
+ <!DOCTYPE html>
334
+ <html>
335
+ <head>
336
+ <title>Remote App</title>
337
+ <script src="https://cdn.jsdelivr.net/npm/signal-bridge/dist/index.global.js"></script>
338
+ </head>
339
+ <body>
340
+
341
+ <div id="app">
342
+ <p id="user-name">Memuat data...</p>
343
+ </div>
344
+
345
+ <script>
346
+ // Inisialisasi bridge — otomatis mengirim sinyal "ready" ke host
347
+ const bridge = window.signalBridge.init('my-laravel-app', {
348
+ allowedHostOrigins: ['https://host-app.example.com'],
349
+ debug: true, // aktifkan log di console (nonaktifkan di production)
350
+ });
351
+
352
+ // Dengarkan pesan dari host
353
+ bridge.listen(function (message) {
354
+
355
+ // Handshake awal dari host — host mengirim token & user terenkripsi
356
+ // txn digunakan sebagai key dekripsi per-sesi
357
+ if (message.type === 'connection') {
358
+ const token = bridge.decrypt(message?.token, message?.txn);
359
+ const userData = bridge.decrypt(message?.user, message?.txn);
360
+
361
+ console.log('Decrypted Token:', token);
362
+ console.log('Decrypted User Data:', userData);
363
+
364
+ // Simpan ke state atau tampilkan ke UI
365
+ document.getElementById('user-name').textContent = userData?.name ?? '';
366
+ }
367
+
368
+ // Host mengirim perintah ganti role
369
+ if (message.type === 'change-role') {
370
+ const role = bridge.decrypt(message?.data, message?.txn);
371
+ console.log('Decrypted Role selected:', role);
372
+
373
+ // Lakukan sesuatu dengan role, misalnya redirect atau reload data
374
+ }
375
+
376
+ });
377
+
378
+ // Kirim event ke host
379
+ bridge.emit('page-loaded', { path: window.location.pathname });
380
+ </script>
381
+
382
+ </body>
383
+ </html>
384
+ ```
385
+
386
+ **Route:**
387
+
388
+ ```php
389
+ // routes/web.php
390
+ Route::get('/remote', function () {
391
+ return view('remote');
392
+ })->middleware('auth');
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>
461
+ ```
462
+
463
+ ---
464
+
465
+ ### Skenario D — Menggunakan NPM + Vite (Laravel Modern)
466
+
467
+ **Install:**
468
+
469
+ ```bash
470
+ npm install signal-bridge
471
+ ```
472
+
473
+ **`resources/js/bridge.js`**
474
+
475
+ ```js
476
+ import { initBridge, signalBridge } from 'signal-bridge';
477
+
478
+ const bridge = initBridge('my-app', {
479
+ allowedHostOrigins: [import.meta.env.VITE_HOST_ORIGIN],
480
+ cryptoKey: import.meta.env.VITE_BRIDGE_KEY,
481
+ debug: import.meta.env.DEV,
482
+ });
483
+
484
+ bridge.listen((message) => {
485
+ if (message.type === 'user-data') {
486
+ console.log('Data dari host:', message.payload);
487
+ }
488
+ });
489
+
490
+ bridge.emit('page-loaded', { path: window.location.pathname });
491
+
492
+ // Export untuk dipakai di file lain
493
+ export { bridge };
494
+ ```
495
+
496
+ **`.env`**
497
+
498
+ ```
499
+ VITE_HOST_ORIGIN=https://host-app.example.com
500
+ VITE_BRIDGE_KEY=Xk9mN3pQrT2wAbCd
501
+ ```
502
+
503
+ **`resources/views/remote.blade.php`**
504
+
505
+ ```html
506
+ <!DOCTYPE html>
507
+ <html>
508
+ <head>
509
+ @vite(['resources/js/app.js', 'resources/js/bridge.js'])
510
+ </head>
511
+ <body>
512
+ <div id="app"></div>
513
+ </body>
514
+ </html>
515
+ ```
516
+
517
+ **Akses instance dari file lain (misalnya di komponen Alpine.js / Livewire):**
518
+
519
+ ```js
520
+ import { signalBridge } from 'signal-bridge';
521
+
522
+ // Ambil instance yang sudah dibuat sebelumnya
523
+ const bridge = signalBridge();
524
+ bridge.emit('form-submit', { id: 123 });
525
+ ```
526
+
527
+ ---
528
+
529
+ ### Menggunakan generateKey untuk Membuat Key Baru
530
+
531
+ Jika butuh membuat `BRIDGE_KEY` baru, jalankan di console browser atau Node:
532
+
533
+ ```js
534
+ import { generateKey } from 'signal-bridge';
535
+
536
+ console.log(generateKey(24)); // contoh: "Xk9mN3pQrT2wAbCdEfGhIjKl"
537
+ ```
538
+
539
+ ---
540
+
541
+ ### Catatan Penting untuk Laravel
542
+
543
+ - Selalu set `allowedHostOrigins` secara eksplisit, jangan gunakan wildcard.
544
+ - Simpan `BRIDGE_KEY` di `.env`, bukan hardcode di Blade atau JS.
545
+ - `config('app.bridge_key')` yang di-render ke Blade tetap tampak di source HTML — hanya gunakan untuk data non-kritis.
546
+ - Untuk transfer token/kredensial sensitif, tetap gunakan endpoint API Laravel dengan autentikasi Sanctum/session, bukan lewat bridge.
547
+ - Pastikan header `X-Frame-Options` / `Content-Security-Policy: frame-ancestors` dikonfigurasi benar di Laravel agar hanya host yang diizinkan yang bisa embed halaman Anda.
548
+
549
+ **Contoh CSP di `app/Http/Middleware/`:**
550
+
551
+ ```php
552
+ // Di middleware atau kernel
553
+ $response->headers->set(
554
+ 'Content-Security-Policy',
555
+ "frame-ancestors 'self' https://host-app.example.com"
556
+ );
557
+ ```
558
+
559
+ ---
560
+
245
561
  ## 📜 License
246
562
 
247
563
  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.9",
4
4
  "description": "Secure iframe communication bridge",
5
5
  "author": "Ekahersada <ekahersada@gmail.com>",
6
6
  "main": "dist/index.js",