fluxfiles 1.26.1

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/fluxfiles.d.ts ADDED
@@ -0,0 +1,77 @@
1
+ interface FluxFilesOpenOptions {
2
+ endpoint: string;
3
+ token: string;
4
+ disk?: string;
5
+ /** Available disks shown in sidebar. */
6
+ disks?: string[];
7
+ mode?: 'picker' | 'browser';
8
+ /** Allow multi-select; when true, onSelect receives array of FluxFile */
9
+ multiple?: boolean;
10
+ /** Locale code (e.g. "en", "vi", "ar"). Default: "en". */
11
+ locale?: string;
12
+ /** Theme: "light", "dark", or "auto". */
13
+ theme?: string;
14
+ allowedTypes?: string[];
15
+ maxSize?: number;
16
+ container?: string;
17
+ onSelect?: (file: FluxFile | FluxFile[]) => void;
18
+ onClose?: () => void;
19
+ /** Called when the iframe needs a fresh JWT (401 received). Return new token or null. */
20
+ onTokenRefresh?: (context: { reason: string; disk?: string; path?: string }) => Promise<string | null>;
21
+ }
22
+
23
+ interface FluxFile {
24
+ path: string;
25
+ basename: string;
26
+ key?: string;
27
+ name?: string;
28
+ url?: string;
29
+ size?: number;
30
+ mime?: string;
31
+ disk?: string;
32
+ is_dir?: boolean;
33
+ modified?: string;
34
+ meta?: {
35
+ title?: string | null;
36
+ alt_text?: string | null;
37
+ caption?: string | null;
38
+ tags?: string | null;
39
+ } | null;
40
+ /** Image variant URLs (thumb/medium/large WebP). */
41
+ variants?: Record<string, { url: string; key: string }> | null;
42
+ /** Which variant was selected ("original", "thumb", "medium", "large"). */
43
+ variant?: 'original' | 'thumb' | 'medium' | 'large';
44
+ }
45
+
46
+ interface FluxEvent {
47
+ action: 'upload' | 'delete' | 'move' | 'copy' | 'mkdir' | 'restore' | 'purge' | 'trash' | 'crop' | 'ai_tag';
48
+ disk: string;
49
+ path: string;
50
+ [key: string]: unknown;
51
+ }
52
+
53
+ type FluxFilesEventType = 'FM_READY' | 'FM_SELECT' | 'FM_EVENT' | 'FM_CLOSE' | 'FM_TOKEN_REFRESH';
54
+
55
+ interface FluxFilesSDK {
56
+ open(options: FluxFilesOpenOptions): void;
57
+ close(): void;
58
+ command(action: string, data?: Record<string, unknown>): void;
59
+ navigate(path: string): void;
60
+ setDisk(disk: string): void;
61
+ refresh(): void;
62
+ search(query: string): void;
63
+ crossCopy(dstDisk: string, dstPath?: string): void;
64
+ crossMove(dstDisk: string, dstPath?: string): void;
65
+ aiTag(): void;
66
+ /** Switch locale/language at runtime. */
67
+ setLocale(locale: string): void;
68
+ /** Push a new token (e.g. after background refresh). */
69
+ updateToken(token: string): void;
70
+ on(event: FluxFilesEventType, callback: (data: unknown) => void): () => void;
71
+ off(event: FluxFilesEventType, callback: (data: unknown) => void): void;
72
+ }
73
+
74
+ declare const FluxFiles: FluxFilesSDK;
75
+
76
+ export = FluxFiles;
77
+ export as namespace FluxFiles;
package/fluxfiles.js ADDED
@@ -0,0 +1,231 @@
1
+ (function(root) {
2
+ 'use strict';
3
+
4
+ var VERSION = 1;
5
+ var SOURCE = 'fluxfiles';
6
+ var iframe = null;
7
+ var listeners = {};
8
+ var config = {};
9
+ var ready = false;
10
+ var iframeOrigin = '';
11
+ var _tokenRefreshing = false;
12
+
13
+ function uuid() {
14
+ return 'ff-' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
15
+ }
16
+
17
+ function postToIframe(type, payload) {
18
+ if (!iframe || !iframe.contentWindow) return;
19
+ iframe.contentWindow.postMessage({
20
+ source: SOURCE,
21
+ type: type,
22
+ v: VERSION,
23
+ id: uuid(),
24
+ payload: payload || {}
25
+ }, iframeOrigin || '*');
26
+ }
27
+
28
+ function handleMessage(e) {
29
+ if (iframeOrigin && e.origin !== iframeOrigin) return;
30
+ var msg = e.data;
31
+ if (!msg || msg.source !== SOURCE) return;
32
+
33
+ switch (msg.type) {
34
+ case 'FM_READY':
35
+ ready = true;
36
+ postToIframe('FM_CONFIG', {
37
+ disk: config.disk || 'local',
38
+ disks: config.disks || ['local'],
39
+ token: config.token || '',
40
+ path: config.path || '',
41
+ mode: config.mode || 'picker',
42
+ multiple: !!config.multiple,
43
+ allowedTypes: config.allowedTypes || null,
44
+ maxSize: config.maxSize || null,
45
+ endpoint: config.endpoint || '',
46
+ locale: config.locale || null,
47
+ theme: config.theme || null
48
+ });
49
+ emit('FM_READY', msg.payload);
50
+ break;
51
+
52
+ case 'FM_SELECT':
53
+ if (typeof config.onSelect === 'function') {
54
+ config.onSelect(msg.payload);
55
+ }
56
+ emit('FM_SELECT', msg.payload);
57
+ FluxFiles.close();
58
+ break;
59
+
60
+ case 'FM_TOKEN_REFRESH':
61
+ // Iframe is requesting a new token (401 received)
62
+ if (_tokenRefreshing) break; // prevent concurrent refreshes
63
+ _tokenRefreshing = true;
64
+ emit('FM_TOKEN_REFRESH', msg.payload);
65
+
66
+ if (typeof config.onTokenRefresh === 'function') {
67
+ // onTokenRefresh must return a Promise<string> (new JWT) or Promise<null> (failed)
68
+ Promise.resolve()
69
+ .then(function() { return config.onTokenRefresh(msg.payload); })
70
+ .then(function(newToken) {
71
+ _tokenRefreshing = false;
72
+ if (newToken) {
73
+ config.token = newToken;
74
+ postToIframe('FM_TOKEN_UPDATED', { token: newToken });
75
+ } else {
76
+ postToIframe('FM_TOKEN_FAILED', { reason: 'refresh_returned_null' });
77
+ }
78
+ })
79
+ .catch(function(err) {
80
+ _tokenRefreshing = false;
81
+ console.error('FluxFiles: onTokenRefresh failed', err);
82
+ postToIframe('FM_TOKEN_FAILED', { reason: err.message || 'refresh_error' });
83
+ });
84
+ } else {
85
+ // No handler — notify iframe that refresh is not supported
86
+ _tokenRefreshing = false;
87
+ postToIframe('FM_TOKEN_FAILED', { reason: 'no_handler' });
88
+ }
89
+ break;
90
+
91
+ case 'FM_EVENT':
92
+ emit('FM_EVENT', msg.payload);
93
+ break;
94
+
95
+ case 'FM_CLOSE':
96
+ if (typeof config.onClose === 'function') {
97
+ config.onClose();
98
+ }
99
+ emit('FM_CLOSE', msg.payload);
100
+ break;
101
+ }
102
+ }
103
+
104
+ function emit(type, data) {
105
+ var cbs = listeners[type] || [];
106
+ for (var i = 0; i < cbs.length; i++) {
107
+ try { cbs[i](data); } catch(ex) { console.error('FluxFiles listener error:', ex); }
108
+ }
109
+ }
110
+
111
+ var FluxFiles = {
112
+ open: function(options) {
113
+ config = options || {};
114
+ var endpoint = (config.endpoint || '').replace(/\/+$/, '');
115
+ var container = config.container
116
+ ? document.querySelector(config.container)
117
+ : document.body;
118
+
119
+ // Derive origin from endpoint for postMessage validation
120
+ try {
121
+ var u = new URL(endpoint + '/public/index.html');
122
+ iframeOrigin = u.origin;
123
+ } catch (_) {
124
+ iframeOrigin = window.location.origin;
125
+ }
126
+
127
+ // Clean up existing
128
+ this.close();
129
+
130
+ // Create iframe
131
+ iframe = document.createElement('iframe');
132
+ iframe.id = 'fluxfiles-iframe';
133
+ iframe.src = endpoint + '/public/index.html';
134
+ iframe.style.cssText = 'width:100%;height:100%;border:none;';
135
+ iframe.setAttribute('allow', 'clipboard-write');
136
+
137
+ if (!config.container) {
138
+ // Modal overlay — UI scaled down 5% (icons, text, spacing)
139
+ var overlay = document.createElement('div');
140
+ overlay.id = 'fluxfiles-overlay';
141
+ overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:99999;display:flex;align-items:center;justify-content:center;';
142
+
143
+ var modal = document.createElement('div');
144
+ modal.id = 'fluxfiles-modal';
145
+ modal.style.cssText = 'width:90vw;height:85vh;max-width:1200px;background:#f5f5f7;border-radius:10px;overflow:hidden;box-shadow:0 25px 50px rgba(0,0,0,0.22);display:flex;flex-direction:column;';
146
+
147
+ // Header — macOS-style red close button (left) with × icon
148
+ var header = document.createElement('div');
149
+ header.style.cssText = 'flex-shrink:0;display:flex;align-items:center;justify-content:flex-start;padding:10px 12px;border-bottom:1px solid rgba(0,0,0,0.06);background:#f5f5f7;';
150
+ var closeBtn = document.createElement('button');
151
+ closeBtn.type = 'button';
152
+ closeBtn.setAttribute('aria-label', 'Close');
153
+ closeBtn.style.cssText = 'width:28px;height:28px;border:none;border-radius:6px;background:transparent;color:#6b7280;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:20px;line-height:1;font-weight:300;transition:color .15s,background .15s;flex-shrink:0;';
154
+ closeBtn.textContent = '\u00D7';
155
+ closeBtn.addEventListener('mouseenter', function() { closeBtn.style.background = '#e5e7eb'; closeBtn.style.color = '#374151'; });
156
+ closeBtn.addEventListener('mouseleave', function() { closeBtn.style.background = 'transparent'; closeBtn.style.color = '#6b7280'; });
157
+ closeBtn.addEventListener('click', function() { FluxFiles.close(); });
158
+ header.appendChild(closeBtn);
159
+ modal.appendChild(header);
160
+
161
+ var body = document.createElement('div');
162
+ body.style.cssText = 'flex:1;min-height:0;display:flex;flex-direction:column;';
163
+ body.appendChild(iframe);
164
+ modal.appendChild(body);
165
+ overlay.appendChild(modal);
166
+ document.body.appendChild(overlay);
167
+
168
+ overlay.addEventListener('click', function(e) {
169
+ if (e.target === overlay) FluxFiles.close();
170
+ });
171
+ } else {
172
+ container.appendChild(iframe);
173
+ }
174
+
175
+ window.addEventListener('message', handleMessage);
176
+ },
177
+
178
+ close: function() {
179
+ ready = false;
180
+ window.removeEventListener('message', handleMessage);
181
+
182
+ var overlay = document.getElementById('fluxfiles-overlay');
183
+ if (overlay) overlay.remove();
184
+
185
+ var existing = document.getElementById('fluxfiles-iframe');
186
+ if (existing) existing.remove();
187
+
188
+ iframe = null;
189
+ },
190
+
191
+ command: function(action, data) {
192
+ postToIframe('FM_COMMAND', Object.assign({ action: action }, data || {}));
193
+ },
194
+
195
+ navigate: function(path) { this.command('navigate', { path: path }); },
196
+ setDisk: function(disk) { this.command('setDisk', { disk: disk }); },
197
+ refresh: function() { this.command('refresh'); },
198
+ search: function(q) { this.command('search', { q: q }); },
199
+ crossCopy: function(dstDisk, dstPath) { this.command('crossCopy', { dst_disk: dstDisk, dst_path: dstPath || '' }); },
200
+ crossMove: function(dstDisk, dstPath) { this.command('crossMove', { dst_disk: dstDisk, dst_path: dstPath || '' }); },
201
+ aiTag: function() { this.command('aiTag'); },
202
+ setLocale: function(locale) { this.command('setLocale', { locale: locale }); },
203
+
204
+ updateToken: function(newToken) {
205
+ config.token = newToken;
206
+ if (ready && iframe) {
207
+ postToIframe('FM_TOKEN_UPDATED', { token: newToken });
208
+ }
209
+ },
210
+
211
+ on: function(type, cb) {
212
+ if (!listeners[type]) listeners[type] = [];
213
+ listeners[type].push(cb);
214
+ return function() { FluxFiles.off(type, cb); };
215
+ },
216
+
217
+ off: function(type, cb) {
218
+ if (!listeners[type]) return;
219
+ listeners[type] = listeners[type].filter(function(fn) { return fn !== cb; });
220
+ }
221
+ };
222
+
223
+ // UMD export
224
+ if (typeof module !== 'undefined' && module.exports) {
225
+ module.exports = FluxFiles;
226
+ } else if (typeof define === 'function' && define.amd) {
227
+ define(function() { return FluxFiles; });
228
+ } else {
229
+ root.FluxFiles = FluxFiles;
230
+ }
231
+ })(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this);
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "fluxfiles",
3
+ "version": "1.26.1",
4
+ "description": "FluxFiles JavaScript SDK — embed the file manager in any web app",
5
+ "license": "MIT",
6
+ "main": "fluxfiles.js",
7
+ "types": "fluxfiles.d.ts",
8
+ "files": [
9
+ "fluxfiles.js",
10
+ "fluxfiles.d.ts"
11
+ ],
12
+ "author": "thai-pc",
13
+ "homepage": "https://github.com/thai-pc/fluxfiles",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/thai-pc/fluxfiles.git"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/thai-pc/fluxfiles/issues"
20
+ },
21
+ "keywords": [
22
+ "fluxfiles",
23
+ "file-manager",
24
+ "sdk",
25
+ "iframe",
26
+ "s3",
27
+ "r2",
28
+ "upload",
29
+ "media"
30
+ ]
31
+ }