auchan-mcp 0.2.0 → 0.2.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.
Files changed (2) hide show
  1. package/dist/index.js +80 -27
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -20043,16 +20043,17 @@ var CART_URL = `${AUCHAN_BASE}/on/demandware.store/Sites-AuchanPT-Site/pt_PT/Car
20043
20043
  var BROWSER_CHANNELS = ["msedge", "chrome", void 0];
20044
20044
  var NO_PLAYWRIGHT = "falta o Playwright; instala o pacote de novo (npm install auchan-mcp)";
20045
20045
  var NO_BROWSER = 'n\xE3o encontrei o Microsoft Edge nem o Google Chrome para abrir a janela de login; instala um deles ou corre "npx playwright install chromium"';
20046
- function writeCookies(path, cookies) {
20046
+ function writeState(path, state) {
20047
20047
  mkdirSync2(dirname(path), { recursive: true, mode: 448 });
20048
- writeFileSync2(path, JSON.stringify(cookies, null, 2), { mode: 384 });
20048
+ writeFileSync2(path, JSON.stringify(state, null, 2), { mode: 384 });
20049
20049
  chmodSync(path, 384);
20050
20050
  }
20051
- function readCookies(path) {
20051
+ function readState(path) {
20052
20052
  if (!existsSync(path)) return null;
20053
20053
  try {
20054
- const cookies = JSON.parse(readFileSync3(path, "utf8"));
20055
- return Array.isArray(cookies) && cookies.length ? cookies : null;
20054
+ const saved = JSON.parse(readFileSync3(path, "utf8"));
20055
+ const state = Array.isArray(saved) ? { cookies: saved, origins: [] } : { cookies: saved?.cookies, origins: saved?.origins ?? [] };
20056
+ return Array.isArray(state.cookies) && state.cookies.length ? state : null;
20056
20057
  } catch {
20057
20058
  return null;
20058
20059
  }
@@ -20062,13 +20063,18 @@ function createSession({
20062
20063
  log = console.error,
20063
20064
  loadPlaywright = () => import("playwright"),
20064
20065
  loginTimeoutMs = 5 * 60 * 1e3,
20066
+ keepAliveMs = 10 * 60 * 1e3,
20065
20067
  pollMs = 3e3,
20066
20068
  sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
20067
20069
  }) {
20068
20070
  let playwright = null;
20069
20071
  let api = null;
20072
+ let state = null;
20070
20073
  let loggedIn = false;
20071
20074
  let pending = null;
20075
+ let inFlight = 0;
20076
+ let timer = null;
20077
+ let beating = false;
20072
20078
  async function pw() {
20073
20079
  if (!playwright) {
20074
20080
  try {
@@ -20079,13 +20085,14 @@ function createSession({
20079
20085
  }
20080
20086
  return playwright;
20081
20087
  }
20082
- async function openApi(cookies) {
20088
+ async function openApi(next) {
20089
+ state = next;
20083
20090
  await api?.dispose().catch(() => {
20084
20091
  });
20085
20092
  api = await (await pw()).request.newContext({
20086
20093
  userAgent: USER_AGENT,
20087
20094
  locale: "pt-PT",
20088
- storageState: { cookies, origins: [] }
20095
+ storageState: { cookies: next.cookies, origins: [] }
20089
20096
  });
20090
20097
  }
20091
20098
  async function isLoggedIn(requester) {
@@ -20098,25 +20105,25 @@ function createSession({
20098
20105
  assertNotBlocked(res.status(), "");
20099
20106
  return res.ok() && /data-has-context="true"/.test(await res.text());
20100
20107
  }
20101
- async function launchWindow(chromium) {
20108
+ async function launch(chromium, headless) {
20102
20109
  for (const channel of BROWSER_CHANNELS) {
20103
20110
  try {
20104
- return await chromium.launch({ headless: false, ...channel && { channel } });
20111
+ return await chromium.launch({ headless, ...channel && { channel } });
20105
20112
  } catch {
20106
20113
  }
20107
20114
  }
20108
- throw new Error(NO_BROWSER);
20115
+ return null;
20109
20116
  }
20110
- async function interactiveLogin(savedCookies = null) {
20117
+ async function interactiveLogin(saved = null) {
20111
20118
  const { chromium } = await pw();
20112
- const browser = await launchWindow(chromium);
20119
+ const browser = await launch(chromium, false);
20120
+ if (!browser) throw new Error(NO_BROWSER);
20113
20121
  const closed = new Promise((resolve) => browser.on("disconnected", () => resolve("closed")));
20114
20122
  try {
20115
- const context = await browser.newContext({ locale: "pt-PT" });
20116
- if (savedCookies) await context.addCookies(savedCookies);
20123
+ const context = await browser.newContext({ locale: "pt-PT", ...saved && { storageState: saved } });
20117
20124
  const page = await context.newPage();
20118
- log(savedCookies ? "[auth] Abri uma janela do browser: escolhe a entrega ou o levantamento no Auchan." : "[auth] Abri uma janela do browser: faz o login no Auchan e escolhe a entrega ou o levantamento.");
20119
- await throttle(() => page.goto(savedCookies ? `${AUCHAN_BASE}/pt/` : LOGIN_URL, { waitUntil: "domcontentloaded", timeout: 3e4 }));
20125
+ log(saved ? "[auth] Abri uma janela do browser: escolhe a entrega ou o levantamento no Auchan." : "[auth] Abri uma janela do browser: faz o login no Auchan e escolhe a entrega ou o levantamento.");
20126
+ await throttle(() => page.goto(saved ? `${AUCHAN_BASE}/pt/` : LOGIN_URL, { waitUntil: "domcontentloaded", timeout: 3e4 }));
20120
20127
  const deadline = Date.now() + loginTimeoutMs;
20121
20128
  while (Date.now() < deadline) {
20122
20129
  if (await Promise.race([closed, sleep(pollMs).then(() => "tick")]) === "closed") {
@@ -20125,10 +20132,10 @@ function createSession({
20125
20132
  const url2 = new URL(page.url());
20126
20133
  if (url2.hostname !== "www.auchan.pt" || /login/i.test(url2.pathname)) continue;
20127
20134
  if (await isLoggedIn(context.request) && await hasDeliveryContext(context.request)) {
20128
- const cookies = await context.cookies();
20129
- writeCookies(cookieFile, cookies);
20135
+ const next = await context.storageState();
20136
+ writeState(cookieFile, next);
20130
20137
  log("[auth] Sess\xE3o iniciada.");
20131
- return cookies;
20138
+ return next;
20132
20139
  }
20133
20140
  }
20134
20141
  throw new Error("o login n\xE3o terminou em 5 minutos; tenta de novo");
@@ -20137,25 +20144,60 @@ function createSession({
20137
20144
  });
20138
20145
  }
20139
20146
  }
20147
+ let lastPersist = 0;
20148
+ async function persist(force = false) {
20149
+ if (!api || !state || !force && Date.now() - lastPersist < 5 * 60 * 1e3) return;
20150
+ lastPersist = Date.now();
20151
+ try {
20152
+ const { cookies } = await api.storageState();
20153
+ if (cookies.length) {
20154
+ state = { cookies, origins: state.origins };
20155
+ writeState(cookieFile, state);
20156
+ }
20157
+ } catch {
20158
+ }
20159
+ }
20160
+ function startKeepAlive() {
20161
+ if (timer || !keepAliveMs) return;
20162
+ timer = setInterval(async () => {
20163
+ if (!api || !loggedIn || pending || inFlight || beating) return;
20164
+ beating = true;
20165
+ try {
20166
+ if (await isLoggedIn(api)) {
20167
+ await persist(true);
20168
+ return;
20169
+ }
20170
+ loggedIn = false;
20171
+ } catch {
20172
+ } finally {
20173
+ beating = false;
20174
+ }
20175
+ }, keepAliveMs);
20176
+ timer.unref?.();
20177
+ }
20140
20178
  function ensureSession() {
20141
20179
  if (api && loggedIn) return Promise.resolve("saved");
20142
20180
  pending ??= (async () => {
20143
20181
  try {
20144
- const saved = readCookies(cookieFile);
20182
+ const saved = readState(cookieFile);
20145
20183
  if (saved) {
20146
20184
  await openApi(saved);
20147
20185
  const logged = await isLoggedIn(api);
20148
20186
  if (logged && await hasDeliveryContext(api)) {
20149
20187
  loggedIn = true;
20188
+ await persist(true);
20189
+ startKeepAlive();
20150
20190
  return "saved";
20151
20191
  }
20152
20192
  log(logged ? "[auth] Falta escolher a entrega." : "[auth] A sess\xE3o guardada expirou.");
20153
20193
  await openApi(await interactiveLogin(logged ? saved : null));
20154
20194
  loggedIn = true;
20195
+ startKeepAlive();
20155
20196
  return "window";
20156
20197
  }
20157
20198
  await openApi(await interactiveLogin());
20158
20199
  loggedIn = true;
20200
+ startKeepAlive();
20159
20201
  return "window";
20160
20202
  } finally {
20161
20203
  pending = null;
@@ -20171,15 +20213,21 @@ function createSession({
20171
20213
  return { status: res.status(), text: text2, url: res.url() };
20172
20214
  });
20173
20215
  async function request(method, path, form) {
20174
- await ensureSession();
20175
- let result = await call(method, path, form);
20176
- if (/login/i.test(new URL(result.url).pathname)) {
20177
- loggedIn = false;
20216
+ inFlight++;
20217
+ try {
20178
20218
  await ensureSession();
20179
- result = await call(method, path, form);
20180
- if (/login/i.test(new URL(result.url).pathname)) throw new Error("o site n\xE3o aceitou a sess\xE3o; tenta usar a ferramenta login de novo");
20219
+ let result = await call(method, path, form);
20220
+ if (/login/i.test(new URL(result.url).pathname)) {
20221
+ loggedIn = false;
20222
+ await ensureSession();
20223
+ result = await call(method, path, form);
20224
+ if (/login/i.test(new URL(result.url).pathname)) throw new Error("o site n\xE3o aceitou a sess\xE3o; tenta usar a ferramenta login de novo");
20225
+ }
20226
+ await persist();
20227
+ return { status: result.status, text: result.text };
20228
+ } finally {
20229
+ inFlight--;
20181
20230
  }
20182
- return { status: result.status, text: result.text };
20183
20231
  }
20184
20232
  const site = {
20185
20233
  get: (path) => request("get", path),
@@ -20189,6 +20237,11 @@ function createSession({
20189
20237
  return ensureSession();
20190
20238
  }
20191
20239
  async function close() {
20240
+ if (timer) {
20241
+ clearInterval(timer);
20242
+ timer = null;
20243
+ }
20244
+ await persist(true);
20192
20245
  const current = api;
20193
20246
  api = null;
20194
20247
  loggedIn = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auchan-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Servidor MCP para o Auchan.pt: pesquisa de produtos e gestão do carrinho.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",