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 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
- ### Gambaran Skenario
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
- [ Laravel Host (parent) ] <---postMessage---> [ iframe (Remote) ]
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
- ### Skenario A — Laravel sebagai Host (menampilkan iframe)
265
-
266
- Laravel membuka halaman yang memuat iframe dari SPA lain (React, Vue, dsb).
257
+ ### Instalasi
267
258
 
268
- **`resources/views/host.blade.php`**
259
+ Via CDN di Blade:
269
260
 
270
261
  ```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
- }
262
+ <script src="https://cdn.jsdelivr.net/npm/signal-bridge/dist/index.global.js"></script>
263
+ ```
312
264
 
313
- if (type === 'logout') {
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
- </body>
321
- </html>
267
+ ```bash
268
+ npm install signal-bridge
322
269
  ```
323
270
 
324
271
  ---
325
272
 
326
- ### Skenario B — Laravel sebagai Remote (berjalan di dalam iframe)
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
- // Inisialisasi bridge otomatis mengirim sinyal "ready" ke host
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, // aktifkan log di console (nonaktifkan di production)
290
+ debug: true,
350
291
  });
351
292
 
352
- // Dengarkan pesan dari host
353
293
  bridge.listen(function (message) {
354
294
 
355
- // Handshake awal dari host — host mengirim token & user terenkripsi
356
- // txn digunakan sebagai key dekripsi per-sesi
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 token = bridge.decrypt(message?.token, message?.txn);
359
- const userData = bridge.decrypt(message?.user, message?.txn);
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:', token);
362
- console.log('Decrypted User Data:', userData);
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 atau tampilkan ke UI
365
- document.getElementById('user-name').textContent = userData?.name ?? '';
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 mengirim perintah ganti role
329
+ // Host meminta ganti role
369
330
  if (message.type === 'change-role') {
370
- const role = bridge.decrypt(message?.data, message?.txn);
371
- console.log('Decrypted Role selected:', role);
331
+ const { data, txn } = message.payload;
332
+ const role = window.signalBridge.decrypt(data, txn);
372
333
 
373
- // Lakukan sesuatu dengan role, misalnya redirect atau reload data
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
- // Kirim event ke host
379
- bridge.emit('page-loaded', { path: window.location.pathname });
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
- })->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>
378
+ });
461
379
  ```
462
380
 
463
381
  ---
464
382
 
465
- ### Skenario D — Menggunakan NPM + Vite (Laravel Modern)
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, signalBridge } from 'signal-bridge';
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
- if (message.type === 'user-data') {
486
- console.log('Data dari host:', message.payload);
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
- bridge.emit('page-loaded', { path: window.location.pathname });
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
- **Akses instance dari file lain (misalnya di komponen Alpine.js / Livewire):**
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 sebelumnya
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
- ### Menggunakan generateKey untuk Membuat Key Baru
461
+ ### Catatan Keamanan
530
462
 
531
- Jika butuh membuat `BRIDGE_KEY` baru, jalankan di console browser atau Node:
532
-
533
- ```js
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
- console.log(generateKey(24)); // contoh: "Xk9mN3pQrT2wAbCdEfGhIjKl"
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
  ---
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- var signalBridge = (() => {
2
+ var _signalBridgeLib = (() => {
3
3
  var __create = Object.create;
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signal-bridge",
3
- "version": "1.0.9",
3
+ "version": "1.0.12",
4
4
  "description": "Secure iframe communication bridge",
5
5
  "author": "Ekahersada <ekahersada@gmail.com>",
6
6
  "main": "dist/index.js",