@patchstack/connect 0.3.23 → 0.3.25

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.cjs CHANGED
@@ -47,6 +47,7 @@ __export(src_exports, {
47
47
  findSourceShell: () => findSourceShell,
48
48
  isEmptyStack: () => isEmptyStack,
49
49
  persistApiKey: () => persistApiKey,
50
+ persistPulseAuth: () => persistPulseAuth,
50
51
  persistSiteUuid: () => persistSiteUuid,
51
52
  postManifest: () => postManifest,
52
53
  resolveConfig: () => resolveConfig,
@@ -965,6 +966,88 @@ function compareSegments(a, b) {
965
966
  return 0;
966
967
  }
967
968
 
969
+ // src/pulse-token.ts
970
+ var TOKEN_SKEW_MS = 6e4;
971
+ function buildTokenUrl(manifestEndpoint) {
972
+ const url = new URL(manifestEndpoint);
973
+ const path8 = url.pathname.replace(/\/$/, "");
974
+ url.pathname = path8.endsWith("/manifest") ? `${path8.slice(0, -"/manifest".length)}/token` : "/monitor/pulse/token";
975
+ url.search = "";
976
+ url.hash = "";
977
+ return url.toString();
978
+ }
979
+ function parsePulseAuth(credential) {
980
+ const index = credential.lastIndexOf("-");
981
+ if (index <= 0 || index === credential.length - 1) return null;
982
+ const clientId = credential.slice(index + 1);
983
+ if (!/^\d+$/.test(clientId)) return null;
984
+ return { clientId, clientSecret: credential.slice(0, index) };
985
+ }
986
+ var cached = null;
987
+ var inflight = null;
988
+ function clearPulseToken() {
989
+ cached = null;
990
+ }
991
+ async function getPulseToken(config, fetchImpl = fetch) {
992
+ if (typeof config.pulseAuth !== "string" || config.pulseAuth.length === 0) return null;
993
+ if (cached !== null && Date.now() < cached.expiresAt - TOKEN_SKEW_MS) {
994
+ return cached.token;
995
+ }
996
+ if (inflight !== null) return inflight;
997
+ const credentials = parsePulseAuth(config.pulseAuth);
998
+ if (credentials === null) return null;
999
+ inflight = (async () => {
1000
+ try {
1001
+ const response = await fetchImpl(buildTokenUrl(config.endpoint), {
1002
+ method: "POST",
1003
+ headers: {
1004
+ "Content-Type": "application/json",
1005
+ Accept: "application/json",
1006
+ "User-Agent": "@patchstack/connect"
1007
+ },
1008
+ body: JSON.stringify({
1009
+ grant_type: "client_credentials",
1010
+ client_id: credentials.clientId,
1011
+ client_secret: credentials.clientSecret
1012
+ }),
1013
+ signal: AbortSignal.timeout(config.timeoutMs)
1014
+ });
1015
+ if (!response.ok) return null;
1016
+ const body = await response.json();
1017
+ if (typeof body.access_token !== "string" || body.access_token.length === 0) return null;
1018
+ const expiresIn = Number(body.expires_in);
1019
+ const ttlMs = Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn * 1e3 : 36e5;
1020
+ cached = { token: body.access_token, expiresAt: Date.now() + ttlMs };
1021
+ return body.access_token;
1022
+ } catch {
1023
+ return null;
1024
+ } finally {
1025
+ inflight = null;
1026
+ }
1027
+ })();
1028
+ return inflight;
1029
+ }
1030
+ async function pulseAuthHeader(config, fetchImpl = fetch) {
1031
+ const token = await getPulseToken(config, fetchImpl);
1032
+ return token === null ? {} : { Authorization: `Bearer ${token}` };
1033
+ }
1034
+ async function pulseFetch(config, url, init, fetchImpl = fetch) {
1035
+ const send = async () => {
1036
+ const auth = await pulseAuthHeader(config, fetchImpl);
1037
+ const response = await fetchImpl(url, {
1038
+ ...init,
1039
+ headers: { ...init.headers, ...auth }
1040
+ });
1041
+ return { response, authenticated: auth.Authorization !== void 0 };
1042
+ };
1043
+ const first = await send();
1044
+ if (first.response.status === 401 && first.authenticated) {
1045
+ clearPulseToken();
1046
+ return (await send()).response;
1047
+ }
1048
+ return first.response;
1049
+ }
1050
+
968
1051
  // src/client.ts
969
1052
  var DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest";
970
1053
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -981,7 +1064,7 @@ async function postManifest(config, payload) {
981
1064
  const timeoutMs = config.timeoutMs;
982
1065
  let response;
983
1066
  try {
984
- response = await fetch(url, {
1067
+ response = await pulseFetch(config, url, {
985
1068
  method: "POST",
986
1069
  headers: {
987
1070
  "Content-Type": "application/json",
@@ -1074,9 +1157,11 @@ async function resolveConfig(options) {
1074
1157
  );
1075
1158
  }
1076
1159
  const apiKeyRaw = fromEnv.apiKey ?? fromFile.apiKey ?? null;
1160
+ const pulseAuthRaw = fromEnv.pulseAuth ?? fromFile.pulseAuth ?? apiKeyRaw;
1077
1161
  return {
1078
1162
  siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,
1079
1163
  apiKey: apiKeyRaw === null || apiKeyRaw.length === 0 ? null : apiKeyRaw,
1164
+ pulseAuth: pulseAuthRaw === null || pulseAuthRaw.length === 0 ? null : pulseAuthRaw,
1080
1165
  endpoint,
1081
1166
  timeoutMs,
1082
1167
  environment,
@@ -1097,6 +1182,10 @@ async function persistApiKey(cwd, apiKey) {
1097
1182
  const existing = await readConfigFile(cwd);
1098
1183
  return writeConfigFile(cwd, { ...existing, apiKey });
1099
1184
  }
1185
+ async function persistPulseAuth(cwd, pulseAuth) {
1186
+ const existing = await readConfigFile(cwd);
1187
+ return writeConfigFile(cwd, { ...existing, pulseAuth });
1188
+ }
1100
1189
  async function readConfigFile(cwd) {
1101
1190
  const target = import_node_path6.default.join(cwd, CONFIG_FILENAME);
1102
1191
  let raw;
@@ -1139,6 +1228,7 @@ function readEnv() {
1139
1228
  return {
1140
1229
  siteUuid: process.env.PATCHSTACK_SITE_UUID ?? void 0,
1141
1230
  apiKey: process.env.PATCHSTACK_API_KEY ?? void 0,
1231
+ pulseAuth: process.env.PATCHSTACK_PULSE_AUTH ?? void 0,
1142
1232
  endpoint: process.env.PATCHSTACK_ENDPOINT ?? void 0,
1143
1233
  timeoutMs,
1144
1234
  environment: environmentRaw !== void 0 && environmentRaw.length > 0 ? environmentRaw : void 0
@@ -1309,6 +1399,7 @@ async function scanAndReport(options = {}) {
1309
1399
  }
1310
1400
  if (typeof response.api_key === "string" && response.api_key.length > 0) {
1311
1401
  await persistApiKey(cwd, response.api_key);
1402
+ await persistPulseAuth(cwd, response.api_key);
1312
1403
  }
1313
1404
  return {
1314
1405
  manifest,
@@ -1337,6 +1428,7 @@ async function scanAndReport(options = {}) {
1337
1428
  findSourceShell,
1338
1429
  isEmptyStack,
1339
1430
  persistApiKey,
1431
+ persistPulseAuth,
1340
1432
  persistSiteUuid,
1341
1433
  postManifest,
1342
1434
  resolveConfig,