@patchstack/connect 0.3.22 → 0.3.24

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.d.ts CHANGED
@@ -29,6 +29,18 @@ interface Config {
29
29
  * UUID it returns should be persisted via `persistSiteUuid()`.
30
30
  */
31
31
  siteUuid: string | null;
32
+ /**
33
+ * WP-format site API key (`{oauth.secret}-{oauth.id}`) for authenticated
34
+ * block-log reporting via connector `/api/logs/log`. Issued once as `api_key`
35
+ * on first provision. Prefer `PATCHSTACK_API_KEY` in production deploys.
36
+ */
37
+ apiKey: string | null;
38
+ /**
39
+ * Credential for the authenticated Pulse endpoints (ADR-0018). Exchanged for
40
+ * a short-lived bearer token at `monitor/pulse/token`; never sent directly.
41
+ * Falls back to `apiKey` when unset. Prefer `PATCHSTACK_PULSE_AUTH`.
42
+ */
43
+ pulseAuth: string | null;
32
44
  endpoint: string;
33
45
  timeoutMs: number;
34
46
  /** Environment to report the manifest under. Defaults to 'production'. */
@@ -43,6 +55,11 @@ interface Config {
43
55
  interface StoreManifestResponse {
44
56
  /** The UUID of the site the manifest was stored against. Always returned. */
45
57
  uuid?: string;
58
+ /**
59
+ * WP-format API key for connector log ingest. Present only when oauth
60
+ * credentials are created in this request (first provision / backfill).
61
+ */
62
+ api_key?: string;
46
63
  stored: boolean;
47
64
  manifest_id?: number;
48
65
  checksum?: string;
@@ -101,6 +118,14 @@ declare function postManifest(config: Config, payload: WirePayload): Promise<Sto
101
118
 
102
119
  interface ConfigFile {
103
120
  siteUuid?: string;
121
+ /** WP-format `{secret}-{oauth.id}` for connector /api/logs/log. Server-only. */
122
+ apiKey?: string;
123
+ /**
124
+ * Credential for the authenticated Pulse endpoints (ADR-0018). Same format as
125
+ * `apiKey` and today the same value, but kept as its own field so the Pulse
126
+ * and block-log paths can diverge without disturbing each other. Server-only.
127
+ */
128
+ pulseAuth?: string;
104
129
  endpoint?: string;
105
130
  timeoutMs?: number;
106
131
  environment?: string;
@@ -122,9 +147,19 @@ declare function resolveConfig(options: ResolveConfigOptions): Promise<Config>;
122
147
  declare function writeConfigFile(cwd: string, config: ConfigFile): Promise<string>;
123
148
  /**
124
149
  * Merge a new siteUuid into the existing `.patchstackrc.json` (or create it).
125
- * Preserves any `endpoint` / `timeoutMs` the user already wrote.
150
+ * Preserves any `endpoint` / `timeoutMs` / `apiKey` the user already wrote.
126
151
  */
127
152
  declare function persistSiteUuid(cwd: string, siteUuid: string): Promise<string>;
153
+ /**
154
+ * Persist the WP-format api_key issued at provision (for connector log auth).
155
+ * Never embed this value in the public disclosure widget.
156
+ */
157
+ declare function persistApiKey(cwd: string, apiKey: string): Promise<string>;
158
+ /**
159
+ * Persist the credential used for the authenticated Pulse endpoints.
160
+ * Kept separate from `apiKey` so block-log auth is never disturbed.
161
+ */
162
+ declare function persistPulseAuth(cwd: string, pulseAuth: string): Promise<string>;
128
163
 
129
164
  /**
130
165
  * A best-effort description of the stack a build was produced with, derived
@@ -218,4 +253,4 @@ interface ScanAndReportResult {
218
253
  }
219
254
  declare function scanAndReport(options?: ScanAndReportOptions): Promise<ScanAndReportResult>;
220
255
 
221
- export { type Config, DEFAULT_ENDPOINT, type Ecosystem, type Manifest, type PackageEntry, PatchstackError, type ScanAndReportOptions, type ScanAndReportResult, type SourceWidgetResult, type StackDescriptor, type StoreManifestResponse, WIDGET_MARKER_ATTR, WIDGET_SCRIPT_URL, type WidgetEnsureAction, type WidgetEnsureResult, buildClaimUrl, buildEndpointUrl, buildWidgetTag, buildWirePayload, collectHostingEnvKeys, compareVersions, detectLockfile, detectStack, ensureSourceWidget, ensureWidgetInHtml, findSourceShell, isEmptyStack, persistSiteUuid, postManifest, resolveConfig, scanAndReport, scanLockfile, writeConfigFile };
256
+ export { type Config, DEFAULT_ENDPOINT, type Ecosystem, type Manifest, type PackageEntry, PatchstackError, type ScanAndReportOptions, type ScanAndReportResult, type SourceWidgetResult, type StackDescriptor, type StoreManifestResponse, WIDGET_MARKER_ATTR, WIDGET_SCRIPT_URL, type WidgetEnsureAction, type WidgetEnsureResult, buildClaimUrl, buildEndpointUrl, buildWidgetTag, buildWirePayload, collectHostingEnvKeys, compareVersions, detectLockfile, detectStack, ensureSourceWidget, ensureWidgetInHtml, findSourceShell, isEmptyStack, persistApiKey, persistPulseAuth, persistSiteUuid, postManifest, resolveConfig, scanAndReport, scanLockfile, writeConfigFile };
package/dist/index.js CHANGED
@@ -907,6 +907,88 @@ function compareSegments(a, b) {
907
907
  return 0;
908
908
  }
909
909
 
910
+ // src/pulse-token.ts
911
+ var TOKEN_SKEW_MS = 6e4;
912
+ function buildTokenUrl(manifestEndpoint) {
913
+ const url = new URL(manifestEndpoint);
914
+ const path8 = url.pathname.replace(/\/$/, "");
915
+ url.pathname = path8.endsWith("/manifest") ? `${path8.slice(0, -"/manifest".length)}/token` : "/monitor/pulse/token";
916
+ url.search = "";
917
+ url.hash = "";
918
+ return url.toString();
919
+ }
920
+ function parsePulseAuth(credential) {
921
+ const index = credential.lastIndexOf("-");
922
+ if (index <= 0 || index === credential.length - 1) return null;
923
+ const clientId = credential.slice(index + 1);
924
+ if (!/^\d+$/.test(clientId)) return null;
925
+ return { clientId, clientSecret: credential.slice(0, index) };
926
+ }
927
+ var cached = null;
928
+ var inflight = null;
929
+ function clearPulseToken() {
930
+ cached = null;
931
+ }
932
+ async function getPulseToken(config, fetchImpl = fetch) {
933
+ if (typeof config.pulseAuth !== "string" || config.pulseAuth.length === 0) return null;
934
+ if (cached !== null && Date.now() < cached.expiresAt - TOKEN_SKEW_MS) {
935
+ return cached.token;
936
+ }
937
+ if (inflight !== null) return inflight;
938
+ const credentials = parsePulseAuth(config.pulseAuth);
939
+ if (credentials === null) return null;
940
+ inflight = (async () => {
941
+ try {
942
+ const response = await fetchImpl(buildTokenUrl(config.endpoint), {
943
+ method: "POST",
944
+ headers: {
945
+ "Content-Type": "application/json",
946
+ Accept: "application/json",
947
+ "User-Agent": "@patchstack/connect"
948
+ },
949
+ body: JSON.stringify({
950
+ grant_type: "client_credentials",
951
+ client_id: credentials.clientId,
952
+ client_secret: credentials.clientSecret
953
+ }),
954
+ signal: AbortSignal.timeout(config.timeoutMs)
955
+ });
956
+ if (!response.ok) return null;
957
+ const body = await response.json();
958
+ if (typeof body.access_token !== "string" || body.access_token.length === 0) return null;
959
+ const expiresIn = Number(body.expires_in);
960
+ const ttlMs = Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn * 1e3 : 36e5;
961
+ cached = { token: body.access_token, expiresAt: Date.now() + ttlMs };
962
+ return body.access_token;
963
+ } catch {
964
+ return null;
965
+ } finally {
966
+ inflight = null;
967
+ }
968
+ })();
969
+ return inflight;
970
+ }
971
+ async function pulseAuthHeader(config, fetchImpl = fetch) {
972
+ const token = await getPulseToken(config, fetchImpl);
973
+ return token === null ? {} : { Authorization: `Bearer ${token}` };
974
+ }
975
+ async function pulseFetch(config, url, init, fetchImpl = fetch) {
976
+ const send = async () => {
977
+ const auth = await pulseAuthHeader(config, fetchImpl);
978
+ const response = await fetchImpl(url, {
979
+ ...init,
980
+ headers: { ...init.headers, ...auth }
981
+ });
982
+ return { response, authenticated: auth.Authorization !== void 0 };
983
+ };
984
+ const first = await send();
985
+ if (first.response.status === 401 && first.authenticated) {
986
+ clearPulseToken();
987
+ return (await send()).response;
988
+ }
989
+ return first.response;
990
+ }
991
+
910
992
  // src/client.ts
911
993
  var DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest";
912
994
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -923,7 +1005,7 @@ async function postManifest(config, payload) {
923
1005
  const timeoutMs = config.timeoutMs;
924
1006
  let response;
925
1007
  try {
926
- response = await fetch(url, {
1008
+ response = await pulseFetch(config, url, {
927
1009
  method: "POST",
928
1010
  headers: {
929
1011
  "Content-Type": "application/json",
@@ -1015,8 +1097,12 @@ async function resolveConfig(options) {
1015
1097
  "CONFIG_MISSING"
1016
1098
  );
1017
1099
  }
1100
+ const apiKeyRaw = fromEnv.apiKey ?? fromFile.apiKey ?? null;
1101
+ const pulseAuthRaw = fromEnv.pulseAuth ?? fromFile.pulseAuth ?? apiKeyRaw;
1018
1102
  return {
1019
1103
  siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,
1104
+ apiKey: apiKeyRaw === null || apiKeyRaw.length === 0 ? null : apiKeyRaw,
1105
+ pulseAuth: pulseAuthRaw === null || pulseAuthRaw.length === 0 ? null : pulseAuthRaw,
1020
1106
  endpoint,
1021
1107
  timeoutMs,
1022
1108
  environment,
@@ -1033,6 +1119,14 @@ async function persistSiteUuid(cwd, siteUuid) {
1033
1119
  const existing = await readConfigFile(cwd);
1034
1120
  return writeConfigFile(cwd, { ...existing, siteUuid });
1035
1121
  }
1122
+ async function persistApiKey(cwd, apiKey) {
1123
+ const existing = await readConfigFile(cwd);
1124
+ return writeConfigFile(cwd, { ...existing, apiKey });
1125
+ }
1126
+ async function persistPulseAuth(cwd, pulseAuth) {
1127
+ const existing = await readConfigFile(cwd);
1128
+ return writeConfigFile(cwd, { ...existing, pulseAuth });
1129
+ }
1036
1130
  async function readConfigFile(cwd) {
1037
1131
  const target = path6.join(cwd, CONFIG_FILENAME);
1038
1132
  let raw;
@@ -1074,6 +1168,8 @@ function readEnv() {
1074
1168
  const environmentRaw = process.env.PATCHSTACK_ENVIRONMENT;
1075
1169
  return {
1076
1170
  siteUuid: process.env.PATCHSTACK_SITE_UUID ?? void 0,
1171
+ apiKey: process.env.PATCHSTACK_API_KEY ?? void 0,
1172
+ pulseAuth: process.env.PATCHSTACK_PULSE_AUTH ?? void 0,
1077
1173
  endpoint: process.env.PATCHSTACK_ENDPOINT ?? void 0,
1078
1174
  timeoutMs,
1079
1175
  environment: environmentRaw !== void 0 && environmentRaw.length > 0 ? environmentRaw : void 0
@@ -1242,6 +1338,10 @@ async function scanAndReport(options = {}) {
1242
1338
  if (config.siteUuid === null && response.uuid !== void 0 && response.uuid.length > 0) {
1243
1339
  await persistSiteUuid(cwd, response.uuid);
1244
1340
  }
1341
+ if (typeof response.api_key === "string" && response.api_key.length > 0) {
1342
+ await persistApiKey(cwd, response.api_key);
1343
+ await persistPulseAuth(cwd, response.api_key);
1344
+ }
1245
1345
  return {
1246
1346
  manifest,
1247
1347
  response,
@@ -1267,6 +1367,8 @@ export {
1267
1367
  ensureWidgetInHtml,
1268
1368
  findSourceShell,
1269
1369
  isEmptyStack,
1370
+ persistApiKey,
1371
+ persistPulseAuth,
1270
1372
  persistSiteUuid,
1271
1373
  postManifest,
1272
1374
  resolveConfig,