@asteby/storefront 0.11.0 → 0.11.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/README.md +5 -3
- package/dist/cli.js +65 -30
- package/dist/fiscal-document.d.cts +2 -2
- package/dist/fiscal-document.d.ts +2 -2
- package/dist/index.cjs +5 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/mock.cjs +3 -3
- package/dist/mock.cjs.map +1 -1
- package/dist/mock.d.cts +1 -1
- package/dist/mock.d.ts +1 -1
- package/dist/mock.js +3 -3
- package/dist/mock.js.map +1 -1
- package/dist/mockup.d.cts +1 -1
- package/dist/mockup.d.ts +1 -1
- package/dist/node.cjs +85 -31
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +59 -25
- package/dist/node.d.ts +59 -25
- package/dist/node.js +82 -31
- package/dist/node.js.map +1 -1
- package/dist/{types-D7UJxrba.d.ts → types-B1E3GySa.d.ts} +1 -1
- package/dist/{types-C-Ley-Mt.d.cts → types-bsdlAumU.d.cts} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -415,8 +415,10 @@ npx @asteby/storefront connect --ops https://ops.example.com --env-file .env --t
|
|
|
415
415
|
# Conectada ✓ · 7 alcances · sk_…a91f
|
|
416
416
|
```
|
|
417
417
|
|
|
418
|
-
`connect` redeems the code (`POST {ops}/api/connect/v1/redeem`
|
|
419
|
-
`
|
|
418
|
+
`connect` redeems the code (`POST {ops}/api/connect/v1/redeem` with `{code, client:{name,version,runtime,hostname}}`; OPS
|
|
419
|
+
answers `{success:true,data:{token, connection, scopes, rotation_days, api_base}}`, an invalid, expired or used code is one
|
|
420
|
+
answer: `400 invalid_code`), writes the key ATOMICALLY (temp file + rename, mode `0600`; default `./storefront_token`)
|
|
421
|
+
with its non-secret state beside it (`<file>.json`: connection, `api_base`, `rotation_days`, `rotate_after`), leaves ONLY `OPS_URL` and
|
|
420
422
|
`STOREFRONT_TOKEN_FILE` in the env file (never the key), prints just the prefix and last four characters, and checks
|
|
421
423
|
the key with `token.introspect()`. Exit code is `1` with an es-MX message for an expired or used code, an unreachable
|
|
422
424
|
OPS or a non-TTY stdin.
|
|
@@ -434,7 +436,7 @@ const storefront = createStorefrontClient({
|
|
|
434
436
|
token: storefrontTokenFromEnv()!,
|
|
435
437
|
})
|
|
436
438
|
void diagnose(storefront) // one warning if scopes are missing; never throws, never delays boot
|
|
437
|
-
connectStore().keepFresh() // heartbeat
|
|
439
|
+
connectStore().keepFresh() // heartbeat; rotates (POST /api/connect/v1/rotate) on rotate_now / next_rotation_at; honors Retry-After on 429
|
|
438
440
|
```
|
|
439
441
|
|
|
440
442
|
`token` also accepts any `() => string`, called on every request. `diagnose` logs, at most once, which scopes are
|
package/dist/cli.js
CHANGED
|
@@ -271,7 +271,11 @@ function createStorefrontClient(config) {
|
|
|
271
271
|
* What the token in use is and can do (`GET {baseUrl}/_token`, a gateway route, not a `/storefront` one).
|
|
272
272
|
* Needs no scope, so it answers even when the token is under-provisioned: `missing_scopes` says what to repair.
|
|
273
273
|
*/
|
|
274
|
-
introspect: (options) =>
|
|
274
|
+
introspect: async (options) => {
|
|
275
|
+
const body = await get("/_token", void 0, options);
|
|
276
|
+
const env = body;
|
|
277
|
+
return typeof env?.success === "boolean" && env.data ? env.data : body;
|
|
278
|
+
}
|
|
275
279
|
},
|
|
276
280
|
/** Returns a new client that sends the given buyer session token. */
|
|
277
281
|
withCustomerToken(customerToken) {
|
|
@@ -460,7 +464,7 @@ function repairPath(connectionName) {
|
|
|
460
464
|
import { chmodSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeSync } from "fs";
|
|
461
465
|
import { dirname, join, resolve } from "path";
|
|
462
466
|
import { hostname } from "os";
|
|
463
|
-
var SDK_VERSION = true ? "0.11.
|
|
467
|
+
var SDK_VERSION = true ? "0.11.1" : "dev";
|
|
464
468
|
function writeFileAtomic(file, content, mode = 384) {
|
|
465
469
|
const dir = dirname(file);
|
|
466
470
|
mkdirSync(dir, { recursive: true });
|
|
@@ -489,17 +493,26 @@ function writeFileAtomic(file, content, mode = 384) {
|
|
|
489
493
|
throw err;
|
|
490
494
|
}
|
|
491
495
|
}
|
|
496
|
+
function connectClientInfo() {
|
|
497
|
+
return { name: "@asteby/storefront", version: SDK_VERSION, runtime: `node ${process.version}`, hostname: hostname() };
|
|
498
|
+
}
|
|
492
499
|
var ConnectError = class extends Error {
|
|
493
|
-
constructor(kind, message, status = 0) {
|
|
500
|
+
constructor(kind, message, status = 0, retryAfterSeconds) {
|
|
494
501
|
super(message);
|
|
495
502
|
this.kind = kind;
|
|
496
503
|
this.status = status;
|
|
504
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
497
505
|
}
|
|
498
506
|
kind;
|
|
499
507
|
status;
|
|
508
|
+
retryAfterSeconds;
|
|
500
509
|
name = "ConnectError";
|
|
501
510
|
};
|
|
502
511
|
var trimSlash = (u) => u.replace(/\/+$/, "");
|
|
512
|
+
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
513
|
+
function unwrapEnvelope(body) {
|
|
514
|
+
return isObj(body) && typeof body.success === "boolean" && "data" in body ? body.data : body;
|
|
515
|
+
}
|
|
503
516
|
async function post(fetchImpl, url, body, bearer) {
|
|
504
517
|
let res;
|
|
505
518
|
try {
|
|
@@ -513,48 +526,70 @@ async function post(fetchImpl, url, body, bearer) {
|
|
|
513
526
|
throw new ConnectError("network", "No pudimos comunicarnos con OPS. Revisa la direcci\xF3n y tu conexi\xF3n a internet.");
|
|
514
527
|
}
|
|
515
528
|
const text = await res.text().catch(() => "");
|
|
516
|
-
|
|
517
|
-
if (res.status === 429) throw new ConnectError("rate_limited", "Demasiados intentos. Espera unos minutos e int\xE9ntalo de nuevo.", 429);
|
|
518
|
-
if (res.status === 401 || res.status === 403) throw new ConnectError("unauthorized", "OPS rechaz\xF3 la credencial de esta tienda.", res.status);
|
|
519
|
-
if (res.status >= 500) throw new ConnectError("server", "OPS no pudo atender la solicitud. Int\xE9ntalo de nuevo en un momento.", res.status);
|
|
520
|
-
throw new ConnectError("invalid_code", "El c\xF3digo no es v\xE1lido, ya se us\xF3 o venci\xF3. Genera uno nuevo en OPS \u2192 Conexiones y API.", res.status);
|
|
521
|
-
}
|
|
522
|
-
if (!text) return void 0;
|
|
529
|
+
let json;
|
|
523
530
|
try {
|
|
524
|
-
|
|
531
|
+
json = text ? JSON.parse(text) : void 0;
|
|
525
532
|
} catch {
|
|
526
|
-
|
|
533
|
+
json = void 0;
|
|
527
534
|
}
|
|
535
|
+
if (!res.ok) {
|
|
536
|
+
const o = isObj(json) ? json : {};
|
|
537
|
+
const e = isObj(o.error) ? o.error : void 0;
|
|
538
|
+
const code = typeof o.error === "string" ? o.error : typeof e?.code === "string" ? e.code : "";
|
|
539
|
+
const message = typeof o.message === "string" ? o.message : typeof e?.message === "string" ? e.message : "";
|
|
540
|
+
if (res.status === 429) {
|
|
541
|
+
const ra = Number(res.headers.get("Retry-After"));
|
|
542
|
+
throw new ConnectError("rate_limited", message || "Demasiados intentos. Espera un momento y vuelve a intentarlo.", 429, Number.isFinite(ra) && ra > 0 ? ra : void 0);
|
|
543
|
+
}
|
|
544
|
+
if (res.status === 401 || code === "invalid_token") throw new ConnectError("unauthorized", message || "Clave inv\xE1lida.", res.status);
|
|
545
|
+
if (code === "invalid_code") throw new ConnectError("invalid_code", message || "El c\xF3digo no es v\xE1lido o ya venci\xF3. Genera uno nuevo desde OPS.", res.status);
|
|
546
|
+
if (res.status >= 500) throw new ConnectError("server", message || "OPS no pudo atender la solicitud. Int\xE9ntalo de nuevo en un momento.", res.status);
|
|
547
|
+
throw new ConnectError("server", message || `OPS respondi\xF3 ${res.status}.`, res.status);
|
|
548
|
+
}
|
|
549
|
+
if (json === void 0) throw new ConnectError("invalid_response", "OPS respondi\xF3 algo que no entendimos.", res.status);
|
|
550
|
+
return unwrapEnvelope(json);
|
|
528
551
|
}
|
|
529
|
-
function
|
|
552
|
+
function assertGrant(r) {
|
|
530
553
|
const o = r;
|
|
531
|
-
if (!o || typeof o.
|
|
554
|
+
if (!isObj(o) || typeof o.token !== "string" || !o.token || !isObj(o.connection) || typeof o.connection.id !== "string" || !Array.isArray(o.scopes)) {
|
|
532
555
|
throw new ConnectError("invalid_response", "OPS respondi\xF3 algo que no entendimos.");
|
|
533
556
|
}
|
|
534
557
|
}
|
|
535
558
|
async function redeemConnectionCode(opts) {
|
|
536
559
|
const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
assertRedeem(res);
|
|
560
|
+
const res = await post(fetchImpl, `${trimSlash(opts.opsUrl)}/api/connect/v1/redeem`, { code: opts.code.trim(), client: connectClientInfo() });
|
|
561
|
+
assertGrant(res);
|
|
540
562
|
return res;
|
|
541
563
|
}
|
|
542
564
|
var stateFileFor = (tokenFile) => `${tokenFile}.json`;
|
|
543
|
-
|
|
544
|
-
|
|
565
|
+
var DEFAULT_ROTATION_DAYS = 90;
|
|
566
|
+
var DAY_MS = 864e5;
|
|
567
|
+
function stateFromGrant(g, opsUrl, prev, now = Date.now()) {
|
|
568
|
+
const days = g.rotation_days && g.rotation_days > 0 ? g.rotation_days : prev?.rotation_days ?? DEFAULT_ROTATION_DAYS;
|
|
569
|
+
return {
|
|
570
|
+
ops_url: trimSlash(opsUrl),
|
|
571
|
+
api_base: g.api_base || prev?.api_base || `${trimSlash(opsUrl)}/api/gw`,
|
|
572
|
+
connection: g.connection,
|
|
573
|
+
scopes: g.scopes,
|
|
574
|
+
rotation_days: days,
|
|
575
|
+
rotate_after: new Date(now + days * DAY_MS).toISOString(),
|
|
576
|
+
...g.grace_until ? { grace_until: g.grace_until } : {}
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
function saveConnection(tokenFile, g, state) {
|
|
545
580
|
writeFileAtomic(stateFileFor(tokenFile), JSON.stringify(state, null, 2) + "\n");
|
|
546
|
-
writeFileAtomic(tokenFile,
|
|
581
|
+
writeFileAtomic(tokenFile, g.token + "\n");
|
|
547
582
|
}
|
|
548
583
|
|
|
549
584
|
// src/cli.ts
|
|
550
|
-
var HELP = `Uso: npx @asteby/storefront connect --ops <url> [--env-file <ruta>] [--token-file <ruta>]
|
|
585
|
+
var HELP = `Uso: npx @asteby/storefront connect --ops <url> [--env-file <ruta>] [--token-file <ruta>]
|
|
551
586
|
|
|
552
587
|
Conecta esta tienda con OPS. El c\xF3digo de conexi\xF3n (K7QM-2XPA) se pide en la terminal; nunca va como argumento.
|
|
553
588
|
|
|
554
589
|
--ops Direcci\xF3n de OPS (https://ops.ejemplo.com). Tambi\xE9n OPS_URL.
|
|
555
590
|
--token-file D\xF3nde guardar la clave (por omisi\xF3n ./storefront_token). Modo 0600, escritura at\xF3mica.
|
|
556
591
|
--env-file Archivo .env donde dejar SOLO OPS_URL y STOREFRONT_TOKEN_FILE (nunca la clave).
|
|
557
|
-
|
|
592
|
+
`;
|
|
558
593
|
function parseArgs(argv) {
|
|
559
594
|
const flags = {};
|
|
560
595
|
const [cmd, ...rest] = argv;
|
|
@@ -562,7 +597,7 @@ function parseArgs(argv) {
|
|
|
562
597
|
const a = rest[i];
|
|
563
598
|
const eq = a.indexOf("=");
|
|
564
599
|
const key = (eq > 0 ? a.slice(0, eq) : a).replace(/^--/, "");
|
|
565
|
-
if (!a.startsWith("--") || !["ops", "env-file", "token-file", "
|
|
600
|
+
if (!a.startsWith("--") || !["ops", "env-file", "token-file", "code"].includes(key)) return { cmd, flags, bad: a };
|
|
566
601
|
if (key === "code") return { cmd, flags, bad: "--code (el c\xF3digo se pide en la terminal, no como argumento)" };
|
|
567
602
|
const val = eq > 0 ? a.slice(eq + 1) : rest[++i];
|
|
568
603
|
if (!val) return { cmd, flags, bad: a };
|
|
@@ -616,25 +651,25 @@ ${HELP}`);
|
|
|
616
651
|
return 1;
|
|
617
652
|
}
|
|
618
653
|
try {
|
|
619
|
-
const r = await redeemConnectionCode({ opsUrl, code, ...
|
|
620
|
-
saveConnection(tokenFile, r, opsUrl);
|
|
654
|
+
const r = await redeemConnectionCode({ opsUrl, code, ...io.fetch ? { fetch: io.fetch } : {} });
|
|
655
|
+
saveConnection(tokenFile, r, stateFromGrant(r, opsUrl, null));
|
|
621
656
|
if (envFile) {
|
|
622
657
|
const current = existsSync2(envFile) ? readFileSync2(envFile, "utf8") : "";
|
|
623
658
|
if (/^\s*(export\s+)?STOREFRONT_TOKEN\s*=/m.test(current)) {
|
|
624
659
|
io.stderr("Aviso: tu .env a\xFAn tiene STOREFRONT_TOKEN con una clave anterior; b\xF3rrala, el archivo de clave tiene prioridad.");
|
|
625
660
|
}
|
|
626
|
-
writeFileAtomic(envFile, upsertEnv(current, { OPS_URL:
|
|
661
|
+
writeFileAtomic(envFile, upsertEnv(current, { OPS_URL: opsUrl, STOREFRONT_TOKEN_FILE: tokenFile }), 384);
|
|
627
662
|
}
|
|
628
|
-
io.stdout(`Conectada \u2713 \xB7 ${r.scopes.length} alcances \xB7 ${maskToken(r.
|
|
663
|
+
io.stdout(`Conectada \u2713 \xB7 ${r.scopes.length} alcances \xB7 ${maskToken(r.token)}`);
|
|
629
664
|
io.stdout(`Clave guardada en ${tokenFile} (modo 0600).${envFile ? ` Variables en ${envFile}.` : ` Usa STOREFRONT_TOKEN_FILE=${tokenFile}.`}`);
|
|
630
|
-
const base =
|
|
631
|
-
const sf = createStorefrontClient({ baseUrl: base, token: r.
|
|
665
|
+
const base = r.api_base || `${opsUrl}/api/gw`;
|
|
666
|
+
const sf = createStorefrontClient({ baseUrl: base, token: r.token, ...io.fetch ? { fetch: io.fetch } : {}, timeoutMs: 1e4 });
|
|
632
667
|
try {
|
|
633
668
|
const info = await sf.token.introspect();
|
|
634
669
|
if (info.missing_scopes?.length) {
|
|
635
670
|
io.stderr(`Aviso: a la clave le faltan permisos (${info.missing_scopes.join(", ")}). Rep\xE1ralo en ${repairPath()}.`);
|
|
636
671
|
} else {
|
|
637
|
-
io.stdout(`Verificada con OPS \xB7 entorno ${info.environment}.`);
|
|
672
|
+
io.stdout(`Verificada con OPS \xB7 entorno ${info.environment ?? r.connection.environment}.`);
|
|
638
673
|
}
|
|
639
674
|
} catch {
|
|
640
675
|
io.stderr("Aviso: la clave se guard\xF3 pero no pudimos verificarla con OPS todav\xEDa.");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { U as FiscalDocumentSource, aR as RegionFiscalEntry, Z as IdentityDocumentSpec, T as FiscalDocumentEvidence, aS as RegionFiscalId, $ as IdentityRulesV1 } from './types-
|
|
2
|
-
export { Y as IdentityCapture, _ as IdentityFieldRule, a0 as IdentityStrategy } from './types-
|
|
1
|
+
import { U as FiscalDocumentSource, aR as RegionFiscalEntry, Z as IdentityDocumentSpec, T as FiscalDocumentEvidence, aS as RegionFiscalId, $ as IdentityRulesV1 } from './types-bsdlAumU.cjs';
|
|
2
|
+
export { Y as IdentityCapture, _ as IdentityFieldRule, a0 as IdentityStrategy } from './types-bsdlAumU.cjs';
|
|
3
3
|
import './geo.cjs';
|
|
4
4
|
|
|
5
5
|
/** A value read from a document: text, or the codes of a catalog (`multiple` fields). */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { U as FiscalDocumentSource, aR as RegionFiscalEntry, Z as IdentityDocumentSpec, T as FiscalDocumentEvidence, aS as RegionFiscalId, $ as IdentityRulesV1 } from './types-
|
|
2
|
-
export { Y as IdentityCapture, _ as IdentityFieldRule, a0 as IdentityStrategy } from './types-
|
|
1
|
+
import { U as FiscalDocumentSource, aR as RegionFiscalEntry, Z as IdentityDocumentSpec, T as FiscalDocumentEvidence, aS as RegionFiscalId, $ as IdentityRulesV1 } from './types-B1E3GySa.js';
|
|
2
|
+
export { Y as IdentityCapture, _ as IdentityFieldRule, a0 as IdentityStrategy } from './types-B1E3GySa.js';
|
|
3
3
|
import './geo.js';
|
|
4
4
|
|
|
5
5
|
/** A value read from a document: text, or the codes of a catalog (`multiple` fields). */
|
package/dist/index.cjs
CHANGED
|
@@ -303,7 +303,11 @@ function createStorefrontClient(config) {
|
|
|
303
303
|
* What the token in use is and can do (`GET {baseUrl}/_token`, a gateway route, not a `/storefront` one).
|
|
304
304
|
* Needs no scope, so it answers even when the token is under-provisioned: `missing_scopes` says what to repair.
|
|
305
305
|
*/
|
|
306
|
-
introspect: (options) =>
|
|
306
|
+
introspect: async (options) => {
|
|
307
|
+
const body = await get("/_token", void 0, options);
|
|
308
|
+
const env = body;
|
|
309
|
+
return typeof env?.success === "boolean" && env.data ? env.data : body;
|
|
310
|
+
}
|
|
307
311
|
},
|
|
308
312
|
/** Returns a new client that sends the given buyer session token. */
|
|
309
313
|
withCustomerToken(customerToken) {
|