wepee commited on
Commit
bac7ea5
·
1 Parent(s): 659efa8

change: integration docs

Browse files
Files changed (2) hide show
  1. README.md +23 -67
  2. resources/js/pages/embed.tsx +36 -24
README.md CHANGED
@@ -51,38 +51,21 @@ ### 1. Quick Start
51
 
52
  ### 2. Authentication
53
 
54
- Widget mendukung dua metode autentikasi. Pilih salah satu sesuai kebutuhan.
55
-
56
- #### 2.1 API Key (Direkomendasikan)
57
-
58
- Gunakan parameter `api_key`. Widget akan secara otomatis menukar API Key dengan bearer token melalui endpoint `/auth/login/api-key` saat pertama kali dimuat.
59
 
60
  ```
61
  /embed?api_key=sk-xxxxxxxxxxxxxxxx
62
  ```
63
 
64
- Token hasil login disimpan di `sessionStorage` browser sehingga tidak perlu login ulang selama sesi tab masih aktif.
65
-
66
- > **Keamanan:** API Key sebaiknya tidak di-hardcode langsung di HTML publik. Untuk lingkungan produksi, generate token di sisi server lalu teruskan lewat metode **Direct Token** (lihat 2.2).
67
-
68
- #### 2.2 Direct Token
69
-
70
- Jika server Anda sudah memiliki bearer token yang valid, teruskan langsung melalui parameter `token`:
71
-
72
- ```
73
- /embed?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
74
- ```
75
-
76
- Token akan langsung disimpan ke `sessionStorage` dan digunakan untuk semua request selanjutnya.
77
 
78
  #### Token Resolution Priority
79
 
80
  Widget menyelesaikan autentikasi dengan urutan berikut:
81
 
82
- 1. Cek `sessionStorage` — jika token sudah tersimpan dari sesi sebelumnya, langsung digunakan.
83
- 2. Cek parameter `?token` di URL.
84
- 3. Cek parameter `?api_key` di URL, lalu tukar dengan token via API.
85
- 4. Jika tidak ada ketiganya → widget menampilkan pesan error autentikasi.
86
 
87
  ---
88
 
@@ -94,8 +77,7 @@ #### 3.1 Authentication
94
 
95
  | Parameter | Tipe | Keterangan |
96
  |-----------|----------|------------|
97
- | `api_key` | `string` | API Key dari Admin Panel. Widget otomatis login dan menyimpan token. |
98
- | `token` | `string` | Bearer token langsung. Digunakan jika server sudah generate token sendiri. |
99
 
100
  #### 3.2 Behavior
101
 
@@ -129,7 +111,7 @@ ### 4. Theming & Customization
129
  | `text_primary` | `#0a0a0a` | Warna teks utama. |
130
  | `text_secondary` | `#737373` | Warna teks sekunder / placeholder. |
131
  | `border` | `#739af1` | Warna border composer dan bubble. |
132
- | `logout` | `#ef4444` | Warna aksi destruktif (tombol hapus sesi). |
133
  | `radius` | `20px` | Border radius bubble chat dan composer. |
134
 
135
  **Contoh URL dengan tema gelap:**
@@ -143,7 +125,7 @@ ### 4. Theming & Customization
143
  &primary=4f8ef7
144
  &primary_gradient=1e3a6e
145
  &border=2a3f6f
146
- &logout=ff6b6b
147
  &radius=16px
148
  ```
149
 
@@ -153,7 +135,7 @@ ### 5. postMessage Events
153
 
154
  Widget berkomunikasi dengan halaman induk melalui `window.postMessage`. Dengarkan event ini di halaman yang meng-embed iframe untuk mendapat notifikasi dari widget.
155
 
156
- #### 6.1 Events yang Dikirim Widget (Outbound)
157
 
158
  | Event type | Kapan dikirim |
159
  |-----------------------------|---------------|
@@ -177,22 +159,22 @@ #### 6.1 Events yang Dikirim Widget (Outbound)
177
 
178
  ### 6. Session Persistence
179
 
180
- Widget menyimpan data sesi di `sessionStorage` browser (bukan `localStorage`), sehingga **otomatis terhapus saat tab ditutup**.
181
 
182
- | Key | Isi |
183
- |------------------------------|-----|
184
- | `sevima_raghub_embed_token` | Bearer token aktif. |
185
- | `sevima_raghub_embed_user` | Objek user JSON (`id`, `name`, `email`, `role`). |
186
 
187
- Karena berbasis `sessionStorage` pada iframe, data ini terisolasi dan tidak dapat diakses oleh halaman induk.
188
 
189
- Untuk memaksa sesi baru (logout), reload iframe atau hapus `sessionStorage` dari dalam iframe.
190
 
191
  ---
192
 
193
  ### 7. Full Integration Examples
194
 
195
- #### 8.1 Embed Sederhana (API Key)
196
 
197
  ```html
198
  <!DOCTYPE html>
@@ -225,7 +207,7 @@ #### 8.1 Embed Sederhana (API Key)
225
  </html>
226
  ```
227
 
228
- #### 8.2 Floating Chat Button
229
 
230
  ```html
231
  <style>
@@ -290,7 +272,7 @@ #### 8.2 Floating Chat Button
290
  </script>
291
  ```
292
 
293
- #### 8.3 Pra-pilih Mata Kuliah & Langsung ke Sesi Tertentu
294
 
295
  ```html
296
  <iframe
@@ -305,39 +287,15 @@ #### 8.3 Pra-pilih Mata Kuliah & Langsung ke Sesi Tertentu
305
  ></iframe>
306
  ```
307
 
308
- #### 8.4 Token dari Server (Lebih Aman untuk Produksi)
309
-
310
- Alur yang direkomendasikan untuk produksi: server Anda meminta token ke RAG Hub API menggunakan API Key, lalu meneruskan token tersebut ke frontend.
311
-
312
- ```javascript
313
- // server-side (Node.js / PHP / Python, dll.)
314
- const response = await fetch('https://your-raghub-domain.com/api/auth/login/api-key', {
315
- method: 'POST',
316
- headers: { 'Content-Type': 'application/json' },
317
- body: JSON.stringify({ api_key: process.env.RAG_API_KEY }),
318
- });
319
- const { token } = await response.json();
320
-
321
- // Render ke template HTML
322
- const iframeSrc = `https://your-raghub-domain.com/embed?token=${token}&user_name=${userName}`;
323
- ```
324
-
325
- ```html
326
- <!-- HTML yang dirender server -->
327
- <iframe src="{{ iframeSrc }}" width="420" height="680" style="border:none;"></iframe>
328
- ```
329
-
330
  ---
331
 
332
  ### 8. Troubleshooting
333
 
334
  #### Widget menampilkan "Token iframe tidak tersedia"
335
 
336
- Pastikan salah satu dari berikut ada di URL:
337
- - `?api_key=` — API Key valid dari Admin Panel.
338
- - `?token=` — Bearer token yang belum expired.
339
 
340
- Jika menggunakan `api_key`, pastikan key tersebut masih aktif dan belum direvoke.
341
 
342
  #### Mata kuliah tidak muncul di dropdown
343
 
@@ -349,15 +307,13 @@ #### Widget tidak merespons tombol ✕ di halaman induk
349
 
350
  #### Token expired di tengah sesi
351
 
352
- Widget tidak melakukan refresh token otomatis. Jika token expired, reload iframe dengan token baru:
353
 
354
  ```javascript
355
  const iframe = document.getElementById('raghub-iframe');
356
- iframe.src = `https://your-raghub-domain.com/embed?token=${newToken}`;
357
  ```
358
 
359
- Reload iframe akan menghapus `sessionStorage`-nya dan memulai sesi baru.
360
-
361
  #### Warna tidak berubah setelah mengubah query string
362
 
363
  Pastikan nilai warna dalam format hex yang valid: 3 atau 6 digit, dengan atau tanpa `#`. Karakter selain `0-9` dan `a-f` akan diabaikan dan diganti nilai default.
 
51
 
52
  ### 2. Authentication
53
 
54
+ Widget menggunakan API Key untuk autentikasi. Saat pertama kali dimuat, widget menukar API Key dengan bearer token melalui endpoint `/auth/login/api-key`, lalu menyimpan token tersebut ke cookie browser selama **7 hari**.
 
 
 
 
55
 
56
  ```
57
  /embed?api_key=sk-xxxxxxxxxxxxxxxx
58
  ```
59
 
60
+ Selama cookie masih valid, widget tidak akan login ulang meski halaman di-refresh. Cookie otomatis dihapus setelah 7 hari atau jika browser cookie dibersihkan.
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
  #### Token Resolution Priority
63
 
64
  Widget menyelesaikan autentikasi dengan urutan berikut:
65
 
66
+ 1. Cek cookie — jika token sudah tersimpan dan belum expired, langsung digunakan.
67
+ 2. Cek parameter `?api_key` di URL, tukar dengan token via API, simpan ke cookie.
68
+ 3. Jika tidak ada keduanya widget menampilkan pesan error autentikasi.
 
69
 
70
  ---
71
 
 
77
 
78
  | Parameter | Tipe | Keterangan |
79
  |-----------|----------|------------|
80
+ | `api_key` | `string` | API Key dari Admin Panel. Widget otomatis login dan menyimpan token ke cookie. |
 
81
 
82
  #### 3.2 Behavior
83
 
 
111
  | `text_primary` | `#0a0a0a` | Warna teks utama. |
112
  | `text_secondary` | `#737373` | Warna teks sekunder / placeholder. |
113
  | `border` | `#739af1` | Warna border composer dan bubble. |
114
+ | `danger` | `#ef4444` | Warna aksi destruktif (tombol hapus sesi). |
115
  | `radius` | `20px` | Border radius bubble chat dan composer. |
116
 
117
  **Contoh URL dengan tema gelap:**
 
125
  &primary=4f8ef7
126
  &primary_gradient=1e3a6e
127
  &border=2a3f6f
128
+ &danger=ff6b6b
129
  &radius=16px
130
  ```
131
 
 
135
 
136
  Widget berkomunikasi dengan halaman induk melalui `window.postMessage`. Dengarkan event ini di halaman yang meng-embed iframe untuk mendapat notifikasi dari widget.
137
 
138
+ #### 5.1 Events yang Dikirim Widget (Outbound)
139
 
140
  | Event type | Kapan dikirim |
141
  |-----------------------------|---------------|
 
159
 
160
  ### 6. Session Persistence
161
 
162
+ Widget menyimpan data sesi di **cookie browser** dengan masa berlaku 7 hari. Cookie diset pada domain RAG Hub (domain iframe), sehingga tidak dapat diakses oleh halaman induk.
163
 
164
+ | Cookie name | Isi | TTL |
165
+ |------------------------------|-----|-----|
166
+ | `sevima_raghub_embed_token` | Bearer token aktif. | 7 hari |
167
+ | `sevima_raghub_embed_user` | Objek user JSON (`id`, `name`, `email`, `role`). | 7 hari |
168
 
169
+ Selama cookie masih valid, widget tidak akan memanggil ulang `/auth/login/api-key` meski halaman di-refresh atau tab ditutup lalu dibuka kembali.
170
 
171
+ Untuk memaksa login ulang (reset sesi), reload iframe dengan mengganti `src` cookie lama akan diabaikan saat API Key baru ditukar.
172
 
173
  ---
174
 
175
  ### 7. Full Integration Examples
176
 
177
+ #### 7.1 Embed Sederhana
178
 
179
  ```html
180
  <!DOCTYPE html>
 
207
  </html>
208
  ```
209
 
210
+ #### 7.2 Floating Chat Button
211
 
212
  ```html
213
  <style>
 
272
  </script>
273
  ```
274
 
275
+ #### 7.3 Pra-pilih Mata Kuliah & Langsung ke Sesi Tertentu
276
 
277
  ```html
278
  <iframe
 
287
  ></iframe>
288
  ```
289
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  ---
291
 
292
  ### 8. Troubleshooting
293
 
294
  #### Widget menampilkan "Token iframe tidak tersedia"
295
 
296
+ Pastikan parameter `?api_key=` ada di URL `src` iframe dan API Key tersebut masih aktif serta belum direvoke di Admin Panel.
 
 
297
 
298
+ Jika cookie lama ada tapi sudah expired, hapus cookie `sevima_raghub_embed_token` dari browser lalu reload.
299
 
300
  #### Mata kuliah tidak muncul di dropdown
301
 
 
307
 
308
  #### Token expired di tengah sesi
309
 
310
+ Widget tidak melakukan refresh token otomatis. Jika token expired, cukup reload iframe — widget akan menukar `api_key` yang sama dengan token baru dan menyimpannya ke cookie:
311
 
312
  ```javascript
313
  const iframe = document.getElementById('raghub-iframe');
314
+ iframe.src = iframe.src; // trigger reload
315
  ```
316
 
 
 
317
  #### Warna tidak berubah setelah mengubah query string
318
 
319
  Pastikan nilai warna dalam format hex yang valid: 3 atau 6 digit, dengan atau tanpa `#`. Karakter selain `0-9` dan `a-f` akan diabaikan dan diganti nilai default.
resources/js/pages/embed.tsx CHANGED
@@ -37,7 +37,7 @@ type EmbedConfig = {
37
  backgroundProfile: string;
38
  border: string;
39
  courseId?: string | null;
40
- logout: string;
41
  primary: string;
42
  primaryGradient: string;
43
  radius: string;
@@ -53,7 +53,7 @@ const COLOR_DEFAULTS = {
53
  background: '#ffffff',
54
  backgroundProfile: '#ffffff',
55
  border: '#739af1',
56
- logout: '#ef4444',
57
  primary: '#2764eb',
58
  primaryGradient: '#ccdcff',
59
  textPrimary: '#0a0a0a',
@@ -94,7 +94,7 @@ function parseEmbedConfig(params: URLSearchParams): EmbedConfig {
94
  backgroundProfile: parseColor(params.get('background_profile'), COLOR_DEFAULTS.backgroundProfile),
95
  border: parseColor(params.get('border'), COLOR_DEFAULTS.border),
96
  courseId: params.get('course_id') ?? params.get('courseId') ?? undefined,
97
- logout: parseColor(params.get('logout'), COLOR_DEFAULTS.logout),
98
  primary: parseColor(params.get('primary'), COLOR_DEFAULTS.primary),
99
  primaryGradient: parseColor(params.get('primary_gradient'), COLOR_DEFAULTS.primaryGradient),
100
  radius: parseRadius(params.get('radius')),
@@ -106,17 +106,39 @@ function parseEmbedConfig(params: URLSearchParams): EmbedConfig {
106
  };
107
  }
108
 
109
- function getStoredEmbedToken(): string | null {
110
- try {
111
- return sessionStorage.getItem(EMBED_TOKEN_KEY);
112
- } catch {
113
- return null;
 
 
 
 
 
 
 
 
114
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  }
116
 
117
  function getStoredEmbedUser(): AuthUser | null {
118
  try {
119
- const raw = sessionStorage.getItem(EMBED_USER_KEY);
120
  return raw ? (JSON.parse(raw) as AuthUser) : null;
121
  } catch {
122
  return null;
@@ -124,13 +146,11 @@ function getStoredEmbedUser(): AuthUser | null {
124
  }
125
 
126
  function storeEmbedSession(token: string, user?: AuthUser): void {
127
- try {
128
- sessionStorage.setItem(EMBED_TOKEN_KEY, token);
129
 
130
- if (user) {
131
- sessionStorage.setItem(EMBED_USER_KEY, JSON.stringify(user));
132
- }
133
- } catch {}
134
  }
135
 
136
  async function resolveEmbedToken(
@@ -142,14 +162,6 @@ async function resolveEmbedToken(
142
  return { token: stored, user: getStoredEmbedUser() ?? undefined };
143
  }
144
 
145
- const tokenParam = params.get('token');
146
-
147
- if (tokenParam) {
148
- storeEmbedSession(tokenParam);
149
-
150
- return { token: tokenParam };
151
- }
152
-
153
  const apiKey = params.get('api_key');
154
 
155
  if (apiKey) {
@@ -498,7 +510,7 @@ export default function Embed() {
498
  const rootStyle = {
499
  '--embed-bg': config.background,
500
  '--embed-border': config.border,
501
- '--embed-danger': config.logout,
502
  '--embed-muted': config.textSecondary,
503
  '--embed-primary': config.primary,
504
  '--embed-primary-soft': config.primaryGradient,
 
37
  backgroundProfile: string;
38
  border: string;
39
  courseId?: string | null;
40
+ danger: string;
41
  primary: string;
42
  primaryGradient: string;
43
  radius: string;
 
53
  background: '#ffffff',
54
  backgroundProfile: '#ffffff',
55
  border: '#739af1',
56
+ danger: '#ef4444',
57
  primary: '#2764eb',
58
  primaryGradient: '#ccdcff',
59
  textPrimary: '#0a0a0a',
 
94
  backgroundProfile: parseColor(params.get('background_profile'), COLOR_DEFAULTS.backgroundProfile),
95
  border: parseColor(params.get('border'), COLOR_DEFAULTS.border),
96
  courseId: params.get('course_id') ?? params.get('courseId') ?? undefined,
97
+ danger: parseColor(params.get('danger'), COLOR_DEFAULTS.danger),
98
  primary: parseColor(params.get('primary'), COLOR_DEFAULTS.primary),
99
  primaryGradient: parseColor(params.get('primary_gradient'), COLOR_DEFAULTS.primaryGradient),
100
  radius: parseRadius(params.get('radius')),
 
106
  };
107
  }
108
 
109
+ const COOKIE_MAX_AGE = 60 * 60 * 24 * 7; // 7 days
110
+
111
+ function setEmbedCookie(name: string, value: string): void {
112
+ const secure = window.location.protocol === 'https:';
113
+ const parts = [
114
+ `${name}=${encodeURIComponent(value)}`,
115
+ 'path=/',
116
+ `max-age=${COOKIE_MAX_AGE}`,
117
+ 'SameSite=None',
118
+ ];
119
+
120
+ if (secure) {
121
+ parts.push('Secure');
122
  }
123
+
124
+ document.cookie = parts.join('; ');
125
+ }
126
+
127
+ function getEmbedCookie(name: string): string | null {
128
+ const match = document.cookie.match(
129
+ new RegExp(`(?:^|;\\s*)${name}=([^;]*)`),
130
+ );
131
+
132
+ return match ? decodeURIComponent(match[1]) : null;
133
+ }
134
+
135
+ function getStoredEmbedToken(): string | null {
136
+ return getEmbedCookie(EMBED_TOKEN_KEY);
137
  }
138
 
139
  function getStoredEmbedUser(): AuthUser | null {
140
  try {
141
+ const raw = getEmbedCookie(EMBED_USER_KEY);
142
  return raw ? (JSON.parse(raw) as AuthUser) : null;
143
  } catch {
144
  return null;
 
146
  }
147
 
148
  function storeEmbedSession(token: string, user?: AuthUser): void {
149
+ setEmbedCookie(EMBED_TOKEN_KEY, token);
 
150
 
151
+ if (user) {
152
+ setEmbedCookie(EMBED_USER_KEY, JSON.stringify(user));
153
+ }
 
154
  }
155
 
156
  async function resolveEmbedToken(
 
162
  return { token: stored, user: getStoredEmbedUser() ?? undefined };
163
  }
164
 
 
 
 
 
 
 
 
 
165
  const apiKey = params.get('api_key');
166
 
167
  if (apiKey) {
 
510
  const rootStyle = {
511
  '--embed-bg': config.background,
512
  '--embed-border': config.border,
513
+ '--embed-danger': config.danger,
514
  '--embed-muted': config.textSecondary,
515
  '--embed-primary': config.primary,
516
  '--embed-primary-soft': config.primaryGradient,