npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sprigr/apps-oauth-utils

v0.3.1

Published

Race-safe OAuth refresh-token rotation utilities for apps in this repo.

Readme

@sprigr/apps-oauth-utils

Race-safe OAuth refresh-token rotation utilities for apps in this repo.

Empty today. Seeded at the start of P1 from packages/auth/src/simpro.ts in sprigr-team. That implementation has months of hardening around:

  • Persist new refresh_token BEFORE discarding old (rotation safety)
  • Single-flight refresh deduplication (concurrent handler calls don't double-refresh)
  • KV-based lock with short TTL for cross-request coordination
  • Terminal vs transient error classification (revoked tokens flag the integration as disconnected; 5xx retries)
  • 5-minute expiry buffer before considered "expired"

Do not re-derive any of this. Copy, adapt the provider-specific endpoints + response shapes, and keep the race logic intact.

Failure messages never carry the provider's raw body (0.2.0)

exchangeAuthCode and refreshOAuthToken build OAuthError.message with describeOAuthFailure, from the provider, the operation, the HTTP status, the terminal/transient classification and the two spec-defined body fields (error, error_description). The raw response body is never interpolated.

A token endpoint's error body is provider-controlled and routinely echoes back the request that failed, so it can contain the authorization code, the client_secret or the refresh_token. Apps write err.message into durable per-install audit columns, and several read those columns back to an agent tool caller or render them on the app's settings page.

A body that is not OAuth-shaped JSON is withheld, and the message says so and gives its byte length, so the omission is visible rather than silent:

google token refresh failed (400); reason=unknown; provider body withheld
  (412 bytes, not OAuth JSON; raw bodies can carry credentials and this
  string is persisted)

classifyOAuthError returns the same terminal / reason as before plus errorCode, errorDescription, unparsed and bodyLength. Destructuring callers are unaffected. The full body is still available at the call site if you want it in a console log, which is not a durable store.

Ported from sprigr/sprigr-apps#560 (PR sprigr/sprigr-apps#1453); tracked here as #41.

Bounding the refresh fetch: timeoutMs (0.3.0)

ProviderConfig.timeoutMs is an optional bound on the token-endpoint fetch that refreshOAuthToken (and so refreshAndPersist and getValidAccessToken) makes. When set, the request carries AbortSignal.timeout(timeoutMs) and a provider that never answers rejects with the runtime's AbortError instead of holding the invocation until the platform kills it. The rotation-race retry gets its own fresh bound.

It is unset by default, so existing callers keep the unbounded behaviour. Set it on scheduled paths (a cron that refreshes every actor's token), where one hung provider can spend the whole invocation budget. Leave it unset on interactive and agent paths, where a merely slow provider should still succeed.

Not for providers that rotate the refresh token on every refresh. The abort cancels the request, and it can land after the provider has already rotated. The old refresh token is then invalid, the new one is in a response nobody reads, and the install needs a reconnect. For a rotating provider, bound only the caller's wait instead: race the refresh promise against a timer, and hand the refresh itself to ctx.waitUntil so the isolate stays alive until the new token is persisted. The ServiceM8 app in sprigr-apps does exactly this for its scheduled refresh.

const config: ProviderConfig = { ...baseConfig, timeoutMs: 10_000 };
await refreshAndPersist(config, store, prefix, false);