@wspc/cli 0.1.8 → 0.1.10

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/cli.js CHANGED
@@ -1520,9 +1520,9 @@ function createConsistencyFetch(opts) {
1520
1520
  }
1521
1521
 
1522
1522
  // src/version.ts
1523
- var VERSION = "0.1.8";
1523
+ var VERSION = "0.1.10";
1524
1524
  var SPEC_SHA = "bec760cd";
1525
- var SPEC_FETCHED_AT = "2026-07-01T09:21:28.049Z";
1525
+ var SPEC_FETCHED_AT = "2026-07-07T06:24:39.560Z";
1526
1526
  var API_BASE = "https://api.wspc.ai";
1527
1527
 
1528
1528
  // src/index.ts
@@ -1564,8 +1564,33 @@ function createAuthInterceptor(mode) {
1564
1564
  }
1565
1565
  let accessToken = mode.accessToken;
1566
1566
  let refreshToken = mode.refreshToken;
1567
+ let expiresAt = mode.expiresAt;
1568
+ const { baseUrl, clientId, onTokenRefresh } = mode;
1567
1569
  const fetchImpl = mode.fetchImpl ?? fetch;
1568
1570
  const now = mode.now ?? Date.now;
1571
+ const SKEW_MS = 3e4;
1572
+ async function refresh() {
1573
+ const refreshRes = await fetchImpl(`${baseUrl}/auth/oauth/token`, {
1574
+ method: "POST",
1575
+ headers: {
1576
+ "content-type": "application/x-www-form-urlencoded",
1577
+ "user-agent": USER_AGENT
1578
+ },
1579
+ body: new URLSearchParams({
1580
+ grant_type: "refresh_token",
1581
+ refresh_token: refreshToken,
1582
+ client_id: clientId
1583
+ })
1584
+ });
1585
+ if (!refreshRes.ok) {
1586
+ throw new WspcAuthExpiredError(await expiredMessage(refreshRes));
1587
+ }
1588
+ const tokens = await refreshRes.json();
1589
+ accessToken = tokens.access_token;
1590
+ refreshToken = tokens.refresh_token;
1591
+ expiresAt = now() + tokens.expires_in * 1e3;
1592
+ await onTokenRefresh({ accessToken, refreshToken, expiresAt });
1593
+ }
1569
1594
  return {
1570
1595
  async onRequest(req) {
1571
1596
  req.headers.set("authorization", `Bearer ${accessToken}`);
@@ -1573,31 +1598,12 @@ function createAuthInterceptor(mode) {
1573
1598
  return req;
1574
1599
  },
1575
1600
  async execute(req) {
1601
+ if (expiresAt !== void 0 && now() >= expiresAt - SKEW_MS) {
1602
+ await refresh();
1603
+ }
1576
1604
  const first = await fetchImpl(await this.onRequest(req.clone()));
1577
1605
  if (first.status !== 401) return first;
1578
- const refreshRes = await fetchImpl(`${mode.baseUrl}/auth/oauth/token`, {
1579
- method: "POST",
1580
- headers: {
1581
- "content-type": "application/x-www-form-urlencoded",
1582
- "user-agent": USER_AGENT
1583
- },
1584
- body: new URLSearchParams({
1585
- grant_type: "refresh_token",
1586
- refresh_token: refreshToken,
1587
- client_id: mode.clientId
1588
- })
1589
- });
1590
- if (!refreshRes.ok) {
1591
- throw new WspcAuthExpiredError(await expiredMessage(refreshRes));
1592
- }
1593
- const tokens = await refreshRes.json();
1594
- accessToken = tokens.access_token;
1595
- refreshToken = tokens.refresh_token;
1596
- await mode.onTokenRefresh({
1597
- accessToken,
1598
- refreshToken,
1599
- expiresAt: now() + tokens.expires_in * 1e3
1600
- });
1606
+ await refresh();
1601
1607
  return fetchImpl(await this.onRequest(req.clone()));
1602
1608
  }
1603
1609
  };
@@ -1654,6 +1660,7 @@ function buildInterceptor(store, resolved, fetchImpl) {
1654
1660
  return createAuthInterceptor({
1655
1661
  accessToken: creds.access_token,
1656
1662
  refreshToken: creds.refresh_token,
1663
+ expiresAt: creds.access_token_expires_at,
1657
1664
  baseUrl: apiBase,
1658
1665
  clientId,
1659
1666
  fetchImpl,
@@ -6331,6 +6338,15 @@ function createDriveRealtimeSource(args) {
6331
6338
  if (stopped || authFailed) return;
6332
6339
  clearReconnectTimer();
6333
6340
  const id = ++connectionId;
6341
+ const headers = args.headers;
6342
+ if (typeof headers !== "function") {
6343
+ openSocket(id, headers);
6344
+ return;
6345
+ }
6346
+ void headers().then((resolved) => openSocket(id, resolved)).catch((error) => closeConnection(id, error));
6347
+ }
6348
+ function openSocket(id, headers) {
6349
+ if (id !== connectionId || stopped || authFailed) return;
6334
6350
  const url = buildDriveRealtimeUrl(args.baseUrl, args.libraryId, currentRealtime);
6335
6351
  activeSocket = connect(url, {
6336
6352
  open() {
@@ -6345,7 +6361,7 @@ function createDriveRealtimeSource(args) {
6345
6361
  close(error) {
6346
6362
  closeConnection(id, error ?? "close");
6347
6363
  }
6348
- }, args.headers === void 0 ? void 0 : { headers: args.headers });
6364
+ }, headers === void 0 ? void 0 : { headers });
6349
6365
  }
6350
6366
  function closeConnection(id, error) {
6351
6367
  if (id !== connectionId || stopped || authFailed) return;
@@ -6540,6 +6556,9 @@ function isInvalidCursorReason(reason) {
6540
6556
  return /\bcursor[_ -]?(invalid|expired|gone|missing|not[_ -]?found)\b|\binvalid[_ -]?cursor\b/i.test(reason);
6541
6557
  }
6542
6558
  function isRealtimeAuthError(error) {
6559
+ if (typeof error === "object" && error !== null && error.code === "WSPC_AUTH_EXPIRED") {
6560
+ return true;
6561
+ }
6543
6562
  return /\b(401|403|auth|authorization|unauthorized|forbidden)\b/i.test(String(error));
6544
6563
  }
6545
6564
  function nativeWebSocketConnector(url, handlers, init) {
@@ -6683,16 +6702,17 @@ async function runDriveWatch(root, options = {}) {
6683
6702
  realtimeSource = options.once ? void 0 : options.realtimeSource;
6684
6703
  if (!options.once && realtimeSource === void 0) {
6685
6704
  state = await ensureDriveRealtimeState(root);
6686
- const { baseUrl, headers } = await loadRealtimeAuthHeaders({
6687
- verifyPath: `/drive/libraries/${encodeURIComponent(state.library_id)}`
6688
- });
6705
+ const verifyPath = `/drive/libraries/${encodeURIComponent(state.library_id)}`;
6706
+ const { baseUrl } = await loadRealtimeAuthHeaders({ verifyPath });
6689
6707
  const realtime = state.realtime;
6690
6708
  if (realtime === void 0) {
6691
6709
  throw new Error("drive realtime state is required");
6692
6710
  }
6693
6711
  realtimeSource = createDriveRealtimeSource({
6694
6712
  baseUrl,
6695
- headers,
6713
+ // Tokens expire while watching; resolve fresh headers on every
6714
+ // (re)connect so an idle drop cannot loop on a stale snapshot.
6715
+ headers: async () => (await loadRealtimeAuthHeaders({ verifyPath })).headers,
6696
6716
  libraryId: state.library_id,
6697
6717
  realtime,
6698
6718
  writeRealtimeState: (next) => writeDriveRealtimeState(root, next)