@trops/dash-core 0.1.615 → 0.1.617

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.
@@ -9758,6 +9758,17 @@ async function pollForToken$1(deviceCode) {
9758
9758
  s.set("tokenType", data.token_type);
9759
9759
  s.set("authenticatedAt", new Date().toISOString());
9760
9760
 
9761
+ // Persist the Cognito refresh token (+ the client id / region needed to
9762
+ // use it) when the registry forwarded it. This is what lets the app mint
9763
+ // fresh access tokens directly against Cognito on expiry instead of
9764
+ // silently 401-ing every registry call after ~1h. Older registries (or a
9765
+ // browser whose Amplify storage didn't surface the refresh token) omit
9766
+ // these — the app then falls back to re-authenticating on expiry.
9767
+ if (data.refresh_token) s.set("refreshToken", data.refresh_token);
9768
+ if (data.cognito_client_id)
9769
+ s.set("cognitoClientId", data.cognito_client_id);
9770
+ if (data.cognito_region) s.set("cognitoRegion", data.cognito_region);
9771
+
9761
9772
  return {
9762
9773
  status: "authorized",
9763
9774
  token: data.access_token,
@@ -9768,6 +9779,106 @@ async function pollForToken$1(deviceCode) {
9768
9779
  throw new Error(`Unexpected response: ${response.status}`);
9769
9780
  }
9770
9781
 
9782
+ /**
9783
+ * Decode a JWT's `exp` claim (no signature verification — we only use it to
9784
+ * decide whether to pre-emptively refresh) and report whether it's expired,
9785
+ * with a 30s skew so we refresh slightly early rather than racing expiry.
9786
+ * Returns false on any parse failure so a malformed token falls through to
9787
+ * the normal request (and the reactive 401 path) rather than forcing churn.
9788
+ */
9789
+ function isAccessTokenExpired(token) {
9790
+ try {
9791
+ const seg = token.split(".")[1];
9792
+ const payload = JSON.parse(Buffer.from(seg, "base64").toString("utf8"));
9793
+ if (!payload.exp) return false;
9794
+ return Date.now() >= payload.exp * 1000 - 30000;
9795
+ } catch {
9796
+ return false;
9797
+ }
9798
+ }
9799
+
9800
+ /**
9801
+ * Mint a fresh access token from the stored Cognito refresh token via
9802
+ * Cognito's `InitiateAuth` (REFRESH_TOKEN_AUTH). The user-pool client is
9803
+ * public (no secret), so no SECRET_HASH is required. Returns true and updates
9804
+ * the stored access token on success; false if there's no refresh token or
9805
+ * Cognito rejects it (expired/revoked — caller decides whether to sign out).
9806
+ */
9807
+ async function refreshAccessToken() {
9808
+ const s = getStore$3();
9809
+ const refreshToken = s.get("refreshToken");
9810
+ const clientId = s.get("cognitoClientId");
9811
+ const region = s.get("cognitoRegion") || "us-east-1";
9812
+ if (!refreshToken || !clientId) return false;
9813
+
9814
+ try {
9815
+ const response = await fetch(
9816
+ `https://cognito-idp.${region}.amazonaws.com/`,
9817
+ {
9818
+ method: "POST",
9819
+ headers: {
9820
+ "Content-Type": "application/x-amz-json-1.1",
9821
+ "X-Amz-Target": "AWSCognitoIdentityProviderService.InitiateAuth",
9822
+ },
9823
+ body: JSON.stringify({
9824
+ AuthFlow: "REFRESH_TOKEN_AUTH",
9825
+ ClientId: clientId,
9826
+ AuthParameters: { REFRESH_TOKEN: refreshToken },
9827
+ }),
9828
+ },
9829
+ );
9830
+ if (!response.ok) return false;
9831
+
9832
+ const data = await response.json();
9833
+ const result = data.AuthenticationResult;
9834
+ if (!result || !result.AccessToken) return false;
9835
+
9836
+ s.set("accessToken", result.AccessToken);
9837
+ s.set("authenticatedAt", new Date().toISOString());
9838
+ // REFRESH_TOKEN_AUTH normally does NOT return a new refresh token, but
9839
+ // honor one if rotation is ever enabled.
9840
+ if (result.RefreshToken) s.set("refreshToken", result.RefreshToken);
9841
+ return true;
9842
+ } catch {
9843
+ return false;
9844
+ }
9845
+ }
9846
+
9847
+ /**
9848
+ * Authenticated fetch with transparent token refresh.
9849
+ *
9850
+ * - Injects the stored access token as a Bearer header (when present).
9851
+ * - Pre-emptively refreshes when the access token is already expired.
9852
+ * - On a 401, attempts one refresh + retry before giving up.
9853
+ *
9854
+ * Returns a real `Response`. Callers that need to react to a still-401
9855
+ * (refresh failed → truly signed out) check `response.status` and decide
9856
+ * whether to clear credentials. Anonymous callers (no token) just get an
9857
+ * unauthenticated request.
9858
+ */
9859
+ async function authedFetch$1(url, options = {}) {
9860
+ let stored = getStoredToken$5();
9861
+
9862
+ if (stored && isAccessTokenExpired(stored.token)) {
9863
+ if (await refreshAccessToken()) stored = getStoredToken$5();
9864
+ }
9865
+
9866
+ const withAuth = (tok) => {
9867
+ const headers = { ...(options.headers || {}) };
9868
+ if (tok) headers.Authorization = `Bearer ${tok}`;
9869
+ return fetch(url, { ...options, headers });
9870
+ };
9871
+
9872
+ let response = await withAuth(stored && stored.token);
9873
+ if (response.status === 401 && stored) {
9874
+ if (await refreshAccessToken()) {
9875
+ const fresh = getStoredToken$5();
9876
+ response = await withAuth(fresh && fresh.token);
9877
+ }
9878
+ }
9879
+ return response;
9880
+ }
9881
+
9771
9882
  /**
9772
9883
  * Get the stored auth token.
9773
9884
  *
@@ -9817,14 +9928,11 @@ async function getRegistryProfile$3() {
9817
9928
  if (!stored) return null;
9818
9929
 
9819
9930
  try {
9820
- const response = await fetch(`${REGISTRY_BASE_URL$2}/api/auth/me`, {
9821
- headers: {
9822
- Authorization: `Bearer ${stored.token}`,
9823
- },
9824
- });
9931
+ const response = await authedFetch$1(`${REGISTRY_BASE_URL$2}/api/auth/me`);
9825
9932
 
9826
9933
  if (response.status === 401) {
9827
- // Token expired or invalid — clear stored credentials
9934
+ // Still 401 after authedFetch tried to refresh — the refresh token is
9935
+ // gone/expired too, so the session is genuinely over. Clear it.
9828
9936
  clearToken$2();
9829
9937
  return null;
9830
9938
  }
@@ -9861,10 +9969,9 @@ async function updateRegistryProfile$1(updates) {
9861
9969
  if (!stored) return null;
9862
9970
 
9863
9971
  try {
9864
- const response = await fetch(`${REGISTRY_BASE_URL$2}/api/auth/me`, {
9972
+ const response = await authedFetch$1(`${REGISTRY_BASE_URL$2}/api/auth/me`, {
9865
9973
  method: "PATCH",
9866
9974
  headers: {
9867
- Authorization: `Bearer ${stored.token}`,
9868
9975
  "Content-Type": "application/json",
9869
9976
  },
9870
9977
  body: JSON.stringify(updates),
@@ -9893,11 +10000,9 @@ async function getRegistryPackages$1() {
9893
10000
  if (!stored) return null;
9894
10001
 
9895
10002
  try {
9896
- const response = await fetch(`${REGISTRY_BASE_URL$2}/api/auth/me/packages`, {
9897
- headers: {
9898
- Authorization: `Bearer ${stored.token}`,
9899
- },
9900
- });
10003
+ const response = await authedFetch$1(
10004
+ `${REGISTRY_BASE_URL$2}/api/auth/me/packages`,
10005
+ );
9901
10006
 
9902
10007
  if (response.status === 401) {
9903
10008
  clearToken$2();
@@ -9924,12 +10029,11 @@ async function updateRegistryPackage$1(scope, name, updates) {
9924
10029
  if (!stored) return null;
9925
10030
 
9926
10031
  try {
9927
- const response = await fetch(
10032
+ const response = await authedFetch$1(
9928
10033
  `${REGISTRY_BASE_URL$2}/api/packages/${encodeURIComponent(scope)}/${encodeURIComponent(name)}`,
9929
10034
  {
9930
10035
  method: "PATCH",
9931
10036
  headers: {
9932
- Authorization: `Bearer ${stored.token}`,
9933
10037
  "Content-Type": "application/json",
9934
10038
  },
9935
10039
  body: JSON.stringify(updates),
@@ -9966,13 +10070,10 @@ async function deleteRegistryPackage$1(scope, name) {
9966
10070
  }
9967
10071
 
9968
10072
  try {
9969
- const response = await fetch(
10073
+ const response = await authedFetch$1(
9970
10074
  `${REGISTRY_BASE_URL$2}/api/packages/${encodeURIComponent(scope)}/${encodeURIComponent(name)}`,
9971
10075
  {
9972
10076
  method: "DELETE",
9973
- headers: {
9974
- Authorization: `Bearer ${stored.token}`,
9975
- },
9976
10077
  },
9977
10078
  );
9978
10079
 
@@ -10035,6 +10136,9 @@ var registryAuthController$2 = {
10035
10136
  updateRegistryPackage: updateRegistryPackage$1,
10036
10137
  deleteRegistryPackage: deleteRegistryPackage$1,
10037
10138
  clearToken: clearToken$2,
10139
+ refreshAccessToken,
10140
+ authedFetch: authedFetch$1,
10141
+ isAccessTokenExpired,
10038
10142
  };
10039
10143
 
10040
10144
  /**
@@ -38834,18 +38938,7 @@ const path$9 = require$$1$2;
38834
38938
  const fs$7 = require$$0$3;
38835
38939
  const os$1 = require$$2;
38836
38940
  const { toPackageId } = packageId;
38837
- const { getStoredToken: getStoredToken$4 } = registryAuthController$2;
38838
-
38839
- /**
38840
- * Build request headers for the registry. When the user is signed in, we
38841
- * include the Bearer token so the server returns private packages that
38842
- * the user owns or has been granted entitlements for. Anonymous fetches
38843
- * still work and simply return only public packages.
38844
- */
38845
- function buildAuthHeaders() {
38846
- const stored = getStoredToken$4();
38847
- return stored?.token ? { Authorization: `Bearer ${stored.token}` } : {};
38848
- }
38941
+ const { getStoredToken: getStoredToken$4, authedFetch } = registryAuthController$2;
38849
38942
 
38850
38943
  // Default registry API base URL
38851
38944
  const DEFAULT_REGISTRY_API_URL = "https://main.d919rwhuzp7rj.amplifyapp.com";
@@ -38923,9 +39016,7 @@ async function fetchRegistryIndex(forceRefresh = false) {
38923
39016
  "[RegistryController] Fetching registry from:",
38924
39017
  registryUrl,
38925
39018
  );
38926
- const response = await fetch(registryUrl, {
38927
- headers: buildAuthHeaders(),
38928
- });
39019
+ const response = await authedFetch(registryUrl);
38929
39020
  if (!response.ok) {
38930
39021
  throw new Error(
38931
39022
  `Failed to fetch registry: ${response.status} ${response.statusText}`,
@@ -38940,9 +39031,7 @@ async function fetchRegistryIndex(forceRefresh = false) {
38940
39031
  `${process.env.DASH_REGISTRY_API_URL || DEFAULT_REGISTRY_API_URL}/api/packages`;
38941
39032
  console.log("[RegistryController] Fetching registry from:", registryUrl);
38942
39033
 
38943
- const response = await fetch(registryUrl, {
38944
- headers: buildAuthHeaders(),
38945
- });
39034
+ const response = await authedFetch(registryUrl);
38946
39035
  if (!response.ok) {
38947
39036
  throw new Error(
38948
39037
  `Failed to fetch registry: ${response.status} ${response.statusText}`,
@@ -39265,13 +39354,13 @@ async function checkUpdates(installedWidgets = []) {
39265
39354
  ]) {
39266
39355
  const url = `${registryBase}${endpointPath}`;
39267
39356
  try {
39268
- const response = await fetch(url, {
39357
+ // authedFetch injects the Bearer token and transparently refreshes it
39358
+ // on expiry. Auth is optional here but widens what /resolve returns
39359
+ // for private packages where ownership matters.
39360
+ const response = await authedFetch(url, {
39269
39361
  method: "POST",
39270
39362
  headers: {
39271
39363
  "Content-Type": "application/json",
39272
- // Auth is optional but widens what /resolve returns for
39273
- // private packages where ownership matters.
39274
- ...buildAuthHeaders(),
39275
39364
  },
39276
39365
  body: refsBody,
39277
39366
  });
@@ -39441,21 +39530,17 @@ async function fetchPackageSource(packageName, componentName = null) {
39441
39530
  );
39442
39531
  }
39443
39532
 
39444
- const fetchOpts = {};
39445
39533
  const registryBase =
39446
39534
  process.env.DASH_REGISTRY_API_URL ||
39447
39535
  "https://main.d919rwhuzp7rj.amplifyapp.com";
39448
- if (
39536
+ // Use authedFetch (Bearer + auto-refresh) only for registry-hosted URLs;
39537
+ // external download URLs (e.g. GitHub releases) must NOT receive our token.
39538
+ const isRegistryUrl =
39449
39539
  pkg.downloadUrl.includes(registryBase) ||
39450
- pkg.downloadUrl.includes("/api/packages/")
39451
- ) {
39452
- const auth = getStoredToken$4();
39453
- if (auth?.token) {
39454
- fetchOpts.headers = { Authorization: `Bearer ${auth.token}` };
39455
- }
39456
- }
39457
-
39458
- const response = await fetch(pkg.downloadUrl, fetchOpts);
39540
+ pkg.downloadUrl.includes("/api/packages/");
39541
+ const response = isRegistryUrl
39542
+ ? await authedFetch(pkg.downloadUrl)
39543
+ : await fetch(pkg.downloadUrl);
39459
39544
  if (!response.ok) {
39460
39545
  if (response.status === 401) {
39461
39546
  throw new Error("Authentication required to preview this package");
@@ -39630,21 +39715,17 @@ async function fetchPackageManifest(packageName) {
39630
39715
  );
39631
39716
  }
39632
39717
 
39633
- const fetchOpts = {};
39634
39718
  const registryBase =
39635
39719
  process.env.DASH_REGISTRY_API_URL ||
39636
39720
  "https://main.d919rwhuzp7rj.amplifyapp.com";
39637
- if (
39721
+ // Use authedFetch (Bearer + auto-refresh) only for registry-hosted URLs;
39722
+ // external download URLs (e.g. GitHub releases) must NOT receive our token.
39723
+ const isRegistryUrl =
39638
39724
  pkg.downloadUrl.includes(registryBase) ||
39639
- pkg.downloadUrl.includes("/api/packages/")
39640
- ) {
39641
- const auth = getStoredToken$4();
39642
- if (auth?.token) {
39643
- fetchOpts.headers = { Authorization: `Bearer ${auth.token}` };
39644
- }
39645
- }
39646
-
39647
- const response = await fetch(pkg.downloadUrl, fetchOpts);
39725
+ pkg.downloadUrl.includes("/api/packages/");
39726
+ const response = isRegistryUrl
39727
+ ? await authedFetch(pkg.downloadUrl)
39728
+ : await fetch(pkg.downloadUrl);
39648
39729
  if (!response.ok) {
39649
39730
  if (response.status === 401) {
39650
39731
  throw new Error("Authentication required to fetch this package manifest");