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.
- package/dist/index.js +80 -27
- 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
|
|
20046
|
+
function writeState(path, state) {
|
|
20047
20047
|
mkdirSync2(dirname(path), { recursive: true, mode: 448 });
|
|
20048
|
-
writeFileSync2(path, JSON.stringify(
|
|
20048
|
+
writeFileSync2(path, JSON.stringify(state, null, 2), { mode: 384 });
|
|
20049
20049
|
chmodSync(path, 384);
|
|
20050
20050
|
}
|
|
20051
|
-
function
|
|
20051
|
+
function readState(path) {
|
|
20052
20052
|
if (!existsSync(path)) return null;
|
|
20053
20053
|
try {
|
|
20054
|
-
const
|
|
20055
|
-
|
|
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(
|
|
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
|
|
20108
|
+
async function launch(chromium, headless) {
|
|
20102
20109
|
for (const channel of BROWSER_CHANNELS) {
|
|
20103
20110
|
try {
|
|
20104
|
-
return await chromium.launch({ headless
|
|
20111
|
+
return await chromium.launch({ headless, ...channel && { channel } });
|
|
20105
20112
|
} catch {
|
|
20106
20113
|
}
|
|
20107
20114
|
}
|
|
20108
|
-
|
|
20115
|
+
return null;
|
|
20109
20116
|
}
|
|
20110
|
-
async function interactiveLogin(
|
|
20117
|
+
async function interactiveLogin(saved = null) {
|
|
20111
20118
|
const { chromium } = await pw();
|
|
20112
|
-
const browser = await
|
|
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(
|
|
20119
|
-
await throttle(() => page.goto(
|
|
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
|
|
20129
|
-
|
|
20135
|
+
const next = await context.storageState();
|
|
20136
|
+
writeState(cookieFile, next);
|
|
20130
20137
|
log("[auth] Sess\xE3o iniciada.");
|
|
20131
|
-
return
|
|
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 =
|
|
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
|
-
|
|
20175
|
-
|
|
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))
|
|
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;
|