Repository navigation
flowreel 0.4.0
Install: npm install --save-dev @confidee/flowreel@0.4.0 (npm, published by the release workflow with a provenance attestation).
Every request flowreel makes is bounded, and a failure says what it
means. The Microsoft 365 driver never follows a redirect and says whether
a failed send may have gone out; a mail wait rides out throttling and
brief network drops; the sign-in, session and persona requests have a time
limit; and the shared summary no longer carries Playwright's call log,
which could hold a live session cookie. Two security fixes: upgrade if
you use the Microsoft 365 driver or share summary.md.
Security
GraphMailDriverno longer follows redirects (#22). Affects 0.3.0
and earlier. The driver's requests usedfetch's default
redirect: "follow", and the fetch standard keeps the body on a 307 or
308: a 307 onsendMailsent the mail a second time (and reported the
send as successful), a redirect loop sent it up to 21 times, and a 307
on the token request re-sent the form body,client_secretincluded,
to the redirect target. Every Graph and token request now uses
redirect: "manual": a 3xx is aGraphHttpErrorthat sent nothing
(mayHaveSent: false), never followed and never retried. A project
that passes its ownfetchmust make one attempt per call and never
follow redirects.summary.md,summary.jsonandsummary.linesno longer carry
Playwright's call log (#26). Affects 0.3.0 and earlier. The summary
reporter copied up to 4000 characters of every error, and a failed
Playwright request's error goes on, after its first line, with a
"Call log" that names the full URL with its query string, every
request header (the persona's session cookie, a CSRF token) and a
response'sset-cookie(on a stalled sign-in, the fresh session
cookie): the file flowreel tells you to share could carry a live
session. The summary now keeps an error up to its call log and drops
everyhttp(s)://URL's query string and fragment; the HTML report
(where atoHaveURLfailure that differs only in its query still shows
the difference) and traces keep
the whole error and stay credential-bearing. flowreel's own sign-in
seam and session probes rethrow the first line of Playwright's error
only, without a cause, and so do the template and example adapters. If
you shared asummary.mdfrom a run that failed on a request, check it
for aCall log:block and revoke the sessions it names (a completed
teardown already revoked them).
Added
-
requestTimeoutMsforGraphMailDriver(@confidee/flowreel/mail-graph):
every Graph and token request, from the connection to the last byte of
the body, gets at most this long (default 30 s), and insidewaitForno
more than the wait has left. A whole number of milliseconds from 1 to
2147483647; anything else is aConfigError. -
GraphRequestTimeoutErrorfrom@confidee/flowreel/mail-graph
(timeoutMs, plusoperation,mayHaveSentandclientRequestIdfrom
GraphRequestError): what a request that got no answer throws. A
sendMailor a reply'ssendsetsmayHaveSent, says the mail may have
been sent and names itsclient-request-id; it is never retried. -
GraphRequestErrorfrom@confidee/flowreel/mail-graph: the base of
every error a Graph or token request ends in (operation,
mayHaveSent, always a boolean, andclientRequestId), with a
one-line message. A bad 2xx answer (a body that is not JSON, a token
answer withoutaccess_token) throws it as is, withmayHaveSent
false.GraphHttpError,GraphRequestTimeoutErrorand the new
GraphTransportErrorextend it (#17). -
GraphTransportErrorfrom@confidee/flowreel/mail-graph(code, such
asECONNRESETorUND_ERR_SOCKET; nocause): what a failure below
HTTP throws, instead of a plainError. The message is unchanged:
Graph <operation> failed: fetch failed (ECONNREFUSED)(#17). -
GraphHttpErrortakes an optional fifth argument, a details object
(mayHaveSent,clientRequestId,requestId,date,traceId,
correlationId,code), and exposes those fields; its four positional
arguments are unchanged (#17, #19). -
Request ids for
GraphMailDriver: every Graph and token request carries
a fresh randomclient-request-id, and the token request sends
return-client-request-id: true. Every error keeps it; a
GraphHttpErroralso keeps Graph'srequest-idanddate, and a token
error Entra'strace_idandcorrelation_id, when GUID-shaped. A token
error's message always names itstrace_idandcorrelation_idon the
detail line; a Graph request's message names its ids only when the mail
may have been sent (For Microsoft support: client-request-id …) (#19). -
requestTimeoutMsfor both seam HTTP clients
(@confidee/flowreel/rails-inertia), 30 s by default:
seamHttpFromRequest(request, { requestTimeoutMs })passes it as
Playwright's per-calltimeout(inside@playwright/testa request
context otherwise has none), andseamHttpFromFetch(baseURL, options)
now also takes{ fetch, requestTimeoutMs, setTimeout, clearTimeout }
besides thefetchfunction it took before. A seam request that runs
out is aborted and throws
Seam <METHOD> <path> got no answer within 30 s; raise requestTimeoutMs if a job drain legitimately takes longer
(#20). -
requestTimeoutMsfor flowreel's session requests (#24), 30 s by
default, validated like every flowreel request bound (a whole number of
milliseconds from 1 to 2147483647, else aConfigErrornaming the
option): onseamSignIn's options (@confidee/flowreel/rails-inertia),
onSessionProtocol(isSessionSignedOut;revokeSession, which never
throws, returnsrevoked: falsenaming the option, so teardown warns
loudly), and on
createPersonaApisandtourFixtures()for every persona request. It
is thetimeoutof each request context they open. -
requestFailure(error)from@confidee/flowreel/core: what may be
said of a failed Playwright request, the first line of its message
without a URL's query string, at most 300 characters. For a project's
own adapter (#26). -
/flowreel:doctoradvises whene2e/flowreel/adapter.tsopens request
contexts without a timeout (an adapter scaffolded before 0.4.0). It
only reads the file, with grep.
Changed
GraphMailDriver.waitFor: a temporary network failure on a mail read
or the token request is now re-polled instead of ending the wait (#23):
ECONNRESET,UND_ERR_SOCKET,EPIPE,ECONNABORTED,ETIMEDOUT,
EAI_AGAINand undici's connect, headers and body timeouts (an
AggregateErroronly when every inner code is listed), with the
token's back-off (5 s, then 10 s, 20 s … up to 60 s, never past the
wait's end, on top of the wait's own poll interval), counted apart for the token request and the reads. Only
once that endpoint has answered the driver: at first contact the same
error still ends the wait, so a firewall or broken DNS fails fast. Any
other code, or none, stays fatal (ECONNREFUSED,ENOTFOUND, TLS, a
customfetchthat throws a plain error), nothing that may have sent
mail is ever re-polled, and nothing is retried outsidewaitFor. A
wait that times out reports the step, the code and the count only
(Last observed: Graph read failed (ECONNRESET), 3 in a row, no mailbox read completed in this wait, retrying).GraphMailDriver.waitForre-polls a429,503or504only on a
GET read; a write's never was reachable there, and now cannot be.- The persona request contexts (
createPersonaApis, the tour'sas()
andpersonaApis) now have a 30 stimeoutof their own, which
overrides the project'sactionTimeoutfor them (15 s under flowreel's
createConfig); a request's owntimeoutstill wins. Set
requestTimeoutMsontourFixtures()for a longer one. - Global teardown (
signOutAll) now survives anAppAdapter.signOut
that throws, against its contract: that persona gets the loud "could
not sign out" warning (with the first line of the error) and the other
personas are still signed out. Before, one throw skipped every later
persona. seamSignInandisSessionSignedOutthrow
Test sign-in (POST <path>) failed: …and
Session probe GET <path> failed: …with the first line of
Playwright's error, instead of Playwright's error itself (#26).- The template adapter (
/flowreel:init) and the example's set a
REQUEST_TIMEOUT_MS(30 s) on every request context and onSESSION,
describe a failed request withrequestFailure(), andhealth()
returns a failed request's first line instead of throwing; the
template shipsunit/adapter.spec.ts, which checks both./flowreel:init
never rewrites an existing adapter: copy these lines into one you
scaffolded before (the doctor points it out). GraphMailDriver: a failedsendMailor replysendnow says whether
the mail may have been sent from everything observed, not only a
timeout. Any 5xx but 509 (503 included) and any transport failure that
does not prove the request never left (a reset or closed socket,
terminated,EPIPE, any code on areadorwrite, an unknown or
missing code) setmayHaveSent; a 3xx, a 4xx (429 included), 509 and
a DNS, connect or TLS failure (agetaddrinfoorconnectsyscall, or
a listed code on an error without a syscall) do not. An error status
whose body does not arrive within the bound stays a may-have-sent
timeout. A 2xx status now resolves the send at once, even when its empty
body then fails or never ends. Nothing is retried (#17).GraphMailDriver.waitFor: a token request that is throttled or meets a
server error (HTTP 429, 500, 502, 503, 504, or OAuth error
temporarily_unavailable) is now re-polled instead of ending the wait,
after the response's Retry-After exactly, else 5 s, then 10 s, 20 s …
up to 60 s, never past the wait's end. A token request that got no
answer gets the same back-off. Credentials failures stay fatal (token
transport errors: see #23 above), and nothing is retried outside
waitFor(#18). Only consecutive failures double the back-off: a token
obtained resets it.GraphMailDriver.health(): a request that could not connect (a DNS,
connect or TLS failure) now points at the network path, not the
credentials.GraphMailDriver: an error code from a response body (Graph's
error.code, Entra'serror) is shown only when code-shaped, else as
UnknownError; and the client secret is redacted before the message is
joined into one line, so a secret split across a line break is not
reassembled.
Fixed
- flowreel's session requests could hang (#24). The sign-in seam, the
session probes and sign-out, the persona API contexts and the template
adapter opened request contexts without atimeout, so they got
Playwright's default: 30 s in global setup and teardown, the project's
actionTimeoutin a test (15 s undercreateConfig), and none at all
withactionTimeout: 0or without flowreel's config, where a server
that never answered, or a body that never ended, hung the run until
the test or fixture timeout. Every one of them is now bounded by
requestTimeoutMs(30 s by default), body included. - A Microsoft Graph request that never answered could stall a run (#16).
A hung request ended only at Node'sfetchlimit of 300 s, a body that
trickled in never ended, a hung token request held up every later call
until it ended, andwaitFor's owntimeoutMscould not end a request
in flight. Now every request is bounded, a wait ends at itstimeoutMs
(a read that got no answer is re-polled, and after the first attempt
none starts once no time is left), a timed-out token request is dropped
so the next call asks again,health()points at the network instead
of the credentials when a request got no answer, and a transport error
keeps its cause's error code (fetch failed (ECONNREFUSED)) and nothing
else of it. GraphMailDriver: a 2xx answer whose body cannot be read now throws the
named, redacted transport error (Graph <operation> failed: …) instead of
the raw stream error, which could echo the access token.GraphMailDriver: a token answer whose body is JSONnullnow fails with
"no access_token returned" instead of a bareTypeError.GraphMailDriverredacts the client secret and the access token in their
URI-encoded and form-encoded forms too, so a transport error that echoes
the token request's body no longer carries the secret.