@prodigio-io/sdk 2.0.0 → 2.1.0

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.mts CHANGED
@@ -116,6 +116,7 @@ declare class Prodigio {
116
116
  name: string;
117
117
  content: string;
118
118
  };
119
+ static destroy(): void;
119
120
  static init(config: ProdigioInitConfig): Promise<void>;
120
121
  }
121
122
 
package/dist/index.d.ts CHANGED
@@ -116,6 +116,7 @@ declare class Prodigio {
116
116
  name: string;
117
117
  content: string;
118
118
  };
119
+ static destroy(): void;
119
120
  static init(config: ProdigioInitConfig): Promise<void>;
120
121
  }
121
122
 
package/dist/index.js CHANGED
@@ -68,7 +68,6 @@ async function verifyBadgeToken(token) {
68
68
  const parts = token.split(".");
69
69
  if (parts.length !== 3) return null;
70
70
  const [headerB64, payloadB64, sigB64] = parts;
71
- const signingInput = `${headerB64}.${payloadB64}`;
72
71
  const signature = base64urlToArrayBuffer(sigB64);
73
72
  const keyData = pemToArrayBuffer(PRODIGIO_PUBLIC_KEY_PEM);
74
73
  const publicKey = await crypto.subtle.importKey(
@@ -78,12 +77,11 @@ async function verifyBadgeToken(token) {
78
77
  false,
79
78
  ["verify"]
80
79
  );
81
- const encoder = new TextEncoder();
82
80
  const valid = await crypto.subtle.verify(
83
81
  "RSASSA-PKCS1-v1_5",
84
82
  publicKey,
85
83
  signature,
86
- encoder.encode(signingInput)
84
+ new TextEncoder().encode(`${headerB64}.${payloadB64}`)
87
85
  );
88
86
  if (!valid) return null;
89
87
  const claims = JSON.parse(atob(payloadB64.replace(/-/g, "+").replace(/_/g, "/")));
@@ -106,25 +104,13 @@ function injectBadge(position = "bottom-right") {
106
104
  el.setAttribute("data-prodigio-badge", "true");
107
105
  el.setAttribute("aria-label", "Hiring powered by Prodigio");
108
106
  el.style.cssText = `
109
- position: fixed;
110
- bottom: 20px;
111
- ${side}
112
- z-index: 2147483647;
113
- display: inline-flex;
114
- align-items: center;
115
- gap: 6px;
116
- padding: 6px 10px;
117
- background: #ffffff;
118
- border: 1px solid #e5e5e5;
119
- border-radius: 20px;
120
- box-shadow: 0 2px 8px rgba(0,0,0,0.08);
107
+ position: fixed; bottom: 20px; ${side}
108
+ z-index: 2147483647; display: inline-flex; align-items: center; gap: 6px;
109
+ padding: 6px 10px; background: #ffffff; border: 1px solid #e5e5e5;
110
+ border-radius: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);
121
111
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
122
- font-size: 11px;
123
- font-weight: 500;
124
- color: #666666;
125
- text-decoration: none;
126
- cursor: pointer;
127
- transition: box-shadow 0.15s ease;
112
+ font-size: 11px; font-weight: 500; color: #666666;
113
+ text-decoration: none; cursor: pointer; transition: box-shadow 0.15s ease;
128
114
  `;
129
115
  el.innerHTML = `
130
116
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -132,7 +118,7 @@ function injectBadge(position = "bottom-right") {
132
118
  <path d="M7 7h5.5a3.5 3.5 0 0 1 0 7H7V7z" fill="#fff"/>
133
119
  <path d="M7 14h6a3 3 0 0 1 0 6H7v-6z" fill="#fff" opacity="0.6"/>
134
120
  </svg>
135
- <span>Hiring by Prodigio</span>
121
+ <span>Hiring powered by Prodigio</span>
136
122
  `;
137
123
  el.addEventListener("mouseenter", () => {
138
124
  el.style.boxShadow = "0 4px 12px rgba(0,0,0,0.12)";
@@ -149,20 +135,48 @@ function removeBadge() {
149
135
  }
150
136
  function cacheToken(apiKey, token, badgeExemptUntil, configVersion) {
151
137
  try {
152
- const entry = { token, badgeExemptUntil, configVersion, cachedAt: Date.now() };
153
- localStorage.setItem(`${BADGE_CACHE_KEY}_${apiKey}`, JSON.stringify(entry));
138
+ localStorage.setItem(`${BADGE_CACHE_KEY}_${apiKey}`, JSON.stringify({ token, badgeExemptUntil, configVersion, cachedAt: Date.now() }));
154
139
  } catch {
155
140
  }
156
141
  }
157
142
  function getCachedToken(apiKey) {
158
143
  try {
159
144
  const raw = localStorage.getItem(`${BADGE_CACHE_KEY}_${apiKey}`);
160
- if (!raw) return null;
161
- return JSON.parse(raw);
145
+ return raw ? JSON.parse(raw) : null;
162
146
  } catch {
163
147
  return null;
164
148
  }
165
149
  }
150
+ var _routeWatcherInstalled = false;
151
+ var _routeChangeCallbacks = [];
152
+ function installRouteWatcher() {
153
+ if (_routeWatcherInstalled || typeof window === "undefined") return;
154
+ _routeWatcherInstalled = true;
155
+ const dispatch = () => _routeChangeCallbacks.forEach((cb) => cb());
156
+ const orig = {
157
+ push: history.pushState.bind(history),
158
+ replace: history.replaceState.bind(history)
159
+ };
160
+ history.pushState = (...args) => {
161
+ orig.push(...args);
162
+ dispatch();
163
+ };
164
+ history.replaceState = (...args) => {
165
+ orig.replace(...args);
166
+ dispatch();
167
+ };
168
+ window.addEventListener("popstate", dispatch);
169
+ window.addEventListener("hashchange", dispatch);
170
+ }
171
+ var _initialized = false;
172
+ function normalizePath(p) {
173
+ return p.replace(/\/$/, "").split("?")[0];
174
+ }
175
+ function pathMatches(current, careersPath) {
176
+ const c = normalizePath(current);
177
+ const target = normalizePath(careersPath);
178
+ return c === target || c.startsWith(target + "/");
179
+ }
166
180
  var _Prodigio = class _Prodigio {
167
181
  constructor(config) {
168
182
  // ── Jobs ──────────────────────────────────────────────────────────────────
@@ -206,9 +220,7 @@ var _Prodigio = class _Prodigio {
206
220
  const url = new URL(`${this.baseUrl}${path}`);
207
221
  url.searchParams.set("key", this.apiKey);
208
222
  if (params) {
209
- for (const [k, v] of Object.entries(params)) {
210
- if (v !== void 0) url.searchParams.set(k, v);
211
- }
223
+ for (const [k, v] of Object.entries(params)) if (v !== void 0) url.searchParams.set(k, v);
212
224
  }
213
225
  const res = await fetch(url.toString());
214
226
  if (!res.ok) {
@@ -243,68 +255,75 @@ var _Prodigio = class _Prodigio {
243
255
  }
244
256
  static badge(poweredBy) {
245
257
  const pb = poweredBy != null ? poweredBy : _Prodigio.POWERED_BY;
246
- return {
247
- href: pb.url,
248
- "data-prodigio-badge": "true",
249
- target: "_blank",
250
- rel: "noopener noreferrer",
251
- text: pb.text
252
- };
258
+ return { href: pb.url, "data-prodigio-badge": "true", target: "_blank", rel: "noopener noreferrer", text: pb.text };
253
259
  }
254
260
  static meta() {
255
261
  return { name: "prodigio-badge", content: "true" };
256
262
  }
257
- // ── init() — v2 badge auto-injection ─────────────────────────────────────
263
+ // ── destroy() ─────────────────────────────────────────────────────────────
264
+ static destroy() {
265
+ removeBadge();
266
+ _initialized = false;
267
+ }
268
+ // ── init() ────────────────────────────────────────────────────────────────
258
269
  static async init(config) {
259
270
  var _a;
260
271
  if (typeof window === "undefined") return;
272
+ if (_initialized) return;
273
+ _initialized = true;
261
274
  const { key, badgeToken, baseUrl = DEFAULT_BASE_URL, badge: badgeConfig } = config;
262
275
  const position = (_a = badgeConfig == null ? void 0 : badgeConfig.position) != null ? _a : "bottom-right";
263
- const hostname = window.location.hostname;
264
276
  const cached = getCachedToken(key);
265
- let bestToken = null;
266
- let bestTokenRaw = null;
267
277
  const [initClaims, cachedClaims] = await Promise.all([
268
278
  badgeToken ? verifyBadgeToken(badgeToken) : Promise.resolve(null),
269
279
  cached ? verifyBadgeToken(cached.token) : Promise.resolve(null)
270
280
  ]);
281
+ let bestToken = null;
271
282
  if (initClaims && cachedClaims) {
272
- if (new Date(initClaims.badgeExemptUntil) >= new Date(cachedClaims.badgeExemptUntil)) {
273
- bestToken = initClaims;
274
- bestTokenRaw = badgeToken;
275
- } else {
276
- bestToken = cachedClaims;
277
- bestTokenRaw = cached.token;
278
- }
279
- } else if (initClaims) {
280
- bestToken = initClaims;
281
- bestTokenRaw = badgeToken;
282
- } else if (cachedClaims) {
283
- bestToken = cachedClaims;
284
- bestTokenRaw = cached.token;
283
+ bestToken = new Date(initClaims.badgeExemptUntil) >= new Date(cachedClaims.badgeExemptUntil) ? initClaims : cachedClaims;
284
+ } else {
285
+ bestToken = initClaims != null ? initClaims : cachedClaims;
285
286
  }
287
+ const hostname = window.location.hostname;
286
288
  const domainAllowed = bestToken ? bestToken.allowedDomains.length === 0 || bestToken.allowedDomains.includes(hostname) : true;
287
289
  const isExempt = bestToken !== null && !bestToken.badgeRequired && domainAllowed;
288
- if (isExempt) {
289
- removeBadge();
290
- } else if (!bestToken && cached) {
291
- const cacheAge = Date.now() - cached.cachedAt;
292
- if (cacheAge < GRACE_PERIOD_MS) {
290
+ let careersPath = null;
291
+ function evaluateBadge() {
292
+ if (isExempt) {
293
293
  removeBadge();
294
+ return;
295
+ }
296
+ if (careersPath) {
297
+ if (pathMatches(window.location.pathname, careersPath)) {
298
+ injectBadge(position);
299
+ } else {
300
+ removeBadge();
301
+ }
294
302
  } else {
303
+ if (!bestToken && cached) {
304
+ const cacheAge = Date.now() - cached.cachedAt;
305
+ if (cacheAge < GRACE_PERIOD_MS) {
306
+ removeBadge();
307
+ return;
308
+ }
309
+ }
295
310
  injectBadge(position);
296
311
  }
297
- } else {
298
- injectBadge(position);
299
312
  }
313
+ evaluateBadge();
314
+ installRouteWatcher();
315
+ _routeChangeCallbacks.push(evaluateBadge);
300
316
  setTimeout(async () => {
301
317
  var _a2, _b;
302
318
  try {
303
319
  const res = await fetch(`${baseUrl}/api/sdk/config?key=${encodeURIComponent(key)}`);
304
320
  if (!res.ok) return;
305
321
  const data = await res.json();
322
+ if (data.careersPath !== void 0) {
323
+ careersPath = data.careersPath;
324
+ }
306
325
  if (data.badgeRequired) {
307
- injectBadge(position);
326
+ evaluateBadge();
308
327
  return;
309
328
  }
310
329
  if (data.badgeToken) {
@@ -314,12 +333,13 @@ var _Prodigio = class _Prodigio {
314
333
  removeBadge();
315
334
  }
316
335
  }
336
+ evaluateBadge();
317
337
  } catch {
318
338
  }
319
339
  }, 0);
320
340
  }
321
341
  };
322
- // ── Badge (static helpers) ────────────────────────────────────────────────
342
+ // ── Badge static helpers ──────────────────────────────────────────────────
323
343
  _Prodigio.POWERED_BY = {
324
344
  text: "Hiring powered by Prodigio",
325
345
  url: "https://prodigio.io"
package/dist/index.mjs CHANGED
@@ -42,7 +42,6 @@ async function verifyBadgeToken(token) {
42
42
  const parts = token.split(".");
43
43
  if (parts.length !== 3) return null;
44
44
  const [headerB64, payloadB64, sigB64] = parts;
45
- const signingInput = `${headerB64}.${payloadB64}`;
46
45
  const signature = base64urlToArrayBuffer(sigB64);
47
46
  const keyData = pemToArrayBuffer(PRODIGIO_PUBLIC_KEY_PEM);
48
47
  const publicKey = await crypto.subtle.importKey(
@@ -52,12 +51,11 @@ async function verifyBadgeToken(token) {
52
51
  false,
53
52
  ["verify"]
54
53
  );
55
- const encoder = new TextEncoder();
56
54
  const valid = await crypto.subtle.verify(
57
55
  "RSASSA-PKCS1-v1_5",
58
56
  publicKey,
59
57
  signature,
60
- encoder.encode(signingInput)
58
+ new TextEncoder().encode(`${headerB64}.${payloadB64}`)
61
59
  );
62
60
  if (!valid) return null;
63
61
  const claims = JSON.parse(atob(payloadB64.replace(/-/g, "+").replace(/_/g, "/")));
@@ -80,25 +78,13 @@ function injectBadge(position = "bottom-right") {
80
78
  el.setAttribute("data-prodigio-badge", "true");
81
79
  el.setAttribute("aria-label", "Hiring powered by Prodigio");
82
80
  el.style.cssText = `
83
- position: fixed;
84
- bottom: 20px;
85
- ${side}
86
- z-index: 2147483647;
87
- display: inline-flex;
88
- align-items: center;
89
- gap: 6px;
90
- padding: 6px 10px;
91
- background: #ffffff;
92
- border: 1px solid #e5e5e5;
93
- border-radius: 20px;
94
- box-shadow: 0 2px 8px rgba(0,0,0,0.08);
81
+ position: fixed; bottom: 20px; ${side}
82
+ z-index: 2147483647; display: inline-flex; align-items: center; gap: 6px;
83
+ padding: 6px 10px; background: #ffffff; border: 1px solid #e5e5e5;
84
+ border-radius: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08);
95
85
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
96
- font-size: 11px;
97
- font-weight: 500;
98
- color: #666666;
99
- text-decoration: none;
100
- cursor: pointer;
101
- transition: box-shadow 0.15s ease;
86
+ font-size: 11px; font-weight: 500; color: #666666;
87
+ text-decoration: none; cursor: pointer; transition: box-shadow 0.15s ease;
102
88
  `;
103
89
  el.innerHTML = `
104
90
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -106,7 +92,7 @@ function injectBadge(position = "bottom-right") {
106
92
  <path d="M7 7h5.5a3.5 3.5 0 0 1 0 7H7V7z" fill="#fff"/>
107
93
  <path d="M7 14h6a3 3 0 0 1 0 6H7v-6z" fill="#fff" opacity="0.6"/>
108
94
  </svg>
109
- <span>Hiring by Prodigio</span>
95
+ <span>Hiring powered by Prodigio</span>
110
96
  `;
111
97
  el.addEventListener("mouseenter", () => {
112
98
  el.style.boxShadow = "0 4px 12px rgba(0,0,0,0.12)";
@@ -123,20 +109,48 @@ function removeBadge() {
123
109
  }
124
110
  function cacheToken(apiKey, token, badgeExemptUntil, configVersion) {
125
111
  try {
126
- const entry = { token, badgeExemptUntil, configVersion, cachedAt: Date.now() };
127
- localStorage.setItem(`${BADGE_CACHE_KEY}_${apiKey}`, JSON.stringify(entry));
112
+ localStorage.setItem(`${BADGE_CACHE_KEY}_${apiKey}`, JSON.stringify({ token, badgeExemptUntil, configVersion, cachedAt: Date.now() }));
128
113
  } catch {
129
114
  }
130
115
  }
131
116
  function getCachedToken(apiKey) {
132
117
  try {
133
118
  const raw = localStorage.getItem(`${BADGE_CACHE_KEY}_${apiKey}`);
134
- if (!raw) return null;
135
- return JSON.parse(raw);
119
+ return raw ? JSON.parse(raw) : null;
136
120
  } catch {
137
121
  return null;
138
122
  }
139
123
  }
124
+ var _routeWatcherInstalled = false;
125
+ var _routeChangeCallbacks = [];
126
+ function installRouteWatcher() {
127
+ if (_routeWatcherInstalled || typeof window === "undefined") return;
128
+ _routeWatcherInstalled = true;
129
+ const dispatch = () => _routeChangeCallbacks.forEach((cb) => cb());
130
+ const orig = {
131
+ push: history.pushState.bind(history),
132
+ replace: history.replaceState.bind(history)
133
+ };
134
+ history.pushState = (...args) => {
135
+ orig.push(...args);
136
+ dispatch();
137
+ };
138
+ history.replaceState = (...args) => {
139
+ orig.replace(...args);
140
+ dispatch();
141
+ };
142
+ window.addEventListener("popstate", dispatch);
143
+ window.addEventListener("hashchange", dispatch);
144
+ }
145
+ var _initialized = false;
146
+ function normalizePath(p) {
147
+ return p.replace(/\/$/, "").split("?")[0];
148
+ }
149
+ function pathMatches(current, careersPath) {
150
+ const c = normalizePath(current);
151
+ const target = normalizePath(careersPath);
152
+ return c === target || c.startsWith(target + "/");
153
+ }
140
154
  var _Prodigio = class _Prodigio {
141
155
  constructor(config) {
142
156
  // ── Jobs ──────────────────────────────────────────────────────────────────
@@ -180,9 +194,7 @@ var _Prodigio = class _Prodigio {
180
194
  const url = new URL(`${this.baseUrl}${path}`);
181
195
  url.searchParams.set("key", this.apiKey);
182
196
  if (params) {
183
- for (const [k, v] of Object.entries(params)) {
184
- if (v !== void 0) url.searchParams.set(k, v);
185
- }
197
+ for (const [k, v] of Object.entries(params)) if (v !== void 0) url.searchParams.set(k, v);
186
198
  }
187
199
  const res = await fetch(url.toString());
188
200
  if (!res.ok) {
@@ -217,68 +229,75 @@ var _Prodigio = class _Prodigio {
217
229
  }
218
230
  static badge(poweredBy) {
219
231
  const pb = poweredBy != null ? poweredBy : _Prodigio.POWERED_BY;
220
- return {
221
- href: pb.url,
222
- "data-prodigio-badge": "true",
223
- target: "_blank",
224
- rel: "noopener noreferrer",
225
- text: pb.text
226
- };
232
+ return { href: pb.url, "data-prodigio-badge": "true", target: "_blank", rel: "noopener noreferrer", text: pb.text };
227
233
  }
228
234
  static meta() {
229
235
  return { name: "prodigio-badge", content: "true" };
230
236
  }
231
- // ── init() — v2 badge auto-injection ─────────────────────────────────────
237
+ // ── destroy() ─────────────────────────────────────────────────────────────
238
+ static destroy() {
239
+ removeBadge();
240
+ _initialized = false;
241
+ }
242
+ // ── init() ────────────────────────────────────────────────────────────────
232
243
  static async init(config) {
233
244
  var _a;
234
245
  if (typeof window === "undefined") return;
246
+ if (_initialized) return;
247
+ _initialized = true;
235
248
  const { key, badgeToken, baseUrl = DEFAULT_BASE_URL, badge: badgeConfig } = config;
236
249
  const position = (_a = badgeConfig == null ? void 0 : badgeConfig.position) != null ? _a : "bottom-right";
237
- const hostname = window.location.hostname;
238
250
  const cached = getCachedToken(key);
239
- let bestToken = null;
240
- let bestTokenRaw = null;
241
251
  const [initClaims, cachedClaims] = await Promise.all([
242
252
  badgeToken ? verifyBadgeToken(badgeToken) : Promise.resolve(null),
243
253
  cached ? verifyBadgeToken(cached.token) : Promise.resolve(null)
244
254
  ]);
255
+ let bestToken = null;
245
256
  if (initClaims && cachedClaims) {
246
- if (new Date(initClaims.badgeExemptUntil) >= new Date(cachedClaims.badgeExemptUntil)) {
247
- bestToken = initClaims;
248
- bestTokenRaw = badgeToken;
249
- } else {
250
- bestToken = cachedClaims;
251
- bestTokenRaw = cached.token;
252
- }
253
- } else if (initClaims) {
254
- bestToken = initClaims;
255
- bestTokenRaw = badgeToken;
256
- } else if (cachedClaims) {
257
- bestToken = cachedClaims;
258
- bestTokenRaw = cached.token;
257
+ bestToken = new Date(initClaims.badgeExemptUntil) >= new Date(cachedClaims.badgeExemptUntil) ? initClaims : cachedClaims;
258
+ } else {
259
+ bestToken = initClaims != null ? initClaims : cachedClaims;
259
260
  }
261
+ const hostname = window.location.hostname;
260
262
  const domainAllowed = bestToken ? bestToken.allowedDomains.length === 0 || bestToken.allowedDomains.includes(hostname) : true;
261
263
  const isExempt = bestToken !== null && !bestToken.badgeRequired && domainAllowed;
262
- if (isExempt) {
263
- removeBadge();
264
- } else if (!bestToken && cached) {
265
- const cacheAge = Date.now() - cached.cachedAt;
266
- if (cacheAge < GRACE_PERIOD_MS) {
264
+ let careersPath = null;
265
+ function evaluateBadge() {
266
+ if (isExempt) {
267
267
  removeBadge();
268
+ return;
269
+ }
270
+ if (careersPath) {
271
+ if (pathMatches(window.location.pathname, careersPath)) {
272
+ injectBadge(position);
273
+ } else {
274
+ removeBadge();
275
+ }
268
276
  } else {
277
+ if (!bestToken && cached) {
278
+ const cacheAge = Date.now() - cached.cachedAt;
279
+ if (cacheAge < GRACE_PERIOD_MS) {
280
+ removeBadge();
281
+ return;
282
+ }
283
+ }
269
284
  injectBadge(position);
270
285
  }
271
- } else {
272
- injectBadge(position);
273
286
  }
287
+ evaluateBadge();
288
+ installRouteWatcher();
289
+ _routeChangeCallbacks.push(evaluateBadge);
274
290
  setTimeout(async () => {
275
291
  var _a2, _b;
276
292
  try {
277
293
  const res = await fetch(`${baseUrl}/api/sdk/config?key=${encodeURIComponent(key)}`);
278
294
  if (!res.ok) return;
279
295
  const data = await res.json();
296
+ if (data.careersPath !== void 0) {
297
+ careersPath = data.careersPath;
298
+ }
280
299
  if (data.badgeRequired) {
281
- injectBadge(position);
300
+ evaluateBadge();
282
301
  return;
283
302
  }
284
303
  if (data.badgeToken) {
@@ -288,12 +307,13 @@ var _Prodigio = class _Prodigio {
288
307
  removeBadge();
289
308
  }
290
309
  }
310
+ evaluateBadge();
291
311
  } catch {
292
312
  }
293
313
  }, 0);
294
314
  }
295
315
  };
296
- // ── Badge (static helpers) ────────────────────────────────────────────────
316
+ // ── Badge static helpers ──────────────────────────────────────────────────
297
317
  _Prodigio.POWERED_BY = {
298
318
  text: "Hiring powered by Prodigio",
299
319
  url: "https://prodigio.io"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prodigio-io/sdk",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Official JavaScript SDK for the Prodigio hiring API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",