@makroz/web 1.2.5 → 1.2.6
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 +1 -1
- package/bin/mk-update.js +245 -41
- package/dist/auth/AuthStorageAdapter.d.ts +74 -0
- package/dist/auth/AuthStorageAdapter.d.ts.map +1 -0
- package/dist/auth/AuthStorageAdapter.js +140 -0
- package/dist/auth/AuthStorageAdapter.js.map +1 -0
- package/dist/auth/MkAuthProvider.d.ts +11 -2
- package/dist/auth/MkAuthProvider.d.ts.map +1 -1
- package/dist/auth/MkAuthProvider.js +15 -25
- package/dist/auth/MkAuthProvider.js.map +1 -1
- package/dist/components/MkFileUpload.d.ts.map +1 -1
- package/dist/components/MkFileUpload.js +36 -2
- package/dist/components/MkFileUpload.js.map +1 -1
- package/dist/components/MkTable.d.ts +8 -1
- package/dist/components/MkTable.d.ts.map +1 -1
- package/dist/components/MkTable.js +28 -2
- package/dist/components/MkTable.js.map +1 -1
- package/dist/hooks/useApi.d.ts +21 -5
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/hooks/useApi.js +83 -63
- package/dist/hooks/useApi.js.map +1 -1
- package/dist/hooks/useMkInfiniteList.d.ts +1 -1
- package/dist/hooks/useMkList.d.ts +8 -12
- package/dist/hooks/useMkList.d.ts.map +1 -1
- package/dist/hooks/useMkList.js +23 -9
- package/dist/hooks/useMkList.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/theme/MkThemeProvider.d.ts.map +1 -1
- package/dist/theme/MkThemeProvider.js.map +1 -1
- package/eslint-plugin-mk/__tests__/no-cross-module-import-dynamic.test.js +89 -0
- package/eslint-plugin-mk/rules/no-cross-module-import.js +16 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Librería de componentes UI para proyectos Web (React). Enfocada en performance
|
|
|
5
5
|
## Características Core
|
|
6
6
|
- **Native CSS Theming**: Todo el look se controla via variables CSS (`--mk-primary`, etc.). Sin Tailwind.
|
|
7
7
|
- **Micro-Animations**: Transiciones fluidas y estados activos integrados.
|
|
8
|
-
- **useApi Hook**: Comunicación estandarizada con backend MK-Director, manejando loading/error/success automáticamente.
|
|
8
|
+
- **useApi Hook**: Comunicación estandarizada con backend MK-Director, manejando loading/error/success automáticamente. AbortController wired up on unmount and param change, `response.ok` checked before parsing JSON, default 30s timeout. Returned by `useApi()` directly so consumer call sites do not need to change.
|
|
9
9
|
|
|
10
10
|
## Instalación & Tematización
|
|
11
11
|
Importa los estilos en tu root:
|
package/bin/mk-update.js
CHANGED
|
@@ -57,10 +57,125 @@ function getLatestRegistryVersion(packageName) {
|
|
|
57
57
|
return 'unknown';
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function getRegistryVersions(packageName) {
|
|
61
|
+
try {
|
|
62
|
+
const stdout = execSync(`npm view ${packageName} versions --json`, {
|
|
63
|
+
encoding: 'utf8',
|
|
64
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
65
|
+
timeout: 5000,
|
|
66
|
+
});
|
|
67
|
+
if (stdout.trim()) {
|
|
68
|
+
const parsed = JSON.parse(stdout.trim());
|
|
69
|
+
return Array.isArray(parsed) ? parsed : [parsed];
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
try {
|
|
73
|
+
const stdout = execSync(`npm view ${packageName} versions`, {
|
|
74
|
+
encoding: 'utf8',
|
|
75
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
76
|
+
timeout: 5000,
|
|
77
|
+
});
|
|
78
|
+
if (stdout.trim()) {
|
|
79
|
+
return stdout.replace(/[\[\]']/g, '').split(',').map(v => v.trim()).filter(Boolean);
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// silent fallback
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseVersion(v) {
|
|
89
|
+
const clean = v.replace(/^v/, '');
|
|
90
|
+
const [main, prerelease] = clean.split('-');
|
|
91
|
+
const [major, minor, patch] = main.split('.').map(Number);
|
|
92
|
+
return {
|
|
93
|
+
major: isNaN(major) ? 0 : major,
|
|
94
|
+
minor: isNaN(minor) ? 0 : minor,
|
|
95
|
+
patch: isNaN(patch) ? 0 : patch,
|
|
96
|
+
prerelease: prerelease || null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function compareVersions(v1, v2) {
|
|
101
|
+
const p1 = parseVersion(v1);
|
|
102
|
+
const p2 = parseVersion(v2);
|
|
103
|
+
|
|
104
|
+
if (p1.major !== p2.major) return p1.major - p2.major;
|
|
105
|
+
if (p1.minor !== p2.minor) return p1.minor - p2.minor;
|
|
106
|
+
if (p1.patch !== p2.patch) return p1.patch - p2.patch;
|
|
107
|
+
|
|
108
|
+
if (p1.prerelease && !p2.prerelease) return -1;
|
|
109
|
+
if (!p1.prerelease && p2.prerelease) return 1;
|
|
110
|
+
|
|
111
|
+
if (p1.prerelease && p2.prerelease) {
|
|
112
|
+
const regex = /^([a-zA-Z.-]+)(\d*)$/;
|
|
113
|
+
const m1 = p1.prerelease.match(regex);
|
|
114
|
+
const m2 = p2.prerelease.match(regex);
|
|
115
|
+
|
|
116
|
+
if (m1 && m2) {
|
|
117
|
+
const label1 = m1[1];
|
|
118
|
+
const label2 = m2[1];
|
|
119
|
+
if (label1 !== label2) {
|
|
120
|
+
return label1.localeCompare(label2);
|
|
121
|
+
}
|
|
122
|
+
const num1 = m1[2] ? parseInt(m1[2], 10) : 0;
|
|
123
|
+
const num2 = m2[2] ? parseInt(m2[2], 10) : 0;
|
|
124
|
+
return num1 - num2;
|
|
125
|
+
}
|
|
126
|
+
return p1.prerelease.localeCompare(p2.prerelease);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function performUpdate(updates, packageManager) {
|
|
133
|
+
const pkgJsonPath = path.join(process.cwd(), 'package.json');
|
|
134
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
|
135
|
+
|
|
136
|
+
const dependencies = pkg.dependencies || {};
|
|
137
|
+
const devDependencies = pkg.devDependencies || {};
|
|
138
|
+
|
|
139
|
+
updates.forEach(upd => {
|
|
140
|
+
const newSpec = upd.isLocal ? `file:../../mk-director/packages/${upd.name.replace('@makroz/', 'mk-')}` : `^${upd.version}`;
|
|
141
|
+
if (dependencies[upd.name]) {
|
|
142
|
+
dependencies[upd.name] = newSpec;
|
|
143
|
+
} else if (devDependencies[upd.name]) {
|
|
144
|
+
devDependencies[upd.name] = newSpec;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
149
|
+
console.log('\n✅ package.json actualizado.');
|
|
150
|
+
|
|
151
|
+
let command = '';
|
|
152
|
+
if (packageManager === 'pnpm') {
|
|
153
|
+
command = 'pnpm install';
|
|
154
|
+
} else if (packageManager === 'yarn') {
|
|
155
|
+
command = 'yarn install';
|
|
156
|
+
} else {
|
|
157
|
+
command = 'npm install';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(`\nEjecutando: ${command}...\n`);
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
execSync(command, { stdio: 'inherit' });
|
|
164
|
+
console.log('\n🏁 Actualización finalizada con éxito.\n');
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.error(`\n❌ Error al ejecutar la actualización: ${e.message}\n`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
60
171
|
async function run() {
|
|
61
172
|
const args = process.argv.slice(2);
|
|
62
173
|
const toLocal = args.includes('--local') || args.includes('--dev');
|
|
63
174
|
const toProd = args.includes('--prod') || args.includes('--registry') || args.includes('--remote');
|
|
175
|
+
const dryRun = args.includes('--dry-run');
|
|
176
|
+
|
|
177
|
+
const cleanArgs = args.filter(arg => !['--local', '--dev', '--prod', '--registry', '--remote', '--dry-run'].includes(arg));
|
|
178
|
+
const targetVersionArg = cleanArgs.find(arg => !arg.startsWith('-'));
|
|
64
179
|
|
|
65
180
|
const pkgJsonPath = path.join(process.cwd(), 'package.json');
|
|
66
181
|
if (!fs.existsSync(pkgJsonPath)) {
|
|
@@ -117,6 +232,10 @@ async function run() {
|
|
|
117
232
|
updateSection(devDependencies);
|
|
118
233
|
|
|
119
234
|
if (modified) {
|
|
235
|
+
if (dryRun) {
|
|
236
|
+
console.log('\n[Simulación] Se omitió la escritura en package.json y la ejecución del gestor de paquetes.');
|
|
237
|
+
process.exit(0);
|
|
238
|
+
}
|
|
120
239
|
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
|
|
121
240
|
console.log('\n✅ package.json actualizado correctamente.');
|
|
122
241
|
const packageManager = detectPackageManager();
|
|
@@ -159,37 +278,44 @@ async function run() {
|
|
|
159
278
|
const isLocal = spec.startsWith('file:');
|
|
160
279
|
const installed = getInstalledVersion(name);
|
|
161
280
|
|
|
162
|
-
let
|
|
281
|
+
let versions = [];
|
|
282
|
+
let latestStable = 'unknown';
|
|
283
|
+
|
|
163
284
|
if (isLocal) {
|
|
164
|
-
|
|
285
|
+
const localVersion = getLatestLocalVersion(spec);
|
|
286
|
+
if (localVersion !== 'unknown') {
|
|
287
|
+
versions = [localVersion];
|
|
288
|
+
}
|
|
165
289
|
} else {
|
|
166
|
-
|
|
290
|
+
versions = getRegistryVersions(name);
|
|
291
|
+
latestStable = getLatestRegistryVersion(name);
|
|
167
292
|
}
|
|
168
293
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
needsUpdate = true;
|
|
294
|
+
let higherVersions = [];
|
|
295
|
+
if (installed !== 'unknown') {
|
|
296
|
+
higherVersions = versions.filter(v => compareVersions(v, installed) > 0);
|
|
297
|
+
} else {
|
|
298
|
+
higherVersions = versions;
|
|
175
299
|
}
|
|
176
300
|
|
|
301
|
+
higherVersions.sort((a, b) => compareVersions(b, a));
|
|
302
|
+
|
|
177
303
|
packageInfos.push({
|
|
178
304
|
name,
|
|
179
305
|
currentSpec: spec,
|
|
180
306
|
installedVersion: installed,
|
|
181
|
-
|
|
307
|
+
higherVersions,
|
|
308
|
+
latestStable,
|
|
182
309
|
isLocal,
|
|
183
|
-
needsUpdate,
|
|
310
|
+
needsUpdate: higherVersions.length > 0,
|
|
184
311
|
});
|
|
185
312
|
}
|
|
186
313
|
|
|
187
|
-
// Print summary of packages
|
|
188
314
|
console.log('Estado de los paquetes:');
|
|
189
315
|
for (const info of packageInfos) {
|
|
190
316
|
const typeLabel = info.isLocal ? '[Local]' : '[Registry]';
|
|
191
317
|
if (info.needsUpdate) {
|
|
192
|
-
console.log(` ⚠️ ${info.name} ${typeLabel}: ${info.installedVersion} -> ${info.
|
|
318
|
+
console.log(` ⚠️ ${info.name} ${typeLabel}: ${info.installedVersion} -> ${info.higherVersions[0]} (Actualizaciones disponibles: ${info.higherVersions.length})`);
|
|
193
319
|
} else {
|
|
194
320
|
console.log(` ✅ ${info.name} ${typeLabel}: ${info.installedVersion} (Al día)`);
|
|
195
321
|
}
|
|
@@ -205,45 +331,123 @@ async function run() {
|
|
|
205
331
|
const packageManager = detectPackageManager();
|
|
206
332
|
console.log(`\nGestor de paquetes detectado: ${packageManager}`);
|
|
207
333
|
|
|
334
|
+
if (targetVersionArg) {
|
|
335
|
+
const targetVersionClean = targetVersionArg.replace(/^v/, '');
|
|
336
|
+
console.log(`\n📌 Versión especificada por argumento: v${targetVersionClean}`);
|
|
337
|
+
|
|
338
|
+
if (dryRun) {
|
|
339
|
+
console.log(`[Simulación] Se actualizarían los siguientes paquetes a v${targetVersionClean}:`);
|
|
340
|
+
updatable.forEach(info => console.log(` - ${info.name}`));
|
|
341
|
+
process.exit(0);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const rl = readline.createInterface({
|
|
345
|
+
input: process.stdin,
|
|
346
|
+
output: process.stdout,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
rl.question(`\n¿Confirmás la actualización a v${targetVersionClean}? (Y/n): `, (answer) => {
|
|
350
|
+
rl.close();
|
|
351
|
+
if (answer.trim().toLowerCase() === 'n') {
|
|
352
|
+
console.log('Actualización cancelada.');
|
|
353
|
+
process.exit(0);
|
|
354
|
+
}
|
|
355
|
+
performUpdate(updatable.map(info => ({ name: info.name, version: targetVersionClean, isLocal: info.isLocal })), packageManager);
|
|
356
|
+
});
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
208
360
|
const rl = readline.createInterface({
|
|
209
361
|
input: process.stdin,
|
|
210
362
|
output: process.stdout,
|
|
211
363
|
});
|
|
212
364
|
|
|
213
|
-
|
|
214
|
-
rl.close();
|
|
215
|
-
const confirmed = answer.trim().toLowerCase() !== 'n';
|
|
365
|
+
const selectedUpdates = [];
|
|
216
366
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
367
|
+
async function askPackageUpdate(index) {
|
|
368
|
+
if (index >= updatable.length) {
|
|
369
|
+
rl.close();
|
|
370
|
+
if (selectedUpdates.length === 0) {
|
|
371
|
+
console.log('No se seleccionó ninguna actualización.');
|
|
372
|
+
process.exit(0);
|
|
373
|
+
}
|
|
221
374
|
|
|
222
|
-
|
|
223
|
-
|
|
375
|
+
console.log('\nSelección de actualizaciones:');
|
|
376
|
+
selectedUpdates.forEach(upd => {
|
|
377
|
+
console.log(` - ${upd.name} -> v${upd.version}`);
|
|
378
|
+
});
|
|
224
379
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
} else {
|
|
230
|
-
// npm
|
|
231
|
-
const args = updatable.map(info => {
|
|
232
|
-
return info.isLocal ? info.name : `${info.name}@latest`;
|
|
233
|
-
}).join(' ');
|
|
234
|
-
command = `npm install ${args}`;
|
|
235
|
-
}
|
|
380
|
+
if (dryRun) {
|
|
381
|
+
console.log('\n[Simulación] Se omitirá la descarga e instalación.');
|
|
382
|
+
process.exit(0);
|
|
383
|
+
}
|
|
236
384
|
|
|
237
|
-
|
|
385
|
+
const confirmRl = readline.createInterface({
|
|
386
|
+
input: process.stdin,
|
|
387
|
+
output: process.stdout,
|
|
388
|
+
});
|
|
238
389
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
390
|
+
confirmRl.question('\n¿Confirmás la actualización? (Y/n): ', (answer) => {
|
|
391
|
+
confirmRl.close();
|
|
392
|
+
if (answer.trim().toLowerCase() === 'n') {
|
|
393
|
+
console.log('Actualización cancelada.');
|
|
394
|
+
process.exit(0);
|
|
395
|
+
}
|
|
396
|
+
performUpdate(selectedUpdates, packageManager);
|
|
397
|
+
});
|
|
398
|
+
return;
|
|
245
399
|
}
|
|
246
|
-
|
|
400
|
+
|
|
401
|
+
const info = updatable[index];
|
|
402
|
+
console.log(`\n----------------------------------------`);
|
|
403
|
+
console.log(`¿A qué versión querés actualizar ${info.name}? (Actual: ${info.installedVersion})`);
|
|
404
|
+
|
|
405
|
+
const choices = info.higherVersions.map((version, i) => {
|
|
406
|
+
const isLatestStable = version === info.latestStable;
|
|
407
|
+
const isRc = /rc|alpha|beta/i.test(version);
|
|
408
|
+
const marker = isLatestStable ? ' ⭐ (última estable)' : (isRc ? ' 🧪 (pre-release)' : '');
|
|
409
|
+
return {
|
|
410
|
+
text: `v${version}${marker}`,
|
|
411
|
+
version,
|
|
412
|
+
};
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
choices.forEach((choice, i) => {
|
|
416
|
+
console.log(` [${i + 1}] ${choice.text}`);
|
|
417
|
+
});
|
|
418
|
+
console.log(` [s] Omitir este paquete`);
|
|
419
|
+
|
|
420
|
+
rl.question(`\nIngresá el número de opción (1-${choices.length}) o 's' para omitir [default 1]: `, (ans) => {
|
|
421
|
+
const cleanAns = ans.trim();
|
|
422
|
+
if (cleanAns.toLowerCase() === 's') {
|
|
423
|
+
console.log(`Omitiendo ${info.name}.`);
|
|
424
|
+
askPackageUpdate(index + 1);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
let choiceIndex = 0;
|
|
429
|
+
if (cleanAns !== '') {
|
|
430
|
+
const parsedAns = parseInt(cleanAns, 10);
|
|
431
|
+
if (isNaN(parsedAns) || parsedAns < 1 || parsedAns > choices.length) {
|
|
432
|
+
console.log('Opción inválida. Reintentando...');
|
|
433
|
+
askPackageUpdate(index);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
choiceIndex = parsedAns - 1;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const selectedVersion = choices[choiceIndex].version;
|
|
440
|
+
selectedUpdates.push({
|
|
441
|
+
name: info.name,
|
|
442
|
+
version: selectedVersion,
|
|
443
|
+
isLocal: info.isLocal,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
askPackageUpdate(index + 1);
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
askPackageUpdate(0);
|
|
247
451
|
}
|
|
248
452
|
|
|
249
453
|
run();
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage adapters for `<MkAuthProvider>`.
|
|
3
|
+
*
|
|
4
|
+
* `AuthStorageAdapter` is the interface; `CookieStorageAdapter` is the
|
|
5
|
+
* default implementation; `LocalStorageAdapter` is an opt-in escape hatch
|
|
6
|
+
* kept for one release cycle and marked `@deprecated`.
|
|
7
|
+
*
|
|
8
|
+
* ## Why a Cookie default?
|
|
9
|
+
*
|
|
10
|
+
* Mobile (`@makroz/mobile`) already stores tokens in `expo-secure-store`
|
|
11
|
+
* (device keychain / Android Keystore). That is the secure pattern for
|
|
12
|
+
* tokens. Web never had an equivalent — the previous default wrote
|
|
13
|
+
* `access_token` and `refresh_token` into `localStorage`, where any
|
|
14
|
+
* XSS payload could exfiltrate them and where they survive tab close
|
|
15
|
+
* (extending the attack window). Switching the default to cookies
|
|
16
|
+
* aligns web with the mobile pattern; the migration story is in the
|
|
17
|
+
* package CHANGELOG.
|
|
18
|
+
*
|
|
19
|
+
* ## SSR safety
|
|
20
|
+
*
|
|
21
|
+
* Both adapters are no-ops when `document` / `window` are unavailable
|
|
22
|
+
* (server-side rendering). The provider should be hydrated client-side
|
|
23
|
+
* to pick up tokens — that has always been the case.
|
|
24
|
+
*
|
|
25
|
+
* ## Future GA work
|
|
26
|
+
*
|
|
27
|
+
* When the consumer migrates to `makroz/director-laravel` Sanctum v4
|
|
28
|
+
* cookie mode, the cookies will become `httpOnly` and the JS adapter
|
|
29
|
+
* will only manage a non-secret CSRF/read mirror. That sprint is
|
|
30
|
+
* out of scope here; this file only ships the adapter surface.
|
|
31
|
+
*/
|
|
32
|
+
import type { AuthStorageAdapter } from './types';
|
|
33
|
+
export interface CookieStorageAdapterOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Emit the `Secure` flag. Set `true` in production over HTTPS.
|
|
36
|
+
* Defaults to `false` (so the adapter works in `http://localhost`
|
|
37
|
+
* during development without surprises).
|
|
38
|
+
*/
|
|
39
|
+
secure?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Default storage adapter. Reads/writes browser cookies via
|
|
43
|
+
* `document.cookie`. Use this unless you have a specific reason to
|
|
44
|
+
* pin tokens in JavaScript-managed storage (and you almost certainly
|
|
45
|
+
* don't — that's the XSS risk this adapter exists to retire).
|
|
46
|
+
*/
|
|
47
|
+
export declare class CookieStorageAdapter implements AuthStorageAdapter {
|
|
48
|
+
private readonly secure;
|
|
49
|
+
constructor(opts?: CookieStorageAdapterOptions);
|
|
50
|
+
getItem(key: string): string | null;
|
|
51
|
+
setItem(key: string, value: string): void;
|
|
52
|
+
removeItem(key: string): void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* @deprecated Use `CookieStorageAdapter` instead. Persists tokens in
|
|
56
|
+
* `localStorage`, where any XSS payload can read them and where they
|
|
57
|
+
* survive tab close — the opposite of what mobile's
|
|
58
|
+
* `expo-secure-store` does. Scheduled for removal in v1.4.x. A
|
|
59
|
+
* one-shot `console.warn` is emitted on the first `setItem` to make
|
|
60
|
+
* accidental usage greppable.
|
|
61
|
+
*/
|
|
62
|
+
export declare class LocalStorageAdapter implements AuthStorageAdapter {
|
|
63
|
+
getItem(key: string): string | null;
|
|
64
|
+
setItem(key: string, value: string): void;
|
|
65
|
+
removeItem(key: string): void;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Factory for the default storage used by `<MkAuthProvider>` when no
|
|
69
|
+
* `storage` prop is passed. Always returns a fresh `CookieStorageAdapter`
|
|
70
|
+
* so multiple providers in the same tree (different scopes) get their
|
|
71
|
+
* own adapter instances — cookies themselves are scoped by key name.
|
|
72
|
+
*/
|
|
73
|
+
export declare function getDefaultAuthStorage(): AuthStorageAdapter;
|
|
74
|
+
//# sourceMappingURL=AuthStorageAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthStorageAdapter.d.ts","sourceRoot":"","sources":["../../src/auth/AuthStorageAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAKlD,MAAM,WAAW,2BAA2B;IACxC;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;GAKG;AACH,qBAAa,oBAAqB,YAAW,kBAAkB;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;gBAErB,IAAI,GAAE,2BAAgC;IAIlD,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAkBnC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAOzC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAKhC;AAID;;;;;;;GAOG;AACH,qBAAa,mBAAoB,YAAW,kBAAkB;IAC1D,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IASnC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAUzC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAQhC;AAaD;;;;;GAKG;AACH,wBAAgB,qBAAqB,IAAI,kBAAkB,CAE1D"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage adapters for `<MkAuthProvider>`.
|
|
3
|
+
*
|
|
4
|
+
* `AuthStorageAdapter` is the interface; `CookieStorageAdapter` is the
|
|
5
|
+
* default implementation; `LocalStorageAdapter` is an opt-in escape hatch
|
|
6
|
+
* kept for one release cycle and marked `@deprecated`.
|
|
7
|
+
*
|
|
8
|
+
* ## Why a Cookie default?
|
|
9
|
+
*
|
|
10
|
+
* Mobile (`@makroz/mobile`) already stores tokens in `expo-secure-store`
|
|
11
|
+
* (device keychain / Android Keystore). That is the secure pattern for
|
|
12
|
+
* tokens. Web never had an equivalent — the previous default wrote
|
|
13
|
+
* `access_token` and `refresh_token` into `localStorage`, where any
|
|
14
|
+
* XSS payload could exfiltrate them and where they survive tab close
|
|
15
|
+
* (extending the attack window). Switching the default to cookies
|
|
16
|
+
* aligns web with the mobile pattern; the migration story is in the
|
|
17
|
+
* package CHANGELOG.
|
|
18
|
+
*
|
|
19
|
+
* ## SSR safety
|
|
20
|
+
*
|
|
21
|
+
* Both adapters are no-ops when `document` / `window` are unavailable
|
|
22
|
+
* (server-side rendering). The provider should be hydrated client-side
|
|
23
|
+
* to pick up tokens — that has always been the case.
|
|
24
|
+
*
|
|
25
|
+
* ## Future GA work
|
|
26
|
+
*
|
|
27
|
+
* When the consumer migrates to `makroz/director-laravel` Sanctum v4
|
|
28
|
+
* cookie mode, the cookies will become `httpOnly` and the JS adapter
|
|
29
|
+
* will only manage a non-secret CSRF/read mirror. That sprint is
|
|
30
|
+
* out of scope here; this file only ships the adapter surface.
|
|
31
|
+
*/
|
|
32
|
+
const COOKIE_DEFAULTS = 'Path=/; SameSite=Lax';
|
|
33
|
+
const COOKIE_MAX_AGE = 60 * 60 * 24 * 30; // 30 days
|
|
34
|
+
/**
|
|
35
|
+
* Default storage adapter. Reads/writes browser cookies via
|
|
36
|
+
* `document.cookie`. Use this unless you have a specific reason to
|
|
37
|
+
* pin tokens in JavaScript-managed storage (and you almost certainly
|
|
38
|
+
* don't — that's the XSS risk this adapter exists to retire).
|
|
39
|
+
*/
|
|
40
|
+
export class CookieStorageAdapter {
|
|
41
|
+
constructor(opts = {}) {
|
|
42
|
+
var _a;
|
|
43
|
+
this.secure = (_a = opts.secure) !== null && _a !== void 0 ? _a : false;
|
|
44
|
+
}
|
|
45
|
+
getItem(key) {
|
|
46
|
+
if (typeof document === 'undefined' || !document.cookie)
|
|
47
|
+
return null;
|
|
48
|
+
const cookies = document.cookie.split('; ');
|
|
49
|
+
for (const cookie of cookies) {
|
|
50
|
+
const eq = cookie.indexOf('=');
|
|
51
|
+
if (eq === -1)
|
|
52
|
+
continue;
|
|
53
|
+
const name = cookie.slice(0, eq);
|
|
54
|
+
if (name !== key)
|
|
55
|
+
continue;
|
|
56
|
+
const raw = cookie.slice(eq + 1);
|
|
57
|
+
try {
|
|
58
|
+
return decodeURIComponent(raw);
|
|
59
|
+
}
|
|
60
|
+
catch (_a) {
|
|
61
|
+
return raw;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
setItem(key, value) {
|
|
67
|
+
if (typeof document === 'undefined')
|
|
68
|
+
return;
|
|
69
|
+
const encoded = encodeURIComponent(value);
|
|
70
|
+
const flags = `${COOKIE_DEFAULTS}${this.secure ? '; Secure' : ''}`;
|
|
71
|
+
document.cookie = `${key}=${encoded}; Max-Age=${COOKIE_MAX_AGE}; ${flags}`;
|
|
72
|
+
}
|
|
73
|
+
removeItem(key) {
|
|
74
|
+
if (typeof document === 'undefined')
|
|
75
|
+
return;
|
|
76
|
+
const flags = `${COOKIE_DEFAULTS}${this.secure ? '; Secure' : ''}`;
|
|
77
|
+
document.cookie = `${key}=; Max-Age=0; ${flags}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
let localStorageWarned = false;
|
|
81
|
+
/**
|
|
82
|
+
* @deprecated Use `CookieStorageAdapter` instead. Persists tokens in
|
|
83
|
+
* `localStorage`, where any XSS payload can read them and where they
|
|
84
|
+
* survive tab close — the opposite of what mobile's
|
|
85
|
+
* `expo-secure-store` does. Scheduled for removal in v1.4.x. A
|
|
86
|
+
* one-shot `console.warn` is emitted on the first `setItem` to make
|
|
87
|
+
* accidental usage greppable.
|
|
88
|
+
*/
|
|
89
|
+
export class LocalStorageAdapter {
|
|
90
|
+
getItem(key) {
|
|
91
|
+
if (typeof window === 'undefined' || !window.localStorage)
|
|
92
|
+
return null;
|
|
93
|
+
try {
|
|
94
|
+
return window.localStorage.getItem(key);
|
|
95
|
+
}
|
|
96
|
+
catch (_a) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
setItem(key, value) {
|
|
101
|
+
warnOnce();
|
|
102
|
+
if (typeof window === 'undefined' || !window.localStorage)
|
|
103
|
+
return;
|
|
104
|
+
try {
|
|
105
|
+
window.localStorage.setItem(key, value);
|
|
106
|
+
}
|
|
107
|
+
catch (_a) {
|
|
108
|
+
/* quota / private mode — silent */
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
removeItem(key) {
|
|
112
|
+
if (typeof window === 'undefined' || !window.localStorage)
|
|
113
|
+
return;
|
|
114
|
+
try {
|
|
115
|
+
window.localStorage.removeItem(key);
|
|
116
|
+
}
|
|
117
|
+
catch (_a) {
|
|
118
|
+
/* silent */
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function warnOnce() {
|
|
123
|
+
if (localStorageWarned)
|
|
124
|
+
return;
|
|
125
|
+
// eslint-disable-next-line no-console
|
|
126
|
+
console.warn('[mk-web] LocalStorageAdapter is deprecated. Tokens in localStorage are ' +
|
|
127
|
+
'reachable from any XSS payload. Migrate to CookieStorageAdapter ' +
|
|
128
|
+
'(browser-managed cookies) or a server-set httpOnly cookie.');
|
|
129
|
+
localStorageWarned = true;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Factory for the default storage used by `<MkAuthProvider>` when no
|
|
133
|
+
* `storage` prop is passed. Always returns a fresh `CookieStorageAdapter`
|
|
134
|
+
* so multiple providers in the same tree (different scopes) get their
|
|
135
|
+
* own adapter instances — cookies themselves are scoped by key name.
|
|
136
|
+
*/
|
|
137
|
+
export function getDefaultAuthStorage() {
|
|
138
|
+
return new CookieStorageAdapter();
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=AuthStorageAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthStorageAdapter.js","sourceRoot":"","sources":["../../src/auth/AuthStorageAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,8 +11,17 @@ export interface MkAuthProviderProps {
|
|
|
11
11
|
/** Children wrapped by the provider. */
|
|
12
12
|
children: ReactNode;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Storage adapter for tokens and the cached user.
|
|
15
|
+
*
|
|
16
|
+
* **Required since v1.3.0.** Default if omitted is
|
|
17
|
+
* `new CookieStorageAdapter()` — aligned with the mobile
|
|
18
|
+
* `expo-secure-store` pattern, immune to the XSS exfiltration
|
|
19
|
+
* window that `localStorage` opened. Tests pass an in-memory
|
|
20
|
+
* adapter; consumers with a specific need can pass
|
|
21
|
+
* `new LocalStorageAdapter()` (deprecated) or their own impl.
|
|
22
|
+
*
|
|
23
|
+
* @see CookieStorageAdapter
|
|
24
|
+
* @see LocalStorageAdapter
|
|
16
25
|
*/
|
|
17
26
|
storage?: AuthStorageAdapter;
|
|
18
27
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MkAuthProvider.d.ts","sourceRoot":"","sources":["../../src/auth/MkAuthProvider.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAUvC,OAAO,KAAK,EAER,kBAAkB,EAKrB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"MkAuthProvider.d.ts","sourceRoot":"","sources":["../../src/auth/MkAuthProvider.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAUvC,OAAO,KAAK,EAER,kBAAkB,EAKrB,MAAM,SAAS,CAAC;AAWjB,MAAM,WAAW,mBAAmB;IAChC,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAsSD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAkB5E"}
|