@prodigio-io/sdk 1.0.5 → 2.0.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 +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +198 -20
- package/dist/index.mjs +198 -20
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -2,6 +2,14 @@ interface ProdigioConfig {
|
|
|
2
2
|
apiKey: string;
|
|
3
3
|
baseUrl?: string;
|
|
4
4
|
}
|
|
5
|
+
interface ProdigioInitConfig {
|
|
6
|
+
key: string;
|
|
7
|
+
badgeToken?: string;
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
badge?: {
|
|
10
|
+
position?: 'bottom-right' | 'bottom-left';
|
|
11
|
+
};
|
|
12
|
+
}
|
|
5
13
|
interface SalaryRange {
|
|
6
14
|
min: number;
|
|
7
15
|
max: number;
|
|
@@ -108,6 +116,7 @@ declare class Prodigio {
|
|
|
108
116
|
name: string;
|
|
109
117
|
content: string;
|
|
110
118
|
};
|
|
119
|
+
static init(config: ProdigioInitConfig): Promise<void>;
|
|
111
120
|
}
|
|
112
121
|
|
|
113
|
-
export { type Achievement, type Applicant, type ApplicationResult, type BadgeProps, type CVInfo, type Job, type ListJobsParams, type PoweredBy, Prodigio, type ProdigioConfig, ProdigioError, type SalaryRange, type SubmitApplicationParams, type UploadResult, Prodigio as default };
|
|
122
|
+
export { type Achievement, type Applicant, type ApplicationResult, type BadgeProps, type CVInfo, type Job, type ListJobsParams, type PoweredBy, Prodigio, type ProdigioConfig, ProdigioError, type ProdigioInitConfig, type SalaryRange, type SubmitApplicationParams, type UploadResult, Prodigio as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,14 @@ interface ProdigioConfig {
|
|
|
2
2
|
apiKey: string;
|
|
3
3
|
baseUrl?: string;
|
|
4
4
|
}
|
|
5
|
+
interface ProdigioInitConfig {
|
|
6
|
+
key: string;
|
|
7
|
+
badgeToken?: string;
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
badge?: {
|
|
10
|
+
position?: 'bottom-right' | 'bottom-left';
|
|
11
|
+
};
|
|
12
|
+
}
|
|
5
13
|
interface SalaryRange {
|
|
6
14
|
min: number;
|
|
7
15
|
max: number;
|
|
@@ -108,6 +116,7 @@ declare class Prodigio {
|
|
|
108
116
|
name: string;
|
|
109
117
|
content: string;
|
|
110
118
|
};
|
|
119
|
+
static init(config: ProdigioInitConfig): Promise<void>;
|
|
111
120
|
}
|
|
112
121
|
|
|
113
|
-
export { type Achievement, type Applicant, type ApplicationResult, type BadgeProps, type CVInfo, type Job, type ListJobsParams, type PoweredBy, Prodigio, type ProdigioConfig, ProdigioError, type SalaryRange, type SubmitApplicationParams, type UploadResult, Prodigio as default };
|
|
122
|
+
export { type Achievement, type Applicant, type ApplicationResult, type BadgeProps, type CVInfo, type Job, type ListJobsParams, type PoweredBy, Prodigio, type ProdigioConfig, ProdigioError, type ProdigioInitConfig, type SalaryRange, type SubmitApplicationParams, type UploadResult, Prodigio as default };
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,17 @@ __export(index_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(index_exports);
|
|
28
28
|
var DEFAULT_BASE_URL = "https://hire.prodigio.io";
|
|
29
|
+
var BADGE_CACHE_KEY = "prodigio_badge_token";
|
|
30
|
+
var GRACE_PERIOD_MS = 5 * 60 * 1e3;
|
|
31
|
+
var PRODIGIO_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
32
|
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy85BNWaJVQQA/AFsz612
|
|
33
|
+
I+3K94s6P7r9zoSUvxDhn2J1INyuc27EIcMOHHPDxChrzJGv6bozGbpexYSIIgQd
|
|
34
|
+
ge9Ge9P/J+THQMdUmAbidfp/mR1i0vISaaBvKfFQfIGkuXpzbQB8eEV1s0jACMSd
|
|
35
|
+
qRSKeJEKSKPf3OkWVhaWi4KxT9H14W6lJMBrE0ZaGbAog2HmG69f5+5JjvxU6TWR
|
|
36
|
+
BojS5CYGvSvAIQGnsWm3Rz/HfZoPg1VRMRkGYCbLIVyV3XjLPo/VqBGuvjZSRTk+
|
|
37
|
+
HW72H1ZvBsBnNSEKkOSp5r43Du3x2KUQdYyV7p0iIwMRnMq2enBEEb2qrrj7huln
|
|
38
|
+
BQIDAQAB
|
|
39
|
+
-----END PUBLIC KEY-----`;
|
|
29
40
|
var ProdigioError = class extends Error {
|
|
30
41
|
constructor(message, status, code) {
|
|
31
42
|
super(message);
|
|
@@ -34,6 +45,124 @@ var ProdigioError = class extends Error {
|
|
|
34
45
|
this.name = "ProdigioError";
|
|
35
46
|
}
|
|
36
47
|
};
|
|
48
|
+
function pemToArrayBuffer(pem) {
|
|
49
|
+
const base64 = pem.replace(/-----BEGIN PUBLIC KEY-----/, "").replace(/-----END PUBLIC KEY-----/, "").replace(/\s/g, "");
|
|
50
|
+
const binary = atob(base64);
|
|
51
|
+
const buf = new Uint8Array(binary.length);
|
|
52
|
+
for (let i = 0; i < binary.length; i++) buf[i] = binary.charCodeAt(i);
|
|
53
|
+
return buf.buffer;
|
|
54
|
+
}
|
|
55
|
+
function base64urlToArrayBuffer(b64url) {
|
|
56
|
+
const b64 = b64url.replace(/-/g, "+").replace(/_/g, "/").padEnd(
|
|
57
|
+
b64url.length + (4 - b64url.length % 4) % 4,
|
|
58
|
+
"="
|
|
59
|
+
);
|
|
60
|
+
const binary = atob(b64);
|
|
61
|
+
const buf = new Uint8Array(binary.length);
|
|
62
|
+
for (let i = 0; i < binary.length; i++) buf[i] = binary.charCodeAt(i);
|
|
63
|
+
return buf.buffer;
|
|
64
|
+
}
|
|
65
|
+
async function verifyBadgeToken(token) {
|
|
66
|
+
try {
|
|
67
|
+
if (typeof crypto === "undefined" || !crypto.subtle) return null;
|
|
68
|
+
const parts = token.split(".");
|
|
69
|
+
if (parts.length !== 3) return null;
|
|
70
|
+
const [headerB64, payloadB64, sigB64] = parts;
|
|
71
|
+
const signingInput = `${headerB64}.${payloadB64}`;
|
|
72
|
+
const signature = base64urlToArrayBuffer(sigB64);
|
|
73
|
+
const keyData = pemToArrayBuffer(PRODIGIO_PUBLIC_KEY_PEM);
|
|
74
|
+
const publicKey = await crypto.subtle.importKey(
|
|
75
|
+
"spki",
|
|
76
|
+
keyData,
|
|
77
|
+
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
78
|
+
false,
|
|
79
|
+
["verify"]
|
|
80
|
+
);
|
|
81
|
+
const encoder = new TextEncoder();
|
|
82
|
+
const valid = await crypto.subtle.verify(
|
|
83
|
+
"RSASSA-PKCS1-v1_5",
|
|
84
|
+
publicKey,
|
|
85
|
+
signature,
|
|
86
|
+
encoder.encode(signingInput)
|
|
87
|
+
);
|
|
88
|
+
if (!valid) return null;
|
|
89
|
+
const claims = JSON.parse(atob(payloadB64.replace(/-/g, "+").replace(/_/g, "/")));
|
|
90
|
+
if (claims.exp < Math.floor(Date.now() / 1e3)) return null;
|
|
91
|
+
return claims;
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
var BADGE_ELEMENT_ID = "prodigio-badge-widget";
|
|
97
|
+
function injectBadge(position = "bottom-right") {
|
|
98
|
+
if (typeof document === "undefined") return;
|
|
99
|
+
if (document.getElementById(BADGE_ELEMENT_ID)) return;
|
|
100
|
+
const side = position === "bottom-left" ? "left: 20px;" : "right: 20px;";
|
|
101
|
+
const el = document.createElement("a");
|
|
102
|
+
el.id = BADGE_ELEMENT_ID;
|
|
103
|
+
el.href = "https://prodigio.io";
|
|
104
|
+
el.target = "_blank";
|
|
105
|
+
el.rel = "noopener noreferrer";
|
|
106
|
+
el.setAttribute("data-prodigio-badge", "true");
|
|
107
|
+
el.setAttribute("aria-label", "Hiring powered by Prodigio");
|
|
108
|
+
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);
|
|
121
|
+
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;
|
|
128
|
+
`;
|
|
129
|
+
el.innerHTML = `
|
|
130
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
131
|
+
<rect width="24" height="24" rx="6" fill="#3730A3"/>
|
|
132
|
+
<path d="M7 7h5.5a3.5 3.5 0 0 1 0 7H7V7z" fill="#fff"/>
|
|
133
|
+
<path d="M7 14h6a3 3 0 0 1 0 6H7v-6z" fill="#fff" opacity="0.6"/>
|
|
134
|
+
</svg>
|
|
135
|
+
<span>Hiring by Prodigio</span>
|
|
136
|
+
`;
|
|
137
|
+
el.addEventListener("mouseenter", () => {
|
|
138
|
+
el.style.boxShadow = "0 4px 12px rgba(0,0,0,0.12)";
|
|
139
|
+
});
|
|
140
|
+
el.addEventListener("mouseleave", () => {
|
|
141
|
+
el.style.boxShadow = "0 2px 8px rgba(0,0,0,0.08)";
|
|
142
|
+
});
|
|
143
|
+
document.body.appendChild(el);
|
|
144
|
+
}
|
|
145
|
+
function removeBadge() {
|
|
146
|
+
var _a;
|
|
147
|
+
if (typeof document === "undefined") return;
|
|
148
|
+
(_a = document.getElementById(BADGE_ELEMENT_ID)) == null ? void 0 : _a.remove();
|
|
149
|
+
}
|
|
150
|
+
function cacheToken(apiKey, token, badgeExemptUntil, configVersion) {
|
|
151
|
+
try {
|
|
152
|
+
const entry = { token, badgeExemptUntil, configVersion, cachedAt: Date.now() };
|
|
153
|
+
localStorage.setItem(`${BADGE_CACHE_KEY}_${apiKey}`, JSON.stringify(entry));
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function getCachedToken(apiKey) {
|
|
158
|
+
try {
|
|
159
|
+
const raw = localStorage.getItem(`${BADGE_CACHE_KEY}_${apiKey}`);
|
|
160
|
+
if (!raw) return null;
|
|
161
|
+
return JSON.parse(raw);
|
|
162
|
+
} catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
37
166
|
var _Prodigio = class _Prodigio {
|
|
38
167
|
constructor(config) {
|
|
39
168
|
// ── Jobs ──────────────────────────────────────────────────────────────────
|
|
@@ -44,9 +173,7 @@ var _Prodigio = class _Prodigio {
|
|
|
44
173
|
if (params == null ? void 0 : params.department) p.department = params.department;
|
|
45
174
|
return this.get("/api/jobs", p);
|
|
46
175
|
},
|
|
47
|
-
get: (jobId) => {
|
|
48
|
-
return this.get(`/api/jobs/${jobId}`);
|
|
49
|
-
}
|
|
176
|
+
get: (jobId) => this.get(`/api/jobs/${jobId}`)
|
|
50
177
|
};
|
|
51
178
|
// ── Uploads ───────────────────────────────────────────────────────────────
|
|
52
179
|
this.upload = {
|
|
@@ -63,9 +190,7 @@ var _Prodigio = class _Prodigio {
|
|
|
63
190
|
};
|
|
64
191
|
// ── Applications ──────────────────────────────────────────────────────────
|
|
65
192
|
this.applications = {
|
|
66
|
-
submit: (params) =>
|
|
67
|
-
return this.post("/api/applications", params);
|
|
68
|
-
}
|
|
193
|
+
submit: (params) => this.post("/api/applications", params)
|
|
69
194
|
};
|
|
70
195
|
var _a;
|
|
71
196
|
if (typeof config === "string") {
|
|
@@ -96,10 +221,7 @@ var _Prodigio = class _Prodigio {
|
|
|
96
221
|
var _a;
|
|
97
222
|
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
98
223
|
method: "POST",
|
|
99
|
-
headers: {
|
|
100
|
-
"Content-Type": "application/json",
|
|
101
|
-
"x-api-key": this.apiKey
|
|
102
|
-
},
|
|
224
|
+
headers: { "Content-Type": "application/json", "x-api-key": this.apiKey },
|
|
103
225
|
body: JSON.stringify(body)
|
|
104
226
|
});
|
|
105
227
|
if (!res.ok) {
|
|
@@ -112,10 +234,7 @@ var _Prodigio = class _Prodigio {
|
|
|
112
234
|
var _a;
|
|
113
235
|
const url = new URL(`${this.baseUrl}${path}`);
|
|
114
236
|
url.searchParams.set("key", this.apiKey);
|
|
115
|
-
const res = await fetch(url.toString(), {
|
|
116
|
-
method: "POST",
|
|
117
|
-
body: formData
|
|
118
|
-
});
|
|
237
|
+
const res = await fetch(url.toString(), { method: "POST", body: formData });
|
|
119
238
|
if (!res.ok) {
|
|
120
239
|
const body = await res.json().catch(() => ({}));
|
|
121
240
|
throw new ProdigioError((_a = body.error) != null ? _a : `Upload failed: ${res.status}`, res.status);
|
|
@@ -132,16 +251,75 @@ var _Prodigio = class _Prodigio {
|
|
|
132
251
|
text: pb.text
|
|
133
252
|
};
|
|
134
253
|
}
|
|
135
|
-
// Returns the <meta> tag props for server-rendered compliance verification.
|
|
136
|
-
// Add to your page <head> — this is the most reliable detection method.
|
|
137
|
-
// Next.js: export const metadata = { other: { 'prodigio-badge': 'true' } }
|
|
138
254
|
static meta() {
|
|
139
255
|
return { name: "prodigio-badge", content: "true" };
|
|
140
256
|
}
|
|
257
|
+
// ── init() — v2 badge auto-injection ─────────────────────────────────────
|
|
258
|
+
static async init(config) {
|
|
259
|
+
var _a;
|
|
260
|
+
if (typeof window === "undefined") return;
|
|
261
|
+
const { key, badgeToken, baseUrl = DEFAULT_BASE_URL, badge: badgeConfig } = config;
|
|
262
|
+
const position = (_a = badgeConfig == null ? void 0 : badgeConfig.position) != null ? _a : "bottom-right";
|
|
263
|
+
const hostname = window.location.hostname;
|
|
264
|
+
const cached = getCachedToken(key);
|
|
265
|
+
let bestToken = null;
|
|
266
|
+
let bestTokenRaw = null;
|
|
267
|
+
const [initClaims, cachedClaims] = await Promise.all([
|
|
268
|
+
badgeToken ? verifyBadgeToken(badgeToken) : Promise.resolve(null),
|
|
269
|
+
cached ? verifyBadgeToken(cached.token) : Promise.resolve(null)
|
|
270
|
+
]);
|
|
271
|
+
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;
|
|
285
|
+
}
|
|
286
|
+
const domainAllowed = bestToken ? bestToken.allowedDomains.length === 0 || bestToken.allowedDomains.includes(hostname) : true;
|
|
287
|
+
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) {
|
|
293
|
+
removeBadge();
|
|
294
|
+
} else {
|
|
295
|
+
injectBadge(position);
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
injectBadge(position);
|
|
299
|
+
}
|
|
300
|
+
setTimeout(async () => {
|
|
301
|
+
var _a2, _b;
|
|
302
|
+
try {
|
|
303
|
+
const res = await fetch(`${baseUrl}/api/sdk/config?key=${encodeURIComponent(key)}`);
|
|
304
|
+
if (!res.ok) return;
|
|
305
|
+
const data = await res.json();
|
|
306
|
+
if (data.badgeRequired) {
|
|
307
|
+
injectBadge(position);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (data.badgeToken) {
|
|
311
|
+
const refreshedClaims = await verifyBadgeToken(data.badgeToken);
|
|
312
|
+
if (refreshedClaims && !refreshedClaims.badgeRequired) {
|
|
313
|
+
cacheToken(key, data.badgeToken, (_a2 = data.badgeExemptUntil) != null ? _a2 : "", (_b = data.configVersion) != null ? _b : 1);
|
|
314
|
+
removeBadge();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
}, 0);
|
|
320
|
+
}
|
|
141
321
|
};
|
|
142
|
-
// ── Badge
|
|
143
|
-
// Returns props to spread onto your <a> element.
|
|
144
|
-
// poweredBy is optional — defaults to Prodigio's built-in attribution.
|
|
322
|
+
// ── Badge (static helpers) ────────────────────────────────────────────────
|
|
145
323
|
_Prodigio.POWERED_BY = {
|
|
146
324
|
text: "Hiring powered by Prodigio",
|
|
147
325
|
url: "https://prodigio.io"
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
var DEFAULT_BASE_URL = "https://hire.prodigio.io";
|
|
3
|
+
var BADGE_CACHE_KEY = "prodigio_badge_token";
|
|
4
|
+
var GRACE_PERIOD_MS = 5 * 60 * 1e3;
|
|
5
|
+
var PRODIGIO_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
6
|
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy85BNWaJVQQA/AFsz612
|
|
7
|
+
I+3K94s6P7r9zoSUvxDhn2J1INyuc27EIcMOHHPDxChrzJGv6bozGbpexYSIIgQd
|
|
8
|
+
ge9Ge9P/J+THQMdUmAbidfp/mR1i0vISaaBvKfFQfIGkuXpzbQB8eEV1s0jACMSd
|
|
9
|
+
qRSKeJEKSKPf3OkWVhaWi4KxT9H14W6lJMBrE0ZaGbAog2HmG69f5+5JjvxU6TWR
|
|
10
|
+
BojS5CYGvSvAIQGnsWm3Rz/HfZoPg1VRMRkGYCbLIVyV3XjLPo/VqBGuvjZSRTk+
|
|
11
|
+
HW72H1ZvBsBnNSEKkOSp5r43Du3x2KUQdYyV7p0iIwMRnMq2enBEEb2qrrj7huln
|
|
12
|
+
BQIDAQAB
|
|
13
|
+
-----END PUBLIC KEY-----`;
|
|
3
14
|
var ProdigioError = class extends Error {
|
|
4
15
|
constructor(message, status, code) {
|
|
5
16
|
super(message);
|
|
@@ -8,6 +19,124 @@ var ProdigioError = class extends Error {
|
|
|
8
19
|
this.name = "ProdigioError";
|
|
9
20
|
}
|
|
10
21
|
};
|
|
22
|
+
function pemToArrayBuffer(pem) {
|
|
23
|
+
const base64 = pem.replace(/-----BEGIN PUBLIC KEY-----/, "").replace(/-----END PUBLIC KEY-----/, "").replace(/\s/g, "");
|
|
24
|
+
const binary = atob(base64);
|
|
25
|
+
const buf = new Uint8Array(binary.length);
|
|
26
|
+
for (let i = 0; i < binary.length; i++) buf[i] = binary.charCodeAt(i);
|
|
27
|
+
return buf.buffer;
|
|
28
|
+
}
|
|
29
|
+
function base64urlToArrayBuffer(b64url) {
|
|
30
|
+
const b64 = b64url.replace(/-/g, "+").replace(/_/g, "/").padEnd(
|
|
31
|
+
b64url.length + (4 - b64url.length % 4) % 4,
|
|
32
|
+
"="
|
|
33
|
+
);
|
|
34
|
+
const binary = atob(b64);
|
|
35
|
+
const buf = new Uint8Array(binary.length);
|
|
36
|
+
for (let i = 0; i < binary.length; i++) buf[i] = binary.charCodeAt(i);
|
|
37
|
+
return buf.buffer;
|
|
38
|
+
}
|
|
39
|
+
async function verifyBadgeToken(token) {
|
|
40
|
+
try {
|
|
41
|
+
if (typeof crypto === "undefined" || !crypto.subtle) return null;
|
|
42
|
+
const parts = token.split(".");
|
|
43
|
+
if (parts.length !== 3) return null;
|
|
44
|
+
const [headerB64, payloadB64, sigB64] = parts;
|
|
45
|
+
const signingInput = `${headerB64}.${payloadB64}`;
|
|
46
|
+
const signature = base64urlToArrayBuffer(sigB64);
|
|
47
|
+
const keyData = pemToArrayBuffer(PRODIGIO_PUBLIC_KEY_PEM);
|
|
48
|
+
const publicKey = await crypto.subtle.importKey(
|
|
49
|
+
"spki",
|
|
50
|
+
keyData,
|
|
51
|
+
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
|
52
|
+
false,
|
|
53
|
+
["verify"]
|
|
54
|
+
);
|
|
55
|
+
const encoder = new TextEncoder();
|
|
56
|
+
const valid = await crypto.subtle.verify(
|
|
57
|
+
"RSASSA-PKCS1-v1_5",
|
|
58
|
+
publicKey,
|
|
59
|
+
signature,
|
|
60
|
+
encoder.encode(signingInput)
|
|
61
|
+
);
|
|
62
|
+
if (!valid) return null;
|
|
63
|
+
const claims = JSON.parse(atob(payloadB64.replace(/-/g, "+").replace(/_/g, "/")));
|
|
64
|
+
if (claims.exp < Math.floor(Date.now() / 1e3)) return null;
|
|
65
|
+
return claims;
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
var BADGE_ELEMENT_ID = "prodigio-badge-widget";
|
|
71
|
+
function injectBadge(position = "bottom-right") {
|
|
72
|
+
if (typeof document === "undefined") return;
|
|
73
|
+
if (document.getElementById(BADGE_ELEMENT_ID)) return;
|
|
74
|
+
const side = position === "bottom-left" ? "left: 20px;" : "right: 20px;";
|
|
75
|
+
const el = document.createElement("a");
|
|
76
|
+
el.id = BADGE_ELEMENT_ID;
|
|
77
|
+
el.href = "https://prodigio.io";
|
|
78
|
+
el.target = "_blank";
|
|
79
|
+
el.rel = "noopener noreferrer";
|
|
80
|
+
el.setAttribute("data-prodigio-badge", "true");
|
|
81
|
+
el.setAttribute("aria-label", "Hiring powered by Prodigio");
|
|
82
|
+
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);
|
|
95
|
+
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;
|
|
102
|
+
`;
|
|
103
|
+
el.innerHTML = `
|
|
104
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
105
|
+
<rect width="24" height="24" rx="6" fill="#3730A3"/>
|
|
106
|
+
<path d="M7 7h5.5a3.5 3.5 0 0 1 0 7H7V7z" fill="#fff"/>
|
|
107
|
+
<path d="M7 14h6a3 3 0 0 1 0 6H7v-6z" fill="#fff" opacity="0.6"/>
|
|
108
|
+
</svg>
|
|
109
|
+
<span>Hiring by Prodigio</span>
|
|
110
|
+
`;
|
|
111
|
+
el.addEventListener("mouseenter", () => {
|
|
112
|
+
el.style.boxShadow = "0 4px 12px rgba(0,0,0,0.12)";
|
|
113
|
+
});
|
|
114
|
+
el.addEventListener("mouseleave", () => {
|
|
115
|
+
el.style.boxShadow = "0 2px 8px rgba(0,0,0,0.08)";
|
|
116
|
+
});
|
|
117
|
+
document.body.appendChild(el);
|
|
118
|
+
}
|
|
119
|
+
function removeBadge() {
|
|
120
|
+
var _a;
|
|
121
|
+
if (typeof document === "undefined") return;
|
|
122
|
+
(_a = document.getElementById(BADGE_ELEMENT_ID)) == null ? void 0 : _a.remove();
|
|
123
|
+
}
|
|
124
|
+
function cacheToken(apiKey, token, badgeExemptUntil, configVersion) {
|
|
125
|
+
try {
|
|
126
|
+
const entry = { token, badgeExemptUntil, configVersion, cachedAt: Date.now() };
|
|
127
|
+
localStorage.setItem(`${BADGE_CACHE_KEY}_${apiKey}`, JSON.stringify(entry));
|
|
128
|
+
} catch {
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function getCachedToken(apiKey) {
|
|
132
|
+
try {
|
|
133
|
+
const raw = localStorage.getItem(`${BADGE_CACHE_KEY}_${apiKey}`);
|
|
134
|
+
if (!raw) return null;
|
|
135
|
+
return JSON.parse(raw);
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
11
140
|
var _Prodigio = class _Prodigio {
|
|
12
141
|
constructor(config) {
|
|
13
142
|
// ── Jobs ──────────────────────────────────────────────────────────────────
|
|
@@ -18,9 +147,7 @@ var _Prodigio = class _Prodigio {
|
|
|
18
147
|
if (params == null ? void 0 : params.department) p.department = params.department;
|
|
19
148
|
return this.get("/api/jobs", p);
|
|
20
149
|
},
|
|
21
|
-
get: (jobId) => {
|
|
22
|
-
return this.get(`/api/jobs/${jobId}`);
|
|
23
|
-
}
|
|
150
|
+
get: (jobId) => this.get(`/api/jobs/${jobId}`)
|
|
24
151
|
};
|
|
25
152
|
// ── Uploads ───────────────────────────────────────────────────────────────
|
|
26
153
|
this.upload = {
|
|
@@ -37,9 +164,7 @@ var _Prodigio = class _Prodigio {
|
|
|
37
164
|
};
|
|
38
165
|
// ── Applications ──────────────────────────────────────────────────────────
|
|
39
166
|
this.applications = {
|
|
40
|
-
submit: (params) =>
|
|
41
|
-
return this.post("/api/applications", params);
|
|
42
|
-
}
|
|
167
|
+
submit: (params) => this.post("/api/applications", params)
|
|
43
168
|
};
|
|
44
169
|
var _a;
|
|
45
170
|
if (typeof config === "string") {
|
|
@@ -70,10 +195,7 @@ var _Prodigio = class _Prodigio {
|
|
|
70
195
|
var _a;
|
|
71
196
|
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
72
197
|
method: "POST",
|
|
73
|
-
headers: {
|
|
74
|
-
"Content-Type": "application/json",
|
|
75
|
-
"x-api-key": this.apiKey
|
|
76
|
-
},
|
|
198
|
+
headers: { "Content-Type": "application/json", "x-api-key": this.apiKey },
|
|
77
199
|
body: JSON.stringify(body)
|
|
78
200
|
});
|
|
79
201
|
if (!res.ok) {
|
|
@@ -86,10 +208,7 @@ var _Prodigio = class _Prodigio {
|
|
|
86
208
|
var _a;
|
|
87
209
|
const url = new URL(`${this.baseUrl}${path}`);
|
|
88
210
|
url.searchParams.set("key", this.apiKey);
|
|
89
|
-
const res = await fetch(url.toString(), {
|
|
90
|
-
method: "POST",
|
|
91
|
-
body: formData
|
|
92
|
-
});
|
|
211
|
+
const res = await fetch(url.toString(), { method: "POST", body: formData });
|
|
93
212
|
if (!res.ok) {
|
|
94
213
|
const body = await res.json().catch(() => ({}));
|
|
95
214
|
throw new ProdigioError((_a = body.error) != null ? _a : `Upload failed: ${res.status}`, res.status);
|
|
@@ -106,16 +225,75 @@ var _Prodigio = class _Prodigio {
|
|
|
106
225
|
text: pb.text
|
|
107
226
|
};
|
|
108
227
|
}
|
|
109
|
-
// Returns the <meta> tag props for server-rendered compliance verification.
|
|
110
|
-
// Add to your page <head> — this is the most reliable detection method.
|
|
111
|
-
// Next.js: export const metadata = { other: { 'prodigio-badge': 'true' } }
|
|
112
228
|
static meta() {
|
|
113
229
|
return { name: "prodigio-badge", content: "true" };
|
|
114
230
|
}
|
|
231
|
+
// ── init() — v2 badge auto-injection ─────────────────────────────────────
|
|
232
|
+
static async init(config) {
|
|
233
|
+
var _a;
|
|
234
|
+
if (typeof window === "undefined") return;
|
|
235
|
+
const { key, badgeToken, baseUrl = DEFAULT_BASE_URL, badge: badgeConfig } = config;
|
|
236
|
+
const position = (_a = badgeConfig == null ? void 0 : badgeConfig.position) != null ? _a : "bottom-right";
|
|
237
|
+
const hostname = window.location.hostname;
|
|
238
|
+
const cached = getCachedToken(key);
|
|
239
|
+
let bestToken = null;
|
|
240
|
+
let bestTokenRaw = null;
|
|
241
|
+
const [initClaims, cachedClaims] = await Promise.all([
|
|
242
|
+
badgeToken ? verifyBadgeToken(badgeToken) : Promise.resolve(null),
|
|
243
|
+
cached ? verifyBadgeToken(cached.token) : Promise.resolve(null)
|
|
244
|
+
]);
|
|
245
|
+
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;
|
|
259
|
+
}
|
|
260
|
+
const domainAllowed = bestToken ? bestToken.allowedDomains.length === 0 || bestToken.allowedDomains.includes(hostname) : true;
|
|
261
|
+
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) {
|
|
267
|
+
removeBadge();
|
|
268
|
+
} else {
|
|
269
|
+
injectBadge(position);
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
injectBadge(position);
|
|
273
|
+
}
|
|
274
|
+
setTimeout(async () => {
|
|
275
|
+
var _a2, _b;
|
|
276
|
+
try {
|
|
277
|
+
const res = await fetch(`${baseUrl}/api/sdk/config?key=${encodeURIComponent(key)}`);
|
|
278
|
+
if (!res.ok) return;
|
|
279
|
+
const data = await res.json();
|
|
280
|
+
if (data.badgeRequired) {
|
|
281
|
+
injectBadge(position);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (data.badgeToken) {
|
|
285
|
+
const refreshedClaims = await verifyBadgeToken(data.badgeToken);
|
|
286
|
+
if (refreshedClaims && !refreshedClaims.badgeRequired) {
|
|
287
|
+
cacheToken(key, data.badgeToken, (_a2 = data.badgeExemptUntil) != null ? _a2 : "", (_b = data.configVersion) != null ? _b : 1);
|
|
288
|
+
removeBadge();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} catch {
|
|
292
|
+
}
|
|
293
|
+
}, 0);
|
|
294
|
+
}
|
|
115
295
|
};
|
|
116
|
-
// ── Badge
|
|
117
|
-
// Returns props to spread onto your <a> element.
|
|
118
|
-
// poweredBy is optional — defaults to Prodigio's built-in attribution.
|
|
296
|
+
// ── Badge (static helpers) ────────────────────────────────────────────────
|
|
119
297
|
_Prodigio.POWERED_BY = {
|
|
120
298
|
text: "Hiring powered by Prodigio",
|
|
121
299
|
url: "https://prodigio.io"
|