signal-bridge 1.0.7 → 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.
- package/README.md +316 -0
- package/package.json +11 -4
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,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signal-bridge",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Secure iframe communication bridge",
|
|
5
5
|
"author": "Ekahersada <ekahersada@gmail.com>",
|
|
6
|
-
"main": "dist/index.
|
|
7
|
-
"module": "dist/index.
|
|
8
|
-
"unpkg": "dist/index.umd.js",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
9
8
|
"types": "dist/index.d.ts",
|
|
9
|
+
"unpkg": "dist/index.global.js",
|
|
10
|
+
"jsdelivr": "dist/index.global.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"require": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
10
17
|
"scripts": {
|
|
11
18
|
"build": "tsup",
|
|
12
19
|
"dev": "tsup --watch",
|