@sylphx/sdk 0.10.7 → 0.11.1
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/README.md +6 -0
- package/dist/health/index.d.ts +681 -0
- package/dist/health/index.mjs.map +1 -1
- package/dist/index.d.ts +1677 -1349
- package/dist/index.mjs +2310 -1146
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs/index.d.ts +19 -0
- package/dist/nextjs/index.mjs +55 -12
- package/dist/nextjs/index.mjs.map +1 -1
- package/dist/react/index.d.ts +6 -10
- package/dist/react/index.mjs +61 -43
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/index.d.ts +17 -3
- package/dist/server/index.mjs +34 -3
- package/dist/server/index.mjs.map +1 -1
- package/dist/web-analytics.d.ts +90 -0
- package/package.json +5 -5
package/dist/index.mjs
CHANGED
|
@@ -66,8 +66,11 @@ __export(errors_exports, {
|
|
|
66
66
|
TimeoutError: () => TimeoutError,
|
|
67
67
|
ValidationError: () => ValidationError,
|
|
68
68
|
exponentialBackoff: () => exponentialBackoff,
|
|
69
|
-
getErrorCode: () =>
|
|
70
|
-
|
|
69
|
+
getErrorCode: () => getErrorCode2,
|
|
70
|
+
getErrorDetails: () => getErrorDetails2,
|
|
71
|
+
getErrorMessage: () => getErrorMessage2,
|
|
72
|
+
getSafeErrorMessage: () => getSafeErrorMessage,
|
|
73
|
+
isChallengeRequired: () => isChallengeRequired,
|
|
71
74
|
isRetryableError: () => isRetryableError,
|
|
72
75
|
isSylphxError: () => isSylphxError,
|
|
73
76
|
toSylphxError: () => toSylphxError
|
|
@@ -95,21 +98,91 @@ function isRetryableError(error) {
|
|
|
95
98
|
}
|
|
96
99
|
return false;
|
|
97
100
|
}
|
|
98
|
-
function
|
|
101
|
+
function getErrorMessage2(error, fallback = "An unknown error occurred") {
|
|
99
102
|
if (error instanceof Error) {
|
|
100
103
|
return error.message;
|
|
101
104
|
}
|
|
102
105
|
if (typeof error === "string") {
|
|
103
106
|
return error;
|
|
104
107
|
}
|
|
105
|
-
|
|
108
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
109
|
+
const message2 = error.message;
|
|
110
|
+
if (typeof message2 === "string") return message2;
|
|
111
|
+
}
|
|
112
|
+
if (error && typeof error === "object" && "response" in error) {
|
|
113
|
+
const response = error.response;
|
|
114
|
+
if (response?.data?.message) return response.data.message;
|
|
115
|
+
if (response?.data?.error) return response.data.error;
|
|
116
|
+
}
|
|
117
|
+
return fallback;
|
|
106
118
|
}
|
|
107
|
-
function
|
|
119
|
+
function getErrorCode2(error) {
|
|
108
120
|
if (error instanceof SylphxError) {
|
|
109
121
|
return error.code;
|
|
110
122
|
}
|
|
111
123
|
return "UNKNOWN";
|
|
112
124
|
}
|
|
125
|
+
function getErrorStatus(error) {
|
|
126
|
+
if (!error || typeof error !== "object") return void 0;
|
|
127
|
+
if ("status" in error && typeof error.status === "number") {
|
|
128
|
+
return error.status;
|
|
129
|
+
}
|
|
130
|
+
if ("statusCode" in error && typeof error.statusCode === "number") {
|
|
131
|
+
return error.statusCode;
|
|
132
|
+
}
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
function getSafeErrorKey(error) {
|
|
136
|
+
if (error instanceof SylphxError) return error.code;
|
|
137
|
+
if (!error || typeof error !== "object") return void 0;
|
|
138
|
+
if ("code" in error && typeof error.code === "string") {
|
|
139
|
+
return error.code;
|
|
140
|
+
}
|
|
141
|
+
const status = getErrorStatus(error);
|
|
142
|
+
return status == null ? void 0 : String(status);
|
|
143
|
+
}
|
|
144
|
+
function getErrorDetails2(error, fallbackMessage = "An unknown error occurred") {
|
|
145
|
+
const details = {
|
|
146
|
+
message: getErrorMessage2(error, fallbackMessage)
|
|
147
|
+
};
|
|
148
|
+
const code = getSafeErrorKey(error);
|
|
149
|
+
if (code) {
|
|
150
|
+
Object.assign(details, { code });
|
|
151
|
+
}
|
|
152
|
+
const status = getErrorStatus(error);
|
|
153
|
+
if (status != null) {
|
|
154
|
+
Object.assign(details, { status });
|
|
155
|
+
}
|
|
156
|
+
if (error instanceof Error) {
|
|
157
|
+
Object.assign(details, {
|
|
158
|
+
name: error.name,
|
|
159
|
+
stack: error.stack,
|
|
160
|
+
...error.cause ? { cause: error.cause } : {}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return details;
|
|
164
|
+
}
|
|
165
|
+
function getSafeErrorMessage(error, fallback = "Something went wrong. Please try again.") {
|
|
166
|
+
const errorCode = getSafeErrorKey(error);
|
|
167
|
+
if (errorCode && SAFE_ERROR_MESSAGES[errorCode]) return SAFE_ERROR_MESSAGES[errorCode];
|
|
168
|
+
if (error && typeof error === "object") {
|
|
169
|
+
const coded = error;
|
|
170
|
+
if (coded.data?.code && SAFE_ERROR_MESSAGES[coded.data.code]) {
|
|
171
|
+
return SAFE_ERROR_MESSAGES[coded.data.code];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const rawMessage = getErrorMessage2(error, "");
|
|
175
|
+
if (rawMessage && rawMessage.length < 200 && !rawMessage.includes("\n") && !rawMessage.includes("at ") && !INTERNAL_ERROR_PATTERNS.some((pattern) => pattern.test(rawMessage))) {
|
|
176
|
+
return rawMessage;
|
|
177
|
+
}
|
|
178
|
+
return fallback;
|
|
179
|
+
}
|
|
180
|
+
function isChallengeRequired(err) {
|
|
181
|
+
if (!(err instanceof Error)) return false;
|
|
182
|
+
if (err.message.includes("challenge")) return true;
|
|
183
|
+
const errWithData = err;
|
|
184
|
+
return errWithData.data?.code === "FORBIDDEN";
|
|
185
|
+
}
|
|
113
186
|
function toSylphxError(error) {
|
|
114
187
|
if (error instanceof SylphxError) {
|
|
115
188
|
return error;
|
|
@@ -140,7 +213,7 @@ function toSylphxError(error) {
|
|
|
140
213
|
}
|
|
141
214
|
return new SylphxError(error.message, { cause: error });
|
|
142
215
|
}
|
|
143
|
-
return new SylphxError(
|
|
216
|
+
return new SylphxError(getErrorMessage2(error));
|
|
144
217
|
}
|
|
145
218
|
function exponentialBackoff(attempt, baseDelay = BASE_RETRY_DELAY_MS, maxDelay = MAX_RETRY_DELAY_MS) {
|
|
146
219
|
const exponentialDelay = baseDelay * 2 ** attempt;
|
|
@@ -148,7 +221,7 @@ function exponentialBackoff(attempt, baseDelay = BASE_RETRY_DELAY_MS, maxDelay =
|
|
|
148
221
|
const jitter = cappedDelay * 0.25 * (Math.random() * 2 - 1);
|
|
149
222
|
return Math.round(cappedDelay + jitter);
|
|
150
223
|
}
|
|
151
|
-
var ERROR_CODE_STATUS, RETRYABLE_CODES, SylphxError, NetworkError, TimeoutError, AuthenticationError, AuthorizationError, ValidationError, RateLimitError, NotFoundError;
|
|
224
|
+
var ERROR_CODE_STATUS, RETRYABLE_CODES, SylphxError, NetworkError, TimeoutError, AuthenticationError, AuthorizationError, ValidationError, RateLimitError, NotFoundError, INTERNAL_ERROR_PATTERNS, SAFE_ERROR_MESSAGES;
|
|
152
225
|
var init_errors = __esm({
|
|
153
226
|
"src/errors.ts"() {
|
|
154
227
|
"use strict";
|
|
@@ -376,6 +449,57 @@ var init_errors = __esm({
|
|
|
376
449
|
this.resourceId = options?.resourceId;
|
|
377
450
|
}
|
|
378
451
|
};
|
|
452
|
+
INTERNAL_ERROR_PATTERNS = [
|
|
453
|
+
/sql/i,
|
|
454
|
+
/database/i,
|
|
455
|
+
/postgres/i,
|
|
456
|
+
/neon/i,
|
|
457
|
+
/drizzle/i,
|
|
458
|
+
/prisma/i,
|
|
459
|
+
/constraint/i,
|
|
460
|
+
/foreign key/i,
|
|
461
|
+
/unique violation/i,
|
|
462
|
+
/null value/i,
|
|
463
|
+
/column/i,
|
|
464
|
+
/table/i,
|
|
465
|
+
/relation/i,
|
|
466
|
+
/trpc/i,
|
|
467
|
+
/internal server/i,
|
|
468
|
+
/unexpected/i,
|
|
469
|
+
/cannot read propert/i,
|
|
470
|
+
/undefined is not/i,
|
|
471
|
+
/null is not/i,
|
|
472
|
+
/cannot find module/i,
|
|
473
|
+
/econnrefused/i,
|
|
474
|
+
/etimedout/i,
|
|
475
|
+
/enotfound/i
|
|
476
|
+
];
|
|
477
|
+
SAFE_ERROR_MESSAGES = {
|
|
478
|
+
"400": "Invalid request. Please check your input and try again.",
|
|
479
|
+
"401": "Please sign in to continue.",
|
|
480
|
+
"403": "You don't have permission to perform this action.",
|
|
481
|
+
"404": "The requested resource was not found.",
|
|
482
|
+
"409": "This action conflicts with existing data. Please refresh and try again.",
|
|
483
|
+
"429": "Too many requests. Please wait a moment and try again.",
|
|
484
|
+
"500": "Something went wrong on our end. Please try again later.",
|
|
485
|
+
"502": "Service temporarily unavailable. Please try again in a moment.",
|
|
486
|
+
"503": "Service temporarily unavailable. Please try again in a moment.",
|
|
487
|
+
BAD_REQUEST: "Invalid request. Please check your input.",
|
|
488
|
+
UNAUTHORIZED: "Please sign in to continue.",
|
|
489
|
+
FORBIDDEN: "You don't have permission to perform this action.",
|
|
490
|
+
NOT_FOUND: "The requested item was not found.",
|
|
491
|
+
CONFLICT: "This action conflicts with existing data.",
|
|
492
|
+
TOO_MANY_REQUESTS: "Too many requests. Please wait a moment.",
|
|
493
|
+
INTERNAL_SERVER_ERROR: "Something went wrong. Please try again later.",
|
|
494
|
+
TIMEOUT: "Request timed out. Please try again.",
|
|
495
|
+
PRECONDITION_FAILED: "This action cannot be completed right now.",
|
|
496
|
+
QUOTA_EXCEEDED: "You've reached your usage limit. Please upgrade your plan.",
|
|
497
|
+
PAYMENT_REQUIRED: "Payment is required to continue.",
|
|
498
|
+
INVALID_CREDENTIALS: "Invalid email or password.",
|
|
499
|
+
EMAIL_NOT_VERIFIED: "Please verify your email address.",
|
|
500
|
+
ACCOUNT_LOCKED: "Your account has been locked. Please contact support.",
|
|
501
|
+
SESSION_EXPIRED: "Your session has expired. Please sign in again."
|
|
502
|
+
};
|
|
379
503
|
}
|
|
380
504
|
});
|
|
381
505
|
|
|
@@ -4780,8 +4904,112 @@ var init_webapi = __esm({
|
|
|
4780
4904
|
}
|
|
4781
4905
|
});
|
|
4782
4906
|
|
|
4907
|
+
// src/compute.ts
|
|
4908
|
+
import {
|
|
4909
|
+
DEFAULT_MACHINE_SIZE,
|
|
4910
|
+
isMachineSize,
|
|
4911
|
+
MACHINE_CONFIGS,
|
|
4912
|
+
MACHINE_MAX_INSTANCES,
|
|
4913
|
+
MACHINE_RESOURCE_REQUIREMENTS,
|
|
4914
|
+
MACHINE_SIZES,
|
|
4915
|
+
parseMachineSize,
|
|
4916
|
+
resolveMachineConfig,
|
|
4917
|
+
resolveMachineMaxInstances,
|
|
4918
|
+
resolveMachineResources,
|
|
4919
|
+
resolveMachineTierResources,
|
|
4920
|
+
toPublicMachineSize
|
|
4921
|
+
} from "@sylphx/contract/compute";
|
|
4922
|
+
|
|
4923
|
+
// src/config/database-pricing.ts
|
|
4924
|
+
var COMPUTE_PRICE_PER_HOUR_MICRODOLLARS = 8e4;
|
|
4925
|
+
var FREE_COMPUTE_HOURS = 3;
|
|
4926
|
+
var STORAGE_PRICE_PER_GB_MONTH_MICRODOLLARS = 25e4;
|
|
4927
|
+
var FREE_STORAGE_GB = 0.25;
|
|
4928
|
+
var TRANSFER_PRICE_PER_GB_MICRODOLLARS = 9e4;
|
|
4929
|
+
var KV_FREE_STORAGE_GB = 0.25;
|
|
4930
|
+
var HOURS_PER_MONTH = 730;
|
|
4931
|
+
|
|
4932
|
+
// src/config/referrals.ts
|
|
4933
|
+
var DEFAULT_POINTS_REWARD = 100;
|
|
4934
|
+
var DISCOUNT_DURATION_MONTHS = 3;
|
|
4935
|
+
var DISCOUNT_PERCENT = 20;
|
|
4936
|
+
var PREMIUM_TRIAL_DAYS = 7;
|
|
4937
|
+
var REFERRAL_CODE_LENGTH = 8;
|
|
4938
|
+
var REFERRAL_CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
4939
|
+
function generateReferralCode() {
|
|
4940
|
+
const bytes = new Uint8Array(REFERRAL_CODE_LENGTH);
|
|
4941
|
+
crypto.getRandomValues(bytes);
|
|
4942
|
+
let code = "";
|
|
4943
|
+
for (let i = 0; i < REFERRAL_CODE_LENGTH; i++) {
|
|
4944
|
+
code += REFERRAL_CODE_CHARS[bytes[i] % REFERRAL_CODE_CHARS.length];
|
|
4945
|
+
}
|
|
4946
|
+
return code;
|
|
4947
|
+
}
|
|
4948
|
+
|
|
4949
|
+
// src/error-extract.ts
|
|
4950
|
+
function getErrorMessage(error, fallback = "Unknown error") {
|
|
4951
|
+
if (error instanceof Error) {
|
|
4952
|
+
return error.message;
|
|
4953
|
+
}
|
|
4954
|
+
if (typeof error === "string") {
|
|
4955
|
+
return error;
|
|
4956
|
+
}
|
|
4957
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
4958
|
+
const message2 = error.message;
|
|
4959
|
+
if (typeof message2 === "string") {
|
|
4960
|
+
return message2;
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
if (error && typeof error === "object" && "response" in error) {
|
|
4964
|
+
const response = error.response;
|
|
4965
|
+
if (response?.data?.message) return response.data.message;
|
|
4966
|
+
if (response?.data?.error) return response.data.error;
|
|
4967
|
+
}
|
|
4968
|
+
return fallback;
|
|
4969
|
+
}
|
|
4970
|
+
function getErrorCode(error) {
|
|
4971
|
+
if (!error || typeof error !== "object") {
|
|
4972
|
+
return void 0;
|
|
4973
|
+
}
|
|
4974
|
+
if ("code" in error && typeof error.code === "string") {
|
|
4975
|
+
return error.code;
|
|
4976
|
+
}
|
|
4977
|
+
if ("status" in error && typeof error.status === "number") {
|
|
4978
|
+
return String(error.status);
|
|
4979
|
+
}
|
|
4980
|
+
if ("statusCode" in error && typeof error.statusCode === "number") {
|
|
4981
|
+
return String(error.statusCode);
|
|
4982
|
+
}
|
|
4983
|
+
return void 0;
|
|
4984
|
+
}
|
|
4985
|
+
function getErrorDetails(error, fallbackMessage = "Unknown error") {
|
|
4986
|
+
const details = {
|
|
4987
|
+
message: getErrorMessage(error, fallbackMessage)
|
|
4988
|
+
};
|
|
4989
|
+
if (error instanceof Error) {
|
|
4990
|
+
details.name = error.name;
|
|
4991
|
+
details.stack = error.stack;
|
|
4992
|
+
if (error.cause) {
|
|
4993
|
+
details.cause = error.cause;
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
const code = getErrorCode(error);
|
|
4997
|
+
if (code) {
|
|
4998
|
+
details.code = code;
|
|
4999
|
+
}
|
|
5000
|
+
if (error && typeof error === "object") {
|
|
5001
|
+
if ("status" in error && typeof error.status === "number") {
|
|
5002
|
+
details.status = error.status;
|
|
5003
|
+
} else if ("statusCode" in error && typeof error.statusCode === "number") {
|
|
5004
|
+
details.status = error.statusCode;
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
5007
|
+
return details;
|
|
5008
|
+
}
|
|
5009
|
+
|
|
4783
5010
|
// src/connection-url.ts
|
|
4784
5011
|
var SYLPHX_PROTOCOL = "sylphx:";
|
|
5012
|
+
var DEFAULT_DOMAIN = "api.sylphx.com";
|
|
4785
5013
|
var DEFAULT_VERSION = "v1";
|
|
4786
5014
|
var CREDENTIAL_REGEX = /^(pk|sk)_(dev|stg|prod|prev)(?:_[a-z0-9]{12})?_[a-f0-9]{32,64}$/;
|
|
4787
5015
|
var VERSION_REGEX = /^v[0-9]+$/;
|
|
@@ -4813,6 +5041,33 @@ function validateSlug(candidate) {
|
|
|
4813
5041
|
}
|
|
4814
5042
|
return candidate;
|
|
4815
5043
|
}
|
|
5044
|
+
function validateDomain(domain) {
|
|
5045
|
+
if (!domain || domain.includes("/") || domain.includes("@") || domain.includes(" ")) {
|
|
5046
|
+
fail(`domain "${domain}" is not a valid hostname suffix`);
|
|
5047
|
+
}
|
|
5048
|
+
if (domain.includes("://")) {
|
|
5049
|
+
fail(`domain "${domain}" must not contain a scheme`);
|
|
5050
|
+
}
|
|
5051
|
+
return domain.toLowerCase();
|
|
5052
|
+
}
|
|
5053
|
+
function normaliseVersion(version) {
|
|
5054
|
+
if (version === void 0) return DEFAULT_VERSION;
|
|
5055
|
+
if (version === "") return "";
|
|
5056
|
+
if (!VERSION_REGEX.test(version)) {
|
|
5057
|
+
fail(`version "${version}" must match /^v[0-9]+$/`);
|
|
5058
|
+
}
|
|
5059
|
+
return version;
|
|
5060
|
+
}
|
|
5061
|
+
function buildConnectionUrl(input) {
|
|
5062
|
+
const { credential, slug, domain = DEFAULT_DOMAIN, version = DEFAULT_VERSION } = input;
|
|
5063
|
+
parseCredential(credential);
|
|
5064
|
+
const safeSlug = validateSlug(slug);
|
|
5065
|
+
const safeDomain = validateDomain(domain);
|
|
5066
|
+
const safeVersion = normaliseVersion(version);
|
|
5067
|
+
const host = `${safeSlug}.${safeDomain}`;
|
|
5068
|
+
const pathSuffix = safeVersion ? `/${safeVersion}` : "";
|
|
5069
|
+
return `${SYLPHX_PROTOCOL}//${credential}@${host}${pathSuffix}`;
|
|
5070
|
+
}
|
|
4816
5071
|
function parseConnectionUrl(url) {
|
|
4817
5072
|
if (typeof url !== "string" || url.length === 0) {
|
|
4818
5073
|
fail("url must be a non-empty string");
|
|
@@ -5187,6 +5442,1044 @@ async function callApi(config, path, options = {}) {
|
|
|
5187
5442
|
}
|
|
5188
5443
|
}
|
|
5189
5444
|
|
|
5445
|
+
// src/csv.ts
|
|
5446
|
+
function escapeCsvField(value) {
|
|
5447
|
+
if (value === null || value === void 0) {
|
|
5448
|
+
return "";
|
|
5449
|
+
}
|
|
5450
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
5451
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
5452
|
+
}
|
|
5453
|
+
return value;
|
|
5454
|
+
}
|
|
5455
|
+
|
|
5456
|
+
// src/formatting.ts
|
|
5457
|
+
function calculatePercentage(count, total, decimals = 2) {
|
|
5458
|
+
if (total === 0) return 100;
|
|
5459
|
+
const multiplier = 10 ** (decimals + 2);
|
|
5460
|
+
return Math.round(count / total * multiplier) / 10 ** decimals;
|
|
5461
|
+
}
|
|
5462
|
+
function formatMicrodollars(microdollars, options) {
|
|
5463
|
+
return new Intl.NumberFormat("en-US", {
|
|
5464
|
+
style: "currency",
|
|
5465
|
+
currency: "USD",
|
|
5466
|
+
...options
|
|
5467
|
+
}).format(microdollars / 1e6);
|
|
5468
|
+
}
|
|
5469
|
+
function formatCents(cents) {
|
|
5470
|
+
return new Intl.NumberFormat("en-US", {
|
|
5471
|
+
style: "currency",
|
|
5472
|
+
currency: "USD"
|
|
5473
|
+
}).format(cents / 100);
|
|
5474
|
+
}
|
|
5475
|
+
function formatCurrency(amount, optsOrCompact = false) {
|
|
5476
|
+
const opts = typeof optsOrCompact === "boolean" ? { compact: optsOrCompact } : optsOrCompact;
|
|
5477
|
+
const currency = (opts.currency ?? "USD").toUpperCase();
|
|
5478
|
+
const decimals = opts.decimals ?? 2;
|
|
5479
|
+
if (opts.compact && Math.abs(amount) >= 1e3) {
|
|
5480
|
+
return new Intl.NumberFormat("en-US", {
|
|
5481
|
+
style: "currency",
|
|
5482
|
+
currency,
|
|
5483
|
+
notation: "compact",
|
|
5484
|
+
minimumFractionDigits: 1,
|
|
5485
|
+
maximumFractionDigits: 1
|
|
5486
|
+
}).format(amount);
|
|
5487
|
+
}
|
|
5488
|
+
return new Intl.NumberFormat("en-US", {
|
|
5489
|
+
style: "currency",
|
|
5490
|
+
currency,
|
|
5491
|
+
minimumFractionDigits: decimals,
|
|
5492
|
+
maximumFractionDigits: decimals
|
|
5493
|
+
}).format(amount);
|
|
5494
|
+
}
|
|
5495
|
+
function formatPercent(value) {
|
|
5496
|
+
return `${value >= 0 ? "+" : ""}${value.toFixed(1)}%`;
|
|
5497
|
+
}
|
|
5498
|
+
function formatNumber(num, compact = false) {
|
|
5499
|
+
if (compact && num >= 1e3) {
|
|
5500
|
+
return new Intl.NumberFormat("en-US", {
|
|
5501
|
+
notation: "compact",
|
|
5502
|
+
maximumFractionDigits: 1
|
|
5503
|
+
}).format(num);
|
|
5504
|
+
}
|
|
5505
|
+
if (num >= 1e9) return `${(num / 1e9).toFixed(1)}B`;
|
|
5506
|
+
if (num >= 1e6) return `${(num / 1e6).toFixed(1)}M`;
|
|
5507
|
+
if (num >= 1e3) return `${(num / 1e3).toFixed(1)}K`;
|
|
5508
|
+
return num.toLocaleString();
|
|
5509
|
+
}
|
|
5510
|
+
function formatDuration(ms) {
|
|
5511
|
+
if (ms < 1) return "<1ms";
|
|
5512
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
5513
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
5514
|
+
}
|
|
5515
|
+
function formatBytes(bytes, decimals = 1) {
|
|
5516
|
+
if (bytes == null || bytes === 0 || Number.isNaN(bytes)) return "0 B";
|
|
5517
|
+
const k = 1024;
|
|
5518
|
+
const sizes = ["B", "KB", "MB", "GB", "TB", "PB"];
|
|
5519
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
5520
|
+
return `${Number.parseFloat((bytes / k ** i).toFixed(decimals))} ${sizes[i]}`;
|
|
5521
|
+
}
|
|
5522
|
+
var BILLING_STATUS_VARIANTS = /* @__PURE__ */ new Map([
|
|
5523
|
+
// Active/healthy states
|
|
5524
|
+
["healthy", "default"],
|
|
5525
|
+
["active", "default"],
|
|
5526
|
+
// Pending/low states
|
|
5527
|
+
["low", "secondary"],
|
|
5528
|
+
["pending", "secondary"],
|
|
5529
|
+
// Success states
|
|
5530
|
+
["paid", "success"],
|
|
5531
|
+
["completed", "success"],
|
|
5532
|
+
// Warning states
|
|
5533
|
+
["grace_period", "warning"],
|
|
5534
|
+
["overdue", "warning"],
|
|
5535
|
+
// Error states
|
|
5536
|
+
["critical", "error"],
|
|
5537
|
+
["blocked", "error"],
|
|
5538
|
+
["suspended", "error"],
|
|
5539
|
+
["failed", "error"]
|
|
5540
|
+
]);
|
|
5541
|
+
function getBillingStatusVariant(status) {
|
|
5542
|
+
return BILLING_STATUS_VARIANTS.get(status) ?? "outline";
|
|
5543
|
+
}
|
|
5544
|
+
var INVOICE_STATUS_VARIANTS = /* @__PURE__ */ new Map([
|
|
5545
|
+
["draft", "secondary"],
|
|
5546
|
+
["pending", "default"],
|
|
5547
|
+
["paid", "success"],
|
|
5548
|
+
["overdue", "warning"],
|
|
5549
|
+
["failed", "error"],
|
|
5550
|
+
["cancelled", "error"]
|
|
5551
|
+
]);
|
|
5552
|
+
function getInvoiceStatusVariant(status) {
|
|
5553
|
+
return INVOICE_STATUS_VARIANTS.get(status) ?? "outline";
|
|
5554
|
+
}
|
|
5555
|
+
function parseDate(date) {
|
|
5556
|
+
const d = typeof date === "string" ? new Date(date) : date;
|
|
5557
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
5558
|
+
}
|
|
5559
|
+
function formatDate(date, options, fallback = "-") {
|
|
5560
|
+
if (!date) return fallback;
|
|
5561
|
+
const d = parseDate(date);
|
|
5562
|
+
if (!d) return fallback;
|
|
5563
|
+
return d.toLocaleDateString("en-US", {
|
|
5564
|
+
month: "short",
|
|
5565
|
+
day: "numeric",
|
|
5566
|
+
year: "numeric",
|
|
5567
|
+
...options
|
|
5568
|
+
});
|
|
5569
|
+
}
|
|
5570
|
+
function formatDateTime(date, options, fallback = "-") {
|
|
5571
|
+
if (!date) return fallback;
|
|
5572
|
+
const d = parseDate(date);
|
|
5573
|
+
if (!d) return fallback;
|
|
5574
|
+
return d.toLocaleString("en-US", {
|
|
5575
|
+
month: "short",
|
|
5576
|
+
day: "numeric",
|
|
5577
|
+
hour: "2-digit",
|
|
5578
|
+
minute: "2-digit",
|
|
5579
|
+
...options
|
|
5580
|
+
});
|
|
5581
|
+
}
|
|
5582
|
+
var relativeTimeFormatter = new Intl.RelativeTimeFormat("en", {
|
|
5583
|
+
numeric: "auto"
|
|
5584
|
+
});
|
|
5585
|
+
var TIME_DIVISIONS = [
|
|
5586
|
+
{ amount: 60, unit: "second" },
|
|
5587
|
+
{ amount: 60, unit: "minute" },
|
|
5588
|
+
{ amount: 24, unit: "hour" },
|
|
5589
|
+
{ amount: 7, unit: "day" },
|
|
5590
|
+
{ amount: 4.34524, unit: "week" },
|
|
5591
|
+
{ amount: 12, unit: "month" },
|
|
5592
|
+
{ amount: Number.POSITIVE_INFINITY, unit: "year" }
|
|
5593
|
+
];
|
|
5594
|
+
function formatRelativeTime(date) {
|
|
5595
|
+
if (!date) return "Never";
|
|
5596
|
+
const d = parseDate(date);
|
|
5597
|
+
if (!d) return "Never";
|
|
5598
|
+
let seconds = (d.getTime() - Date.now()) / 1e3;
|
|
5599
|
+
for (const { amount, unit } of TIME_DIVISIONS) {
|
|
5600
|
+
if (Math.abs(seconds) < amount) {
|
|
5601
|
+
return relativeTimeFormatter.format(Math.round(seconds), unit);
|
|
5602
|
+
}
|
|
5603
|
+
seconds /= amount;
|
|
5604
|
+
}
|
|
5605
|
+
return formatDate(d);
|
|
5606
|
+
}
|
|
5607
|
+
function formatRelativeTimeShort(date) {
|
|
5608
|
+
if (!date) return "Never";
|
|
5609
|
+
const d = parseDate(date);
|
|
5610
|
+
if (!d) return "Never";
|
|
5611
|
+
const diffMs = Date.now() - d.getTime();
|
|
5612
|
+
const diffSecs = Math.floor(diffMs / 1e3);
|
|
5613
|
+
const diffMins = Math.floor(diffMs / 6e4);
|
|
5614
|
+
const diffHours = Math.floor(diffMs / 36e5);
|
|
5615
|
+
const diffDays = Math.floor(diffMs / 864e5);
|
|
5616
|
+
if (diffSecs < 10) return "Just now";
|
|
5617
|
+
if (diffSecs < 60) return `${diffSecs}s ago`;
|
|
5618
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
5619
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
5620
|
+
if (diffDays === 1) return "Yesterday";
|
|
5621
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
5622
|
+
if (diffDays < 30) return `${Math.floor(diffDays / 7)}w ago`;
|
|
5623
|
+
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
5624
|
+
}
|
|
5625
|
+
function formatMonthYear(date, fallback = "-") {
|
|
5626
|
+
if (!date) return fallback;
|
|
5627
|
+
const d = parseDate(date);
|
|
5628
|
+
if (!d) return fallback;
|
|
5629
|
+
return d.toLocaleDateString("en-US", { month: "long", year: "numeric" });
|
|
5630
|
+
}
|
|
5631
|
+
function formatTime(date, fallback = "-") {
|
|
5632
|
+
if (!date) return fallback;
|
|
5633
|
+
const d = parseDate(date);
|
|
5634
|
+
if (!d) return fallback;
|
|
5635
|
+
return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
|
|
5636
|
+
}
|
|
5637
|
+
|
|
5638
|
+
// src/json.ts
|
|
5639
|
+
function safeJsonParse(input, fallback) {
|
|
5640
|
+
try {
|
|
5641
|
+
return JSON.parse(input);
|
|
5642
|
+
} catch {
|
|
5643
|
+
return fallback ?? null;
|
|
5644
|
+
}
|
|
5645
|
+
}
|
|
5646
|
+
|
|
5647
|
+
// src/utils.ts
|
|
5648
|
+
function getBaseUrl(mode = "relative") {
|
|
5649
|
+
if (typeof globalThis.window !== "undefined") {
|
|
5650
|
+
return mode === "origin" ? globalThis.window.location.origin : "";
|
|
5651
|
+
}
|
|
5652
|
+
if (process.env.NEXT_PUBLIC_APP_URL) {
|
|
5653
|
+
return process.env.NEXT_PUBLIC_APP_URL;
|
|
5654
|
+
}
|
|
5655
|
+
const port = process.env.PORT ?? "3000";
|
|
5656
|
+
return `http://localhost:${port}`;
|
|
5657
|
+
}
|
|
5658
|
+
var HTML_ENTITIES = {
|
|
5659
|
+
"&": "&",
|
|
5660
|
+
"<": "<",
|
|
5661
|
+
">": ">",
|
|
5662
|
+
'"': """,
|
|
5663
|
+
"'": "'"
|
|
5664
|
+
};
|
|
5665
|
+
function escapeHtml(str) {
|
|
5666
|
+
return str.replace(/[&<>"']/g, (char) => HTML_ENTITIES[char]);
|
|
5667
|
+
}
|
|
5668
|
+
function generateSlug(text, maxLength) {
|
|
5669
|
+
const slug = text.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
|
|
5670
|
+
if (maxLength && slug.length > maxLength) {
|
|
5671
|
+
return slug.slice(0, maxLength).replace(/-+$/, "");
|
|
5672
|
+
}
|
|
5673
|
+
return slug;
|
|
5674
|
+
}
|
|
5675
|
+
|
|
5676
|
+
// src/utils/user-agent.ts
|
|
5677
|
+
function parseUserAgent(ua) {
|
|
5678
|
+
if (!ua) {
|
|
5679
|
+
return { browser: null, os: null, deviceType: null };
|
|
5680
|
+
}
|
|
5681
|
+
return {
|
|
5682
|
+
browser: detectBrowser(ua),
|
|
5683
|
+
os: detectOS(ua),
|
|
5684
|
+
deviceType: detectDeviceType(ua)
|
|
5685
|
+
};
|
|
5686
|
+
}
|
|
5687
|
+
function detectBrowser(ua) {
|
|
5688
|
+
if (ua.includes("Edg/")) {
|
|
5689
|
+
const match = ua.match(/Edg\/(\d+)/);
|
|
5690
|
+
return match ? `Edge ${match[1]}` : "Edge";
|
|
5691
|
+
}
|
|
5692
|
+
if (ua.includes("OPR/") || ua.includes("Opera")) {
|
|
5693
|
+
const match = ua.match(/OPR\/(\d+)/) || ua.match(/Opera\/(\d+)/);
|
|
5694
|
+
return match ? `Opera ${match[1]}` : "Opera";
|
|
5695
|
+
}
|
|
5696
|
+
if (ua.includes("Brave")) {
|
|
5697
|
+
return "Brave";
|
|
5698
|
+
}
|
|
5699
|
+
if (ua.includes("Chrome/")) {
|
|
5700
|
+
const match = ua.match(/Chrome\/(\d+)/);
|
|
5701
|
+
return match ? `Chrome ${match[1]}` : "Chrome";
|
|
5702
|
+
}
|
|
5703
|
+
if (ua.includes("Safari/") && !ua.includes("Chrome")) {
|
|
5704
|
+
const match = ua.match(/Version\/(\d+)/);
|
|
5705
|
+
return match ? `Safari ${match[1]}` : "Safari";
|
|
5706
|
+
}
|
|
5707
|
+
if (ua.includes("Firefox/")) {
|
|
5708
|
+
const match = ua.match(/Firefox\/(\d+)/);
|
|
5709
|
+
return match ? `Firefox ${match[1]}` : "Firefox";
|
|
5710
|
+
}
|
|
5711
|
+
if (ua.includes("MSIE") || ua.includes("Trident/")) {
|
|
5712
|
+
const match = ua.match(/(?:MSIE |rv:)(\d+)/);
|
|
5713
|
+
return match ? `IE ${match[1]}` : "Internet Explorer";
|
|
5714
|
+
}
|
|
5715
|
+
return null;
|
|
5716
|
+
}
|
|
5717
|
+
function detectOS(ua) {
|
|
5718
|
+
if (ua.includes("iPhone") || ua.includes("iPad")) {
|
|
5719
|
+
const match = ua.match(/OS (\d+[_\d]*)/);
|
|
5720
|
+
if (match) {
|
|
5721
|
+
const version = match[1].replace(/_/g, ".");
|
|
5722
|
+
return `iOS ${version}`;
|
|
5723
|
+
}
|
|
5724
|
+
return "iOS";
|
|
5725
|
+
}
|
|
5726
|
+
if (ua.includes("Android")) {
|
|
5727
|
+
const match = ua.match(/Android (\d+[.\d]*)/);
|
|
5728
|
+
return match ? `Android ${match[1]}` : "Android";
|
|
5729
|
+
}
|
|
5730
|
+
if (ua.includes("Mac OS X") || ua.includes("macOS")) {
|
|
5731
|
+
const match = ua.match(/Mac OS X (\d+[_\d]*)/) || ua.match(/macOS (\d+[_\d]*)/);
|
|
5732
|
+
if (match) {
|
|
5733
|
+
const version = match[1].replace(/_/g, ".");
|
|
5734
|
+
return `macOS ${version}`;
|
|
5735
|
+
}
|
|
5736
|
+
return "macOS";
|
|
5737
|
+
}
|
|
5738
|
+
if (ua.includes("Windows NT")) {
|
|
5739
|
+
const match = ua.match(/Windows NT ([\d.]+)/);
|
|
5740
|
+
if (match) {
|
|
5741
|
+
const ntVersion = match[1];
|
|
5742
|
+
const windowsVersions = {
|
|
5743
|
+
"10.0": "Windows 10/11",
|
|
5744
|
+
"6.3": "Windows 8.1",
|
|
5745
|
+
"6.2": "Windows 8",
|
|
5746
|
+
"6.1": "Windows 7",
|
|
5747
|
+
"6.0": "Windows Vista",
|
|
5748
|
+
"5.1": "Windows XP"
|
|
5749
|
+
};
|
|
5750
|
+
return windowsVersions[ntVersion] || `Windows NT ${ntVersion}`;
|
|
5751
|
+
}
|
|
5752
|
+
return "Windows";
|
|
5753
|
+
}
|
|
5754
|
+
if (ua.includes("Linux")) {
|
|
5755
|
+
if (ua.includes("Ubuntu")) return "Ubuntu";
|
|
5756
|
+
if (ua.includes("Fedora")) return "Fedora";
|
|
5757
|
+
if (ua.includes("Debian")) return "Debian";
|
|
5758
|
+
return "Linux";
|
|
5759
|
+
}
|
|
5760
|
+
if (ua.includes("CrOS")) {
|
|
5761
|
+
return "Chrome OS";
|
|
5762
|
+
}
|
|
5763
|
+
return null;
|
|
5764
|
+
}
|
|
5765
|
+
function detectDeviceType(ua) {
|
|
5766
|
+
if (ua.includes("iPad") || ua.includes("Tablet") || ua.includes("Android") && !ua.includes("Mobile")) {
|
|
5767
|
+
return "tablet";
|
|
5768
|
+
}
|
|
5769
|
+
if (ua.includes("iPhone") || ua.includes("iPod") || ua.includes("Android") || ua.includes("Mobile") || ua.includes("webOS") || ua.includes("BlackBerry") || ua.includes("IEMobile") || ua.includes("Opera Mini")) {
|
|
5770
|
+
return "mobile";
|
|
5771
|
+
}
|
|
5772
|
+
if (ua.includes("Windows NT") || ua.includes("Mac OS X") || ua.includes("macOS") || ua.includes("Linux") || ua.includes("CrOS")) {
|
|
5773
|
+
return "desktop";
|
|
5774
|
+
}
|
|
5775
|
+
return null;
|
|
5776
|
+
}
|
|
5777
|
+
|
|
5778
|
+
// src/config/auth.ts
|
|
5779
|
+
var MIN_PASSWORD_LENGTH = 8;
|
|
5780
|
+
var MAX_PASSWORD_LENGTH = 128;
|
|
5781
|
+
var PASSWORD_REQUIREMENTS = {
|
|
5782
|
+
minLength: MIN_PASSWORD_LENGTH,
|
|
5783
|
+
maxLength: MAX_PASSWORD_LENGTH,
|
|
5784
|
+
description: `Must be at least ${MIN_PASSWORD_LENGTH} characters`,
|
|
5785
|
+
placeholder: `Min. ${MIN_PASSWORD_LENGTH} characters`
|
|
5786
|
+
};
|
|
5787
|
+
|
|
5788
|
+
// src/config/billing.ts
|
|
5789
|
+
var BYTES_PER_GB = 1024 * 1024 * 1024;
|
|
5790
|
+
var MICRODOLLARS_PER_CENT = 1e4;
|
|
5791
|
+
var INVOICE_DUE_DAYS = 15;
|
|
5792
|
+
var SERVICE_METRICS = {
|
|
5793
|
+
// KV (Key-Value Store)
|
|
5794
|
+
kv: {
|
|
5795
|
+
operations: "operations",
|
|
5796
|
+
// User-friendly: "100K operations"
|
|
5797
|
+
storage: "storage"
|
|
5798
|
+
// GB-month
|
|
5799
|
+
},
|
|
5800
|
+
// Realtime (Pub/Sub Messaging)
|
|
5801
|
+
realtime: {
|
|
5802
|
+
messages: "messages",
|
|
5803
|
+
// User-friendly: "100K messages"
|
|
5804
|
+
connections: "connections"
|
|
5805
|
+
// SSE subscribe connections
|
|
5806
|
+
},
|
|
5807
|
+
// Other services follow the same pattern
|
|
5808
|
+
ai: {
|
|
5809
|
+
tokens: "tokens"
|
|
5810
|
+
},
|
|
5811
|
+
email: {
|
|
5812
|
+
emails: "emails",
|
|
5813
|
+
marketingEmails: "marketing_emails"
|
|
5814
|
+
},
|
|
5815
|
+
notifications: {
|
|
5816
|
+
sends: "sends"
|
|
5817
|
+
},
|
|
5818
|
+
analytics: {
|
|
5819
|
+
events: "events",
|
|
5820
|
+
forwarding: "forwarding"
|
|
5821
|
+
},
|
|
5822
|
+
storage: {
|
|
5823
|
+
capacity: "capacity",
|
|
5824
|
+
uploads: "uploads",
|
|
5825
|
+
egress: "egress"
|
|
5826
|
+
},
|
|
5827
|
+
auth: {
|
|
5828
|
+
mau: "mau"
|
|
5829
|
+
},
|
|
5830
|
+
flags: {
|
|
5831
|
+
evaluations: "evaluations"
|
|
5832
|
+
},
|
|
5833
|
+
consent: {
|
|
5834
|
+
records: "records"
|
|
5835
|
+
},
|
|
5836
|
+
referrals: {
|
|
5837
|
+
conversions: "conversions"
|
|
5838
|
+
},
|
|
5839
|
+
engagement: {
|
|
5840
|
+
operations: "operations"
|
|
5841
|
+
},
|
|
5842
|
+
billing: {
|
|
5843
|
+
subscriptions: "subscriptions",
|
|
5844
|
+
usageRecords: "usage_records"
|
|
5845
|
+
},
|
|
5846
|
+
search: {
|
|
5847
|
+
documents: "documents",
|
|
5848
|
+
searches: "searches"
|
|
5849
|
+
},
|
|
5850
|
+
webhooks: {
|
|
5851
|
+
deliveries: "deliveries"
|
|
5852
|
+
},
|
|
5853
|
+
monitoring: {
|
|
5854
|
+
errors: "errors"
|
|
5855
|
+
},
|
|
5856
|
+
jobs: {
|
|
5857
|
+
invocations: "invocations",
|
|
5858
|
+
cronSchedules: "cron_schedules"
|
|
5859
|
+
},
|
|
5860
|
+
database: {
|
|
5861
|
+
computeSeconds: "compute_seconds",
|
|
5862
|
+
storage: "storage",
|
|
5863
|
+
dataTransferBytes: "data_transfer_bytes"
|
|
5864
|
+
},
|
|
5865
|
+
deploy: {
|
|
5866
|
+
buildMinutes: "build_minutes"
|
|
5867
|
+
}
|
|
5868
|
+
};
|
|
5869
|
+
var COMPUTE_VCPU_ACTIVE_RATE_MICRODOLLARS = 400;
|
|
5870
|
+
var COMPUTE_VCPU_IDLE_RATE_MICRODOLLARS = 50;
|
|
5871
|
+
var COMPUTE_RAM_RATE_MICRODOLLARS = 167;
|
|
5872
|
+
var BUILD_MINUTE_PRICES = {
|
|
5873
|
+
standard: 14e3,
|
|
5874
|
+
// $0.014/min
|
|
5875
|
+
large: 3e4,
|
|
5876
|
+
// $0.030/min
|
|
5877
|
+
xlarge: 126e3
|
|
5878
|
+
// $0.126/min
|
|
5879
|
+
};
|
|
5880
|
+
var BUILD_SIZE_MULTIPLIERS = {
|
|
5881
|
+
standard: 1,
|
|
5882
|
+
large: 2,
|
|
5883
|
+
xlarge: 9
|
|
5884
|
+
};
|
|
5885
|
+
var BUILD_MINUTES_INCLUDED = {
|
|
5886
|
+
free: 100,
|
|
5887
|
+
pro: 500,
|
|
5888
|
+
team: 2e3,
|
|
5889
|
+
enterprise: 1e4
|
|
5890
|
+
};
|
|
5891
|
+
var CI_BUILD_MINUTE_PRICE_MICRODOLLARS = BUILD_MINUTE_PRICES.standard;
|
|
5892
|
+
var CI_FREE_MINUTES_PER_MONTH = BUILD_MINUTES_INCLUDED.team;
|
|
5893
|
+
var CI_SIZE_MULTIPLIERS = {
|
|
5894
|
+
nano: 1,
|
|
5895
|
+
small: 1,
|
|
5896
|
+
standard: 1,
|
|
5897
|
+
large: 2,
|
|
5898
|
+
xlarge: 4,
|
|
5899
|
+
"2xlarge": 8
|
|
5900
|
+
};
|
|
5901
|
+
var CI_MACOS_SIZE_MULTIPLIERS = {
|
|
5902
|
+
standard: 10,
|
|
5903
|
+
large: 20,
|
|
5904
|
+
xlarge: 40
|
|
5905
|
+
};
|
|
5906
|
+
var CI_MACOS_MULTIPLIER = CI_MACOS_SIZE_MULTIPLIERS.standard;
|
|
5907
|
+
var CREDIT_EXPIRY_MONTHS = 12;
|
|
5908
|
+
var MAX_PAYMENT_ATTEMPTS = 3;
|
|
5909
|
+
var BILLING_ALLOWED_ROLES = ["super_admin", "admin", "billing"];
|
|
5910
|
+
function hasBillingAccess(role) {
|
|
5911
|
+
return BILLING_ALLOWED_ROLES.includes(role);
|
|
5912
|
+
}
|
|
5913
|
+
|
|
5914
|
+
// src/config/console-keys.ts
|
|
5915
|
+
var CONSOLE_APP_SLUG = "sylphx-console";
|
|
5916
|
+
function getEnvPrefix() {
|
|
5917
|
+
const envType = process.env.NEXT_PUBLIC_ENV_TYPE;
|
|
5918
|
+
if (envType === "development") return "dev";
|
|
5919
|
+
if (envType === "staging") return "stg";
|
|
5920
|
+
if (envType === "production") return "prod";
|
|
5921
|
+
return process.env.NODE_ENV === "production" ? "prod" : "dev";
|
|
5922
|
+
}
|
|
5923
|
+
|
|
5924
|
+
// src/config/instance-types.ts
|
|
5925
|
+
var VCPU_MINUTE_RATE = 463;
|
|
5926
|
+
var GIB_MINUTE_RATE = 232;
|
|
5927
|
+
var INSTANCE_TYPE_ALIASES = {
|
|
5928
|
+
"starter-1x": "xs",
|
|
5929
|
+
"standard-1x": "sm",
|
|
5930
|
+
"standard-2x": "md",
|
|
5931
|
+
"performance-m": "lg",
|
|
5932
|
+
"performance-l": "xl",
|
|
5933
|
+
"performance-xl": "2xl"
|
|
5934
|
+
};
|
|
5935
|
+
function resolveCanonicalInstanceType(id) {
|
|
5936
|
+
return INSTANCE_TYPE_ALIASES[id] ?? id;
|
|
5937
|
+
}
|
|
5938
|
+
var INSTANCE_TYPES = {
|
|
5939
|
+
// ---- Canonical T-shirt sizes (ADR-034) ----
|
|
5940
|
+
xs: {
|
|
5941
|
+
id: "xs",
|
|
5942
|
+
name: "XS",
|
|
5943
|
+
cpuLimit: "250m",
|
|
5944
|
+
memoryLimit: "512Mi",
|
|
5945
|
+
cpuRequest: "63m",
|
|
5946
|
+
memoryRequest: "256Mi",
|
|
5947
|
+
vcpus: 0.25,
|
|
5948
|
+
memoryMib: 512,
|
|
5949
|
+
vcpuMinuteRateMicrodollars: VCPU_MINUTE_RATE,
|
|
5950
|
+
gbMinuteRateMicrodollars: GIB_MINUTE_RATE,
|
|
5951
|
+
allowedPlans: ["free", "starter", "pro", "team", "enterprise"],
|
|
5952
|
+
maxReplicas: 1,
|
|
5953
|
+
highlights: ["0.25 vCPU, 0.5 GiB RAM", "Available on all plans", "Ideal for dev/staging"]
|
|
5954
|
+
},
|
|
5955
|
+
sm: {
|
|
5956
|
+
id: "sm",
|
|
5957
|
+
name: "SM",
|
|
5958
|
+
cpuLimit: "500m",
|
|
5959
|
+
memoryLimit: "1Gi",
|
|
5960
|
+
cpuRequest: "125m",
|
|
5961
|
+
memoryRequest: "512Mi",
|
|
5962
|
+
vcpus: 0.5,
|
|
5963
|
+
memoryMib: 1024,
|
|
5964
|
+
vcpuMinuteRateMicrodollars: VCPU_MINUTE_RATE,
|
|
5965
|
+
gbMinuteRateMicrodollars: GIB_MINUTE_RATE,
|
|
5966
|
+
allowedPlans: ["starter", "pro", "team", "enterprise"],
|
|
5967
|
+
maxReplicas: 3,
|
|
5968
|
+
highlights: ["0.5 vCPU, 1 GiB RAM", "Good for lightweight services", "Starter plan default"]
|
|
5969
|
+
},
|
|
5970
|
+
md: {
|
|
5971
|
+
id: "md",
|
|
5972
|
+
name: "MD",
|
|
5973
|
+
cpuLimit: "1000m",
|
|
5974
|
+
memoryLimit: "2Gi",
|
|
5975
|
+
cpuRequest: "250m",
|
|
5976
|
+
memoryRequest: "1Gi",
|
|
5977
|
+
vcpus: 1,
|
|
5978
|
+
memoryMib: 2048,
|
|
5979
|
+
vcpuMinuteRateMicrodollars: VCPU_MINUTE_RATE,
|
|
5980
|
+
gbMinuteRateMicrodollars: GIB_MINUTE_RATE,
|
|
5981
|
+
allowedPlans: ["starter", "pro", "team", "enterprise"],
|
|
5982
|
+
maxReplicas: 5,
|
|
5983
|
+
highlights: ["1 vCPU, 2 GiB RAM", "Good for web apps and APIs", "Pro plan default"]
|
|
5984
|
+
},
|
|
5985
|
+
lg: {
|
|
5986
|
+
id: "lg",
|
|
5987
|
+
name: "LG",
|
|
5988
|
+
cpuLimit: "2000m",
|
|
5989
|
+
memoryLimit: "4Gi",
|
|
5990
|
+
cpuRequest: "500m",
|
|
5991
|
+
memoryRequest: "2Gi",
|
|
5992
|
+
vcpus: 2,
|
|
5993
|
+
memoryMib: 4096,
|
|
5994
|
+
vcpuMinuteRateMicrodollars: VCPU_MINUTE_RATE,
|
|
5995
|
+
gbMinuteRateMicrodollars: GIB_MINUTE_RATE,
|
|
5996
|
+
allowedPlans: ["pro", "team", "enterprise"],
|
|
5997
|
+
maxReplicas: 10,
|
|
5998
|
+
highlights: ["2 vCPUs, 4 GiB RAM", "High-throughput APIs and workers", "Team plan default"]
|
|
5999
|
+
},
|
|
6000
|
+
xl: {
|
|
6001
|
+
id: "xl",
|
|
6002
|
+
name: "XL",
|
|
6003
|
+
cpuLimit: "4000m",
|
|
6004
|
+
memoryLimit: "8Gi",
|
|
6005
|
+
cpuRequest: "1000m",
|
|
6006
|
+
memoryRequest: "4Gi",
|
|
6007
|
+
vcpus: 4,
|
|
6008
|
+
memoryMib: 8192,
|
|
6009
|
+
vcpuMinuteRateMicrodollars: VCPU_MINUTE_RATE,
|
|
6010
|
+
gbMinuteRateMicrodollars: GIB_MINUTE_RATE,
|
|
6011
|
+
allowedPlans: ["team", "enterprise"],
|
|
6012
|
+
maxReplicas: 10,
|
|
6013
|
+
highlights: ["4 vCPUs, 8 GiB RAM", "CI builds and ML inference", "Enterprise plan default"]
|
|
6014
|
+
},
|
|
6015
|
+
"2xl": {
|
|
6016
|
+
id: "2xl",
|
|
6017
|
+
name: "2XL",
|
|
6018
|
+
cpuLimit: "8000m",
|
|
6019
|
+
memoryLimit: "16Gi",
|
|
6020
|
+
cpuRequest: "2000m",
|
|
6021
|
+
memoryRequest: "8Gi",
|
|
6022
|
+
vcpus: 8,
|
|
6023
|
+
memoryMib: 16384,
|
|
6024
|
+
vcpuMinuteRateMicrodollars: VCPU_MINUTE_RATE,
|
|
6025
|
+
gbMinuteRateMicrodollars: GIB_MINUTE_RATE,
|
|
6026
|
+
allowedPlans: ["team", "enterprise"],
|
|
6027
|
+
maxReplicas: 20,
|
|
6028
|
+
highlights: ["8 vCPUs, 16 GiB RAM", "Heavy compute and large builds", "Team/Enterprise"]
|
|
6029
|
+
},
|
|
6030
|
+
"4xl": {
|
|
6031
|
+
id: "4xl",
|
|
6032
|
+
name: "4XL",
|
|
6033
|
+
cpuLimit: "16000m",
|
|
6034
|
+
memoryLimit: "32Gi",
|
|
6035
|
+
cpuRequest: "4000m",
|
|
6036
|
+
memoryRequest: "16Gi",
|
|
6037
|
+
vcpus: 16,
|
|
6038
|
+
memoryMib: 32768,
|
|
6039
|
+
vcpuMinuteRateMicrodollars: VCPU_MINUTE_RATE,
|
|
6040
|
+
gbMinuteRateMicrodollars: GIB_MINUTE_RATE,
|
|
6041
|
+
allowedPlans: ["enterprise"],
|
|
6042
|
+
maxReplicas: 20,
|
|
6043
|
+
highlights: ["16 vCPUs, 32 GiB RAM", "Maximum compute power", "Enterprise exclusive"]
|
|
6044
|
+
},
|
|
6045
|
+
// ---- Legacy names (deprecated — kept for backward compat with DB values) ----
|
|
6046
|
+
/** @deprecated Use 'xs' instead */
|
|
6047
|
+
"starter-1x": {
|
|
6048
|
+
id: "starter-1x",
|
|
6049
|
+
name: "Starter 1x",
|
|
6050
|
+
cpuLimit: "1000m",
|
|
6051
|
+
memoryLimit: "2Gi",
|
|
6052
|
+
cpuRequest: "250m",
|
|
6053
|
+
memoryRequest: "1Gi",
|
|
6054
|
+
vcpus: 1,
|
|
6055
|
+
memoryMib: 2048,
|
|
6056
|
+
vcpuMinuteRateMicrodollars: VCPU_MINUTE_RATE,
|
|
6057
|
+
gbMinuteRateMicrodollars: GIB_MINUTE_RATE,
|
|
6058
|
+
allowedPlans: ["free", "starter", "pro", "team", "enterprise"],
|
|
6059
|
+
maxReplicas: 3,
|
|
6060
|
+
highlights: ["1 vCPU, 2 GiB RAM", "Available on all plans", "Ideal for lightweight services"],
|
|
6061
|
+
deprecated: true
|
|
6062
|
+
},
|
|
6063
|
+
/** @deprecated Use 'sm' instead */
|
|
6064
|
+
"standard-1x": {
|
|
6065
|
+
id: "standard-1x",
|
|
6066
|
+
name: "Standard 1x",
|
|
6067
|
+
cpuLimit: "2000m",
|
|
6068
|
+
memoryLimit: "4Gi",
|
|
6069
|
+
cpuRequest: "500m",
|
|
6070
|
+
memoryRequest: "2Gi",
|
|
6071
|
+
vcpus: 2,
|
|
6072
|
+
memoryMib: 4096,
|
|
6073
|
+
vcpuMinuteRateMicrodollars: VCPU_MINUTE_RATE,
|
|
6074
|
+
gbMinuteRateMicrodollars: GIB_MINUTE_RATE,
|
|
6075
|
+
allowedPlans: ["starter", "pro", "team", "enterprise"],
|
|
6076
|
+
maxReplicas: 5,
|
|
6077
|
+
highlights: ["2 vCPUs, 4 GiB RAM", "Good for web apps and APIs", "Starter plan default"],
|
|
6078
|
+
deprecated: true
|
|
6079
|
+
},
|
|
6080
|
+
/** @deprecated Use 'md' instead */
|
|
6081
|
+
"standard-2x": {
|
|
6082
|
+
id: "standard-2x",
|
|
6083
|
+
name: "Standard 2x",
|
|
6084
|
+
cpuLimit: "2000m",
|
|
6085
|
+
memoryLimit: "8Gi",
|
|
6086
|
+
cpuRequest: "500m",
|
|
6087
|
+
memoryRequest: "4Gi",
|
|
6088
|
+
vcpus: 2,
|
|
6089
|
+
memoryMib: 8192,
|
|
6090
|
+
vcpuMinuteRateMicrodollars: VCPU_MINUTE_RATE,
|
|
6091
|
+
gbMinuteRateMicrodollars: GIB_MINUTE_RATE,
|
|
6092
|
+
allowedPlans: ["starter", "pro", "team", "enterprise"],
|
|
6093
|
+
maxReplicas: 5,
|
|
6094
|
+
highlights: [
|
|
6095
|
+
"2 vCPUs, 8 GiB RAM",
|
|
6096
|
+
"Double memory for data-heavy workloads",
|
|
6097
|
+
"Pro plan default"
|
|
6098
|
+
],
|
|
6099
|
+
deprecated: true
|
|
6100
|
+
},
|
|
6101
|
+
/** @deprecated Use 'lg' instead */
|
|
6102
|
+
"performance-m": {
|
|
6103
|
+
id: "performance-m",
|
|
6104
|
+
name: "Performance M",
|
|
6105
|
+
cpuLimit: "4000m",
|
|
6106
|
+
memoryLimit: "16Gi",
|
|
6107
|
+
cpuRequest: "1000m",
|
|
6108
|
+
memoryRequest: "8Gi",
|
|
6109
|
+
vcpus: 4,
|
|
6110
|
+
memoryMib: 16384,
|
|
6111
|
+
vcpuMinuteRateMicrodollars: VCPU_MINUTE_RATE,
|
|
6112
|
+
gbMinuteRateMicrodollars: GIB_MINUTE_RATE,
|
|
6113
|
+
allowedPlans: ["pro", "team", "enterprise"],
|
|
6114
|
+
maxReplicas: 10,
|
|
6115
|
+
highlights: ["4 vCPUs, 16 GiB RAM", "High-throughput APIs and workers", "Team plan default"],
|
|
6116
|
+
deprecated: true
|
|
6117
|
+
},
|
|
6118
|
+
/** @deprecated Use 'xl' instead */
|
|
6119
|
+
"performance-l": {
|
|
6120
|
+
id: "performance-l",
|
|
6121
|
+
name: "Performance L",
|
|
6122
|
+
cpuLimit: "8000m",
|
|
6123
|
+
memoryLimit: "32Gi",
|
|
6124
|
+
cpuRequest: "2000m",
|
|
6125
|
+
memoryRequest: "16Gi",
|
|
6126
|
+
vcpus: 8,
|
|
6127
|
+
memoryMib: 32768,
|
|
6128
|
+
vcpuMinuteRateMicrodollars: VCPU_MINUTE_RATE,
|
|
6129
|
+
gbMinuteRateMicrodollars: GIB_MINUTE_RATE,
|
|
6130
|
+
allowedPlans: ["team", "enterprise"],
|
|
6131
|
+
maxReplicas: 10,
|
|
6132
|
+
highlights: ["8 vCPUs, 32 GiB RAM", "CI builds and ML inference", "Enterprise plan default"],
|
|
6133
|
+
deprecated: true
|
|
6134
|
+
},
|
|
6135
|
+
/** @deprecated Use '2xl' instead */
|
|
6136
|
+
"performance-xl": {
|
|
6137
|
+
id: "performance-xl",
|
|
6138
|
+
name: "Performance XL",
|
|
6139
|
+
cpuLimit: "16000m",
|
|
6140
|
+
memoryLimit: "64Gi",
|
|
6141
|
+
cpuRequest: "4000m",
|
|
6142
|
+
memoryRequest: "32Gi",
|
|
6143
|
+
vcpus: 16,
|
|
6144
|
+
memoryMib: 65536,
|
|
6145
|
+
vcpuMinuteRateMicrodollars: VCPU_MINUTE_RATE,
|
|
6146
|
+
gbMinuteRateMicrodollars: GIB_MINUTE_RATE,
|
|
6147
|
+
allowedPlans: ["enterprise"],
|
|
6148
|
+
maxReplicas: 20,
|
|
6149
|
+
highlights: ["16 vCPUs, 64 GiB RAM", "Heavy compute and large builds", "Enterprise exclusive"],
|
|
6150
|
+
deprecated: true
|
|
6151
|
+
}
|
|
6152
|
+
};
|
|
6153
|
+
var INSTANCE_TYPE_ORDER = ["xs", "sm", "md", "lg", "xl", "2xl", "4xl"];
|
|
6154
|
+
var LEGACY_INSTANCE_TYPE_ORDER = [
|
|
6155
|
+
"starter-1x",
|
|
6156
|
+
"standard-1x",
|
|
6157
|
+
"standard-2x",
|
|
6158
|
+
"performance-m",
|
|
6159
|
+
"performance-l",
|
|
6160
|
+
"performance-xl"
|
|
6161
|
+
];
|
|
6162
|
+
var DEFAULT_INSTANCE_TYPE = {
|
|
6163
|
+
free: "xs",
|
|
6164
|
+
starter: "sm",
|
|
6165
|
+
pro: "md",
|
|
6166
|
+
team: "lg",
|
|
6167
|
+
enterprise: "xl"
|
|
6168
|
+
};
|
|
6169
|
+
function getDefaultInstanceType(plan) {
|
|
6170
|
+
return DEFAULT_INSTANCE_TYPE[plan];
|
|
6171
|
+
}
|
|
6172
|
+
function getAvailableInstanceTypes(plan) {
|
|
6173
|
+
return INSTANCE_TYPE_ORDER.map((id) => INSTANCE_TYPES[id]).filter(
|
|
6174
|
+
(t) => t.allowedPlans.includes(plan) && !t.deprecated
|
|
6175
|
+
);
|
|
6176
|
+
}
|
|
6177
|
+
function resolveResources(id) {
|
|
6178
|
+
const t = INSTANCE_TYPES[id];
|
|
6179
|
+
return {
|
|
6180
|
+
requests: { cpu: t.cpuRequest, memory: t.memoryRequest },
|
|
6181
|
+
limits: { cpu: t.cpuLimit, memory: t.memoryLimit }
|
|
6182
|
+
};
|
|
6183
|
+
}
|
|
6184
|
+
function resolveMaxReplicas(id) {
|
|
6185
|
+
return INSTANCE_TYPES[id].maxReplicas;
|
|
6186
|
+
}
|
|
6187
|
+
var DEFAULT_MAX_REPLICAS = 10;
|
|
6188
|
+
function isValidInstanceType(id) {
|
|
6189
|
+
return id in INSTANCE_TYPES;
|
|
6190
|
+
}
|
|
6191
|
+
function validateInstanceTypeForPlan(id, plan) {
|
|
6192
|
+
const canonicalId = resolveCanonicalInstanceType(id);
|
|
6193
|
+
if (!isValidInstanceType(canonicalId)) {
|
|
6194
|
+
return { valid: false, error: `Unknown instance type: ${id}` };
|
|
6195
|
+
}
|
|
6196
|
+
const definition = INSTANCE_TYPES[canonicalId];
|
|
6197
|
+
if (!definition.allowedPlans.includes(plan)) {
|
|
6198
|
+
return {
|
|
6199
|
+
valid: false,
|
|
6200
|
+
error: `Instance type "${definition.name}" is not available on the ${plan} plan`
|
|
6201
|
+
};
|
|
6202
|
+
}
|
|
6203
|
+
return { valid: true };
|
|
6204
|
+
}
|
|
6205
|
+
|
|
6206
|
+
// src/config/platform-plans.ts
|
|
6207
|
+
var PLATFORM_PLANS = {
|
|
6208
|
+
free: {
|
|
6209
|
+
id: "free",
|
|
6210
|
+
name: "Free",
|
|
6211
|
+
priceMonthly: 0,
|
|
6212
|
+
priceAnnual: 0,
|
|
6213
|
+
includedCreditsMicrodollars: 5e6,
|
|
6214
|
+
// $5
|
|
6215
|
+
features: {
|
|
6216
|
+
customDomains: false,
|
|
6217
|
+
sso: false,
|
|
6218
|
+
priorityCi: false,
|
|
6219
|
+
macosCi: false,
|
|
6220
|
+
support: "community",
|
|
6221
|
+
sla: null,
|
|
6222
|
+
rbac: false,
|
|
6223
|
+
advancedAnalytics: false,
|
|
6224
|
+
whiteLabel: false
|
|
6225
|
+
},
|
|
6226
|
+
limits: {
|
|
6227
|
+
maxProjects: 1,
|
|
6228
|
+
maxMembers: 1,
|
|
6229
|
+
maxCustomDomains: 0,
|
|
6230
|
+
maxConcurrentRunners: 1,
|
|
6231
|
+
maxMacosRunners: 0,
|
|
6232
|
+
maxDatabases: 1,
|
|
6233
|
+
ciMaxJobDurationSeconds: 30 * 60,
|
|
6234
|
+
// 30 min
|
|
6235
|
+
apiRateLimitPerMin: 60,
|
|
6236
|
+
auditLogDays: 0,
|
|
6237
|
+
maxReplicas: 1,
|
|
6238
|
+
includedBuildMinutes: 100,
|
|
6239
|
+
includedBandwidthGb: 100,
|
|
6240
|
+
logRetentionDays: 1,
|
|
6241
|
+
buildMachineTier: "standard"
|
|
6242
|
+
},
|
|
6243
|
+
highlights: [
|
|
6244
|
+
"1 project",
|
|
6245
|
+
"$5 compute credits/mo",
|
|
6246
|
+
"100 build minutes",
|
|
6247
|
+
"100 GB bandwidth",
|
|
6248
|
+
"Community support"
|
|
6249
|
+
],
|
|
6250
|
+
cta: "Start free"
|
|
6251
|
+
},
|
|
6252
|
+
/**
|
|
6253
|
+
* @deprecated ADR-034 removed the Starter tier. Retained for backward compatibility
|
|
6254
|
+
* with existing subscribers. Not shown in pricing UI for new sign-ups.
|
|
6255
|
+
*/
|
|
6256
|
+
starter: {
|
|
6257
|
+
id: "starter",
|
|
6258
|
+
name: "Starter",
|
|
6259
|
+
deprecated: true,
|
|
6260
|
+
priceMonthly: 1900,
|
|
6261
|
+
// $19
|
|
6262
|
+
priceAnnual: 18240,
|
|
6263
|
+
// $19 x 12 x 0.80 = $182.40
|
|
6264
|
+
includedCreditsMicrodollars: 19e6,
|
|
6265
|
+
// $19
|
|
6266
|
+
features: {
|
|
6267
|
+
customDomains: true,
|
|
6268
|
+
sso: false,
|
|
6269
|
+
priorityCi: false,
|
|
6270
|
+
macosCi: true,
|
|
6271
|
+
support: "email",
|
|
6272
|
+
sla: null,
|
|
6273
|
+
rbac: false,
|
|
6274
|
+
advancedAnalytics: false,
|
|
6275
|
+
whiteLabel: false
|
|
6276
|
+
},
|
|
6277
|
+
limits: {
|
|
6278
|
+
maxProjects: 10,
|
|
6279
|
+
maxMembers: 10,
|
|
6280
|
+
maxCustomDomains: 5,
|
|
6281
|
+
maxConcurrentRunners: 5,
|
|
6282
|
+
maxMacosRunners: 1,
|
|
6283
|
+
maxDatabases: 10,
|
|
6284
|
+
ciMaxJobDurationSeconds: 60 * 60,
|
|
6285
|
+
// 1 hr
|
|
6286
|
+
apiRateLimitPerMin: 300,
|
|
6287
|
+
auditLogDays: 30,
|
|
6288
|
+
maxReplicas: 3,
|
|
6289
|
+
includedBuildMinutes: 250,
|
|
6290
|
+
includedBandwidthGb: 500,
|
|
6291
|
+
logRetentionDays: 7,
|
|
6292
|
+
buildMachineTier: "standard"
|
|
6293
|
+
},
|
|
6294
|
+
highlights: ["$19 credits/mo", "10 projects", "Custom domains", "macOS CI", "Email support"],
|
|
6295
|
+
cta: "Get started"
|
|
6296
|
+
},
|
|
6297
|
+
pro: {
|
|
6298
|
+
id: "pro",
|
|
6299
|
+
name: "Pro",
|
|
6300
|
+
priceMonthly: 2e3,
|
|
6301
|
+
// $20
|
|
6302
|
+
priceAnnual: 19200,
|
|
6303
|
+
// $20 x 12 x 0.80 = $192 ($16/mo)
|
|
6304
|
+
includedCreditsMicrodollars: 2e7,
|
|
6305
|
+
// $20
|
|
6306
|
+
features: {
|
|
6307
|
+
customDomains: true,
|
|
6308
|
+
sso: false,
|
|
6309
|
+
priorityCi: true,
|
|
6310
|
+
macosCi: true,
|
|
6311
|
+
support: "priority",
|
|
6312
|
+
sla: "99.9%",
|
|
6313
|
+
rbac: true,
|
|
6314
|
+
advancedAnalytics: true,
|
|
6315
|
+
whiteLabel: false
|
|
6316
|
+
},
|
|
6317
|
+
limits: {
|
|
6318
|
+
maxProjects: null,
|
|
6319
|
+
// unlimited
|
|
6320
|
+
maxMembers: 25,
|
|
6321
|
+
maxCustomDomains: null,
|
|
6322
|
+
// unlimited
|
|
6323
|
+
maxConcurrentRunners: 20,
|
|
6324
|
+
maxMacosRunners: 5,
|
|
6325
|
+
maxDatabases: null,
|
|
6326
|
+
// unlimited
|
|
6327
|
+
ciMaxJobDurationSeconds: 2 * 60 * 60,
|
|
6328
|
+
// 2 hrs
|
|
6329
|
+
apiRateLimitPerMin: 1e3,
|
|
6330
|
+
auditLogDays: 90,
|
|
6331
|
+
maxReplicas: 10,
|
|
6332
|
+
includedBuildMinutes: 500,
|
|
6333
|
+
includedBandwidthGb: 1e3,
|
|
6334
|
+
logRetentionDays: 14,
|
|
6335
|
+
buildMachineTier: "standard"
|
|
6336
|
+
},
|
|
6337
|
+
highlights: [
|
|
6338
|
+
"Unlimited projects",
|
|
6339
|
+
"$20 credits/mo",
|
|
6340
|
+
"500 build minutes",
|
|
6341
|
+
"1 TB bandwidth",
|
|
6342
|
+
"Priority support",
|
|
6343
|
+
"SLA 99.9%"
|
|
6344
|
+
],
|
|
6345
|
+
badge: "Most Popular",
|
|
6346
|
+
cta: "Start Pro"
|
|
6347
|
+
},
|
|
6348
|
+
team: {
|
|
6349
|
+
id: "team",
|
|
6350
|
+
name: "Team",
|
|
6351
|
+
priceMonthly: 2e3,
|
|
6352
|
+
// $20/user
|
|
6353
|
+
priceAnnual: 19200,
|
|
6354
|
+
// $192/user/yr ($16/user/mo)
|
|
6355
|
+
includedCreditsMicrodollars: 2e7,
|
|
6356
|
+
// $20/user
|
|
6357
|
+
perSeat: true,
|
|
6358
|
+
features: {
|
|
6359
|
+
customDomains: true,
|
|
6360
|
+
sso: true,
|
|
6361
|
+
priorityCi: true,
|
|
6362
|
+
macosCi: true,
|
|
6363
|
+
support: "dedicated",
|
|
6364
|
+
sla: "99.95%",
|
|
6365
|
+
rbac: true,
|
|
6366
|
+
advancedAnalytics: true,
|
|
6367
|
+
whiteLabel: true
|
|
6368
|
+
},
|
|
6369
|
+
limits: {
|
|
6370
|
+
maxProjects: null,
|
|
6371
|
+
// unlimited
|
|
6372
|
+
maxMembers: null,
|
|
6373
|
+
// unlimited
|
|
6374
|
+
maxCustomDomains: null,
|
|
6375
|
+
// unlimited
|
|
6376
|
+
maxConcurrentRunners: null,
|
|
6377
|
+
// unlimited
|
|
6378
|
+
maxMacosRunners: null,
|
|
6379
|
+
// unlimited
|
|
6380
|
+
maxDatabases: null,
|
|
6381
|
+
// unlimited
|
|
6382
|
+
ciMaxJobDurationSeconds: 6 * 60 * 60,
|
|
6383
|
+
// 6 hrs
|
|
6384
|
+
apiRateLimitPerMin: 5e3,
|
|
6385
|
+
auditLogDays: 365,
|
|
6386
|
+
maxReplicas: 20,
|
|
6387
|
+
includedBuildMinutes: 2e3,
|
|
6388
|
+
includedBandwidthGb: 5e3,
|
|
6389
|
+
logRetentionDays: 30,
|
|
6390
|
+
buildMachineTier: "large"
|
|
6391
|
+
},
|
|
6392
|
+
highlights: [
|
|
6393
|
+
"$20/user/mo",
|
|
6394
|
+
"SSO / SAML",
|
|
6395
|
+
"2,000 large build minutes",
|
|
6396
|
+
"5 TB bandwidth",
|
|
6397
|
+
"Dedicated support",
|
|
6398
|
+
"SLA 99.95%"
|
|
6399
|
+
],
|
|
6400
|
+
cta: "Get Team"
|
|
6401
|
+
},
|
|
6402
|
+
enterprise: {
|
|
6403
|
+
id: "enterprise",
|
|
6404
|
+
name: "Enterprise",
|
|
6405
|
+
priceMonthly: null,
|
|
6406
|
+
// custom
|
|
6407
|
+
priceAnnual: null,
|
|
6408
|
+
// custom
|
|
6409
|
+
includedCreditsMicrodollars: 0,
|
|
6410
|
+
// negotiated
|
|
6411
|
+
isCustom: true,
|
|
6412
|
+
features: {
|
|
6413
|
+
customDomains: true,
|
|
6414
|
+
sso: true,
|
|
6415
|
+
priorityCi: true,
|
|
6416
|
+
macosCi: true,
|
|
6417
|
+
support: "dedicated",
|
|
6418
|
+
sla: "Custom",
|
|
6419
|
+
rbac: true,
|
|
6420
|
+
advancedAnalytics: true,
|
|
6421
|
+
whiteLabel: true
|
|
6422
|
+
},
|
|
6423
|
+
limits: {
|
|
6424
|
+
maxProjects: null,
|
|
6425
|
+
maxMembers: null,
|
|
6426
|
+
maxCustomDomains: null,
|
|
6427
|
+
maxConcurrentRunners: null,
|
|
6428
|
+
maxMacosRunners: null,
|
|
6429
|
+
maxDatabases: null,
|
|
6430
|
+
ciMaxJobDurationSeconds: 12 * 60 * 60,
|
|
6431
|
+
apiRateLimitPerMin: 0,
|
|
6432
|
+
// 0 = unlimited / negotiated
|
|
6433
|
+
auditLogDays: 730,
|
|
6434
|
+
maxReplicas: null,
|
|
6435
|
+
// custom
|
|
6436
|
+
includedBuildMinutes: 0,
|
|
6437
|
+
// negotiated
|
|
6438
|
+
includedBandwidthGb: 0,
|
|
6439
|
+
// negotiated
|
|
6440
|
+
logRetentionDays: 90,
|
|
6441
|
+
buildMachineTier: "xlarge"
|
|
6442
|
+
},
|
|
6443
|
+
highlights: [
|
|
6444
|
+
"Custom credit volume",
|
|
6445
|
+
"Volume discounts",
|
|
6446
|
+
"Dedicated infrastructure",
|
|
6447
|
+
"Custom SLA",
|
|
6448
|
+
"White-glove onboarding",
|
|
6449
|
+
"Custom contracts"
|
|
6450
|
+
],
|
|
6451
|
+
cta: "Contact sales"
|
|
6452
|
+
}
|
|
6453
|
+
};
|
|
6454
|
+
var PLATFORM_PLAN_ORDER = ["free", "pro", "team", "enterprise"];
|
|
6455
|
+
var PLATFORM_PLAN_ORDER_ALL = [
|
|
6456
|
+
"free",
|
|
6457
|
+
"starter",
|
|
6458
|
+
"pro",
|
|
6459
|
+
"team",
|
|
6460
|
+
"enterprise"
|
|
6461
|
+
];
|
|
6462
|
+
function isPlanDeprecated(planId) {
|
|
6463
|
+
return PLATFORM_PLANS[planId].deprecated === true;
|
|
6464
|
+
}
|
|
6465
|
+
function getActivePlans() {
|
|
6466
|
+
return PLATFORM_PLAN_ORDER.map((id) => PLATFORM_PLANS[id]);
|
|
6467
|
+
}
|
|
6468
|
+
function microsToDollars(microdollars) {
|
|
6469
|
+
return `$${(microdollars / 1e6).toFixed(0)}`;
|
|
6470
|
+
}
|
|
6471
|
+
function centsToDollars(cents) {
|
|
6472
|
+
return `$${(cents / 100).toFixed(0)}`;
|
|
6473
|
+
}
|
|
6474
|
+
function getPlanMonthlyPrice(plan, annual = false) {
|
|
6475
|
+
if (plan.isCustom) return "Custom";
|
|
6476
|
+
const cents = annual && plan.priceAnnual != null ? Math.round(plan.priceAnnual / 12) : plan.priceMonthly;
|
|
6477
|
+
if (cents == null) return "Custom";
|
|
6478
|
+
if (cents === 0) return "$0";
|
|
6479
|
+
const base = centsToDollars(cents);
|
|
6480
|
+
return plan.perSeat ? `${base}/user` : base;
|
|
6481
|
+
}
|
|
6482
|
+
|
|
5190
6483
|
// src/debug.ts
|
|
5191
6484
|
var DEBUG_STORAGE_KEY = "sylphx_debug";
|
|
5192
6485
|
function isDebugEnabled() {
|
|
@@ -5880,400 +7173,98 @@ init_errors();
|
|
|
5880
7173
|
|
|
5881
7174
|
// src/auth.ts
|
|
5882
7175
|
import { authEndpoints } from "@sylphx/contract";
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
7176
|
+
|
|
7177
|
+
// src/dpop.ts
|
|
7178
|
+
var dpop = {
|
|
7179
|
+
/**
|
|
7180
|
+
* Generate a fresh ES256 key pair. Private key is non-extractable
|
|
7181
|
+
* (`extractable: false`) so it can be stored but never serialised.
|
|
7182
|
+
*/
|
|
7183
|
+
async generateKeyPair() {
|
|
7184
|
+
const { privateKey, publicKey } = await crypto.subtle.generateKey(
|
|
7185
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
7186
|
+
false,
|
|
7187
|
+
["sign", "verify"]
|
|
7188
|
+
);
|
|
7189
|
+
const publicJwk = await crypto.subtle.exportKey("jwk", publicKey);
|
|
7190
|
+
const thumbprint = await thumbprintFromJwk(sanitisePublicJwk(publicJwk));
|
|
7191
|
+
return { privateKey, publicKey, thumbprint };
|
|
7192
|
+
},
|
|
7193
|
+
/**
|
|
7194
|
+
* Sign a DPoP proof JWT. When `accessToken` is provided, the proof
|
|
7195
|
+
* includes `ath = base64url(sha256(accessToken))` so the resource
|
|
7196
|
+
* server can bind the proof to the token being presented.
|
|
7197
|
+
*/
|
|
7198
|
+
async generateProof(opts) {
|
|
7199
|
+
const publicJwkRaw = await crypto.subtle.exportKey("jwk", opts.publicKey);
|
|
7200
|
+
const publicJwk = sanitisePublicJwk(publicJwkRaw);
|
|
7201
|
+
const header = { typ: "dpop+jwt", alg: "ES256", jwk: publicJwk };
|
|
7202
|
+
const payload = {
|
|
7203
|
+
jti: randomJti(),
|
|
7204
|
+
htm: opts.method.toUpperCase(),
|
|
7205
|
+
htu: stripQueryAndFragment(opts.uri),
|
|
7206
|
+
iat: Math.floor(Date.now() / 1e3)
|
|
7207
|
+
};
|
|
7208
|
+
if (opts.accessToken) {
|
|
7209
|
+
payload.ath = await sha256Base64Url(new TextEncoder().encode(opts.accessToken));
|
|
5909
7210
|
}
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
body: {
|
|
5922
|
-
email,
|
|
5923
|
-
...options.redirectUrl ? { redirectUrl: options.redirectUrl } : {}
|
|
5924
|
-
}
|
|
5925
|
-
});
|
|
5926
|
-
}
|
|
5927
|
-
async function resendVerificationEmail(config, email) {
|
|
5928
|
-
const endpoint = authEndpoints.resendEmailVerification;
|
|
5929
|
-
const body = { email };
|
|
5930
|
-
await callApi(config, endpoint.path, {
|
|
5931
|
-
method: endpoint.method,
|
|
5932
|
-
body
|
|
5933
|
-
});
|
|
5934
|
-
}
|
|
5935
|
-
async function resetPassword(config, input) {
|
|
5936
|
-
await callApi(config, "/auth/reset-password", {
|
|
5937
|
-
method: "POST",
|
|
5938
|
-
body: { token: input.token, password: input.password }
|
|
5939
|
-
});
|
|
5940
|
-
}
|
|
5941
|
-
async function getSession(config) {
|
|
5942
|
-
if (!config.accessToken) {
|
|
5943
|
-
return { user: null };
|
|
5944
|
-
}
|
|
5945
|
-
const endpoint = authEndpoints.getSession;
|
|
5946
|
-
try {
|
|
5947
|
-
const user2 = await callApi(config, endpoint.path, {
|
|
5948
|
-
method: endpoint.method
|
|
5949
|
-
});
|
|
5950
|
-
return { user: user2 };
|
|
5951
|
-
} catch {
|
|
5952
|
-
return { user: null };
|
|
5953
|
-
}
|
|
5954
|
-
}
|
|
5955
|
-
async function verifyTwoFactor(config, userId, code) {
|
|
5956
|
-
return callApi(config, "/auth/verify-2fa", {
|
|
5957
|
-
method: "POST",
|
|
5958
|
-
body: { userId, code }
|
|
5959
|
-
});
|
|
5960
|
-
}
|
|
5961
|
-
async function introspectToken(config, token, tokenTypeHint) {
|
|
5962
|
-
const response = await fetch(buildApiUrl(config, "/auth/introspect"), {
|
|
5963
|
-
method: "POST",
|
|
5964
|
-
headers: {
|
|
5965
|
-
"Content-Type": "application/json",
|
|
5966
|
-
// RFC 7662 §2: server-to-server call — authenticate with secret key
|
|
5967
|
-
"x-app-secret": config.secretKey ?? ""
|
|
5968
|
-
},
|
|
5969
|
-
body: JSON.stringify({
|
|
5970
|
-
token,
|
|
5971
|
-
token_type_hint: tokenTypeHint
|
|
5972
|
-
})
|
|
5973
|
-
});
|
|
5974
|
-
if (!response.ok) {
|
|
5975
|
-
return { active: false };
|
|
5976
|
-
}
|
|
5977
|
-
return response.json();
|
|
5978
|
-
}
|
|
5979
|
-
async function revokeToken(config, token, options) {
|
|
5980
|
-
await fetch(buildApiUrl(config, "/auth/revoke"), {
|
|
5981
|
-
method: "POST",
|
|
5982
|
-
headers: { "Content-Type": "application/json" },
|
|
5983
|
-
body: JSON.stringify({
|
|
5984
|
-
token: options?.revokeAll ? void 0 : token,
|
|
5985
|
-
client_secret: config.secretKey,
|
|
5986
|
-
user_id: options?.userId,
|
|
5987
|
-
revoke_all: options?.revokeAll
|
|
5988
|
-
})
|
|
5989
|
-
});
|
|
5990
|
-
}
|
|
5991
|
-
async function revokeAllTokens(config, userId) {
|
|
5992
|
-
await revokeToken(config, "", { revokeAll: true, userId });
|
|
5993
|
-
}
|
|
5994
|
-
async function extendedSignUp(config, input) {
|
|
5995
|
-
return callApi(config, "/auth/register", {
|
|
5996
|
-
method: "POST",
|
|
5997
|
-
body: input
|
|
5998
|
-
});
|
|
5999
|
-
}
|
|
6000
|
-
async function inviteUser(config, input) {
|
|
6001
|
-
return callApi(config, "/auth/invite", {
|
|
6002
|
-
method: "POST",
|
|
6003
|
-
body: input
|
|
6004
|
-
});
|
|
6005
|
-
}
|
|
6006
|
-
function normalizeOrgScopedTokenResponse(data) {
|
|
6007
|
-
const accessToken = data.accessToken ?? data.access_token;
|
|
6008
|
-
if (!accessToken) {
|
|
6009
|
-
throw new Error("Invalid org-scoped token response: missing access token");
|
|
6010
|
-
}
|
|
6011
|
-
return {
|
|
6012
|
-
token: accessToken,
|
|
6013
|
-
accessToken,
|
|
6014
|
-
expiresIn: data.expiresIn ?? data.expires_in,
|
|
6015
|
-
tokenType: data.tokenType ?? data.token_type,
|
|
6016
|
-
user: data.user
|
|
6017
|
-
};
|
|
6018
|
-
}
|
|
6019
|
-
async function getOrgScopedToken(config, orgId) {
|
|
6020
|
-
const data = await callApi(config, "/auth/switch-org", {
|
|
6021
|
-
method: "POST",
|
|
6022
|
-
body: { orgId }
|
|
6023
|
-
});
|
|
6024
|
-
return normalizeOrgScopedTokenResponse(data);
|
|
6025
|
-
}
|
|
6026
|
-
async function switchOrg(config, orgId) {
|
|
6027
|
-
return getOrgScopedToken(config, orgId);
|
|
6028
|
-
}
|
|
6029
|
-
var device = {
|
|
6030
|
-
/**
|
|
6031
|
-
* Start a device authorization grant.
|
|
6032
|
-
*
|
|
6033
|
-
* Returns a `DeviceGrant` with `verification_uri_complete` (open this
|
|
6034
|
-
* in the user's browser) and `device_code` (use for polling).
|
|
6035
|
-
*
|
|
6036
|
-
* @example
|
|
6037
|
-
* ```typescript
|
|
6038
|
-
* const grant = await device.init({
|
|
6039
|
-
* baseUrl: 'https://your-app.api.sylphx.com/v1',
|
|
6040
|
-
* clientId: 'sylphx-cli',
|
|
6041
|
-
* scope: ['org:read', 'project:*'],
|
|
6042
|
-
* })
|
|
6043
|
-
* openBrowser(grant.verification_uri_complete)
|
|
6044
|
-
* ```
|
|
6045
|
-
*/
|
|
6046
|
-
async init(opts) {
|
|
6047
|
-
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/device`, {
|
|
6048
|
-
method: "POST",
|
|
6049
|
-
headers: buildDeviceHeaders(opts.userAgent),
|
|
6050
|
-
body: JSON.stringify({
|
|
6051
|
-
client_id: opts.clientId,
|
|
6052
|
-
scope: opts.scope ?? []
|
|
6053
|
-
})
|
|
6054
|
-
});
|
|
6055
|
-
if (!res.ok) throw await deviceError(res, "device.init");
|
|
6056
|
-
return await res.json();
|
|
6057
|
-
},
|
|
6058
|
-
/**
|
|
6059
|
-
* Poll a pending grant. Returns `status: 'pending' | 'approved' |
|
|
6060
|
-
* 'denied' | 'expired'`. On `approved`, the result carries the OAuth
|
|
6061
|
-
* pair (access_token + refresh_token).
|
|
6062
|
-
*
|
|
6063
|
-
* Callers MUST respect the `interval` returned by `init()` — polling
|
|
6064
|
-
* faster than that may return 429 slow_down (RFC 8628 §5.5).
|
|
6065
|
-
*/
|
|
6066
|
-
async poll(opts) {
|
|
6067
|
-
const url = new URL(`${opts.baseUrl.replace(/\/$/, "")}/auth/device/poll`);
|
|
6068
|
-
url.searchParams.set("device_code", opts.deviceCode);
|
|
6069
|
-
const res = await fetch(url.toString(), {
|
|
6070
|
-
method: "GET",
|
|
6071
|
-
headers: buildDeviceHeaders(opts.userAgent)
|
|
6072
|
-
});
|
|
6073
|
-
if (!res.ok) throw await deviceError(res, "device.poll");
|
|
6074
|
-
return await res.json();
|
|
6075
|
-
},
|
|
6076
|
-
/**
|
|
6077
|
-
* Browser leg — the approving user confirms the grant.
|
|
6078
|
-
*
|
|
6079
|
-
* Requires a valid platform-issued access token (`Authorization:
|
|
6080
|
-
* Bearer <accessToken>`) proving the user is logged in on the
|
|
6081
|
-
* Console. Typically called by the Console's `/device` verification
|
|
6082
|
-
* page server-side, forwarding the user's session JWT.
|
|
6083
|
-
*/
|
|
6084
|
-
async approve(opts) {
|
|
6085
|
-
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/device/approve`, {
|
|
6086
|
-
method: "POST",
|
|
6087
|
-
headers: {
|
|
6088
|
-
...buildDeviceHeaders(opts.userAgent),
|
|
6089
|
-
Authorization: `Bearer ${opts.accessToken}`
|
|
6090
|
-
},
|
|
6091
|
-
body: JSON.stringify({ user_code: opts.userCode })
|
|
6092
|
-
});
|
|
6093
|
-
if (!res.ok) throw await deviceError(res, "device.approve");
|
|
6094
|
-
return await res.json();
|
|
6095
|
-
},
|
|
6096
|
-
/**
|
|
6097
|
-
* Browser leg — the user declines the grant.
|
|
6098
|
-
*
|
|
6099
|
-
* Requires a valid platform-issued access token just like `approve`.
|
|
6100
|
-
*/
|
|
6101
|
-
async deny(opts) {
|
|
6102
|
-
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/device/deny`, {
|
|
6103
|
-
method: "POST",
|
|
6104
|
-
headers: {
|
|
6105
|
-
...buildDeviceHeaders(opts.userAgent),
|
|
6106
|
-
Authorization: `Bearer ${opts.accessToken}`
|
|
6107
|
-
},
|
|
6108
|
-
body: JSON.stringify({ user_code: opts.userCode })
|
|
6109
|
-
});
|
|
6110
|
-
if (!res.ok) throw await deviceError(res, "device.deny");
|
|
6111
|
-
return await res.json();
|
|
6112
|
-
}
|
|
6113
|
-
};
|
|
6114
|
-
function buildDeviceHeaders(userAgent) {
|
|
6115
|
-
const headers = {
|
|
6116
|
-
"Content-Type": "application/json"
|
|
6117
|
-
};
|
|
6118
|
-
if (userAgent) headers["User-Agent"] = userAgent;
|
|
6119
|
-
return headers;
|
|
6120
|
-
}
|
|
6121
|
-
var sessions = {
|
|
6122
|
-
/**
|
|
6123
|
-
* List every active platform session for the authenticated user.
|
|
6124
|
-
*
|
|
6125
|
-
* Ordering: most-recently-active first.
|
|
6126
|
-
*
|
|
6127
|
-
* @example
|
|
6128
|
-
* ```typescript
|
|
6129
|
-
* const { sessions } = await auth.sessions.list({
|
|
6130
|
-
* baseUrl: 'https://your-app.api.sylphx.com/v1',
|
|
6131
|
-
* accessToken: platformJwt,
|
|
6132
|
-
* })
|
|
6133
|
-
* ```
|
|
6134
|
-
*/
|
|
6135
|
-
async list(opts) {
|
|
6136
|
-
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-sessions`, {
|
|
6137
|
-
method: "GET",
|
|
6138
|
-
headers: buildPlatformSessionsHeaders(opts.accessToken, opts.userAgent)
|
|
6139
|
-
});
|
|
6140
|
-
if (!res.ok) throw await platformSessionError(res, "sessions.list");
|
|
6141
|
-
return await res.json();
|
|
6142
|
-
},
|
|
6143
|
-
/**
|
|
6144
|
-
* Revoke a specific platform session by id.
|
|
6145
|
-
*
|
|
6146
|
-
* `sessionId` accepts either the prefixed TypeID (`sess_*`) or the
|
|
6147
|
-
* raw UUID — the BaaS side normalises via `parseIdOrError`.
|
|
6148
|
-
*
|
|
6149
|
-
* @example
|
|
6150
|
-
* ```typescript
|
|
6151
|
-
* await auth.sessions.revoke({
|
|
6152
|
-
* baseUrl,
|
|
6153
|
-
* accessToken,
|
|
6154
|
-
* sessionId: 'sess_01hxyz...',
|
|
6155
|
-
* })
|
|
6156
|
-
* ```
|
|
6157
|
-
*/
|
|
6158
|
-
async revoke(opts) {
|
|
6159
|
-
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-sessions/revoke`, {
|
|
6160
|
-
method: "POST",
|
|
6161
|
-
headers: buildPlatformSessionsHeaders(opts.accessToken, opts.userAgent),
|
|
6162
|
-
body: JSON.stringify({ sessionId: opts.sessionId })
|
|
6163
|
-
});
|
|
6164
|
-
if (!res.ok) throw await platformSessionError(res, "sessions.revoke");
|
|
6165
|
-
return await res.json();
|
|
6166
|
-
},
|
|
6167
|
-
/**
|
|
6168
|
-
* Revoke every platform session except the one presenting the
|
|
6169
|
-
* current access token. Used by "sign me out of all other devices".
|
|
6170
|
-
*
|
|
6171
|
-
* When the caller's JWT has no `sid` claim (pure-Bearer CLI/CI
|
|
6172
|
-
* flows), this degenerates to `revokeAll` — every session is
|
|
6173
|
-
* wiped — because there's no "current" row to keep.
|
|
6174
|
-
*
|
|
6175
|
-
* @example
|
|
6176
|
-
* ```typescript
|
|
6177
|
-
* const { revokedCount } = await auth.sessions.revokeOther({
|
|
6178
|
-
* baseUrl,
|
|
6179
|
-
* accessToken,
|
|
6180
|
-
* })
|
|
6181
|
-
* ```
|
|
6182
|
-
*/
|
|
6183
|
-
async revokeOther(opts) {
|
|
6184
|
-
const res = await fetch(
|
|
6185
|
-
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-sessions/revoke-other`,
|
|
6186
|
-
{
|
|
6187
|
-
method: "POST",
|
|
6188
|
-
headers: buildPlatformSessionsHeaders(opts.accessToken, opts.userAgent)
|
|
6189
|
-
}
|
|
6190
|
-
);
|
|
6191
|
-
if (!res.ok) throw await platformSessionError(res, "sessions.revokeOther");
|
|
6192
|
-
return await res.json();
|
|
6193
|
-
},
|
|
6194
|
-
/**
|
|
6195
|
-
* Revoke every platform session for the user, including the
|
|
6196
|
-
* caller's own. Used by "sign me out everywhere" — after a
|
|
6197
|
-
* password change, a compromise scare, or GDPR-style erasure.
|
|
6198
|
-
*
|
|
6199
|
-
* The response includes the count of sessions that were
|
|
6200
|
-
* revoked so the caller can surface it in a toast or audit UI.
|
|
6201
|
-
*
|
|
6202
|
-
* @example
|
|
6203
|
-
* ```typescript
|
|
6204
|
-
* const { count } = await auth.sessions.revokeAll({
|
|
6205
|
-
* baseUrl,
|
|
6206
|
-
* accessToken,
|
|
6207
|
-
* })
|
|
6208
|
-
* ```
|
|
6209
|
-
*/
|
|
6210
|
-
async revokeAll(opts) {
|
|
6211
|
-
const res = await fetch(
|
|
6212
|
-
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-sessions/revoke-all`,
|
|
6213
|
-
{
|
|
6214
|
-
method: "POST",
|
|
6215
|
-
headers: buildPlatformSessionsHeaders(opts.accessToken, opts.userAgent)
|
|
6216
|
-
}
|
|
7211
|
+
if (opts.nonce) payload.nonce = opts.nonce;
|
|
7212
|
+
const headerB64 = base64UrlEncode(new TextEncoder().encode(JSON.stringify(header)));
|
|
7213
|
+
const payloadB64 = base64UrlEncode(new TextEncoder().encode(JSON.stringify(payload)));
|
|
7214
|
+
const signingInput = `${headerB64}.${payloadB64}`;
|
|
7215
|
+
const signingBytes = new TextEncoder().encode(signingInput);
|
|
7216
|
+
const signingBuf = new Uint8Array(signingBytes.byteLength);
|
|
7217
|
+
signingBuf.set(signingBytes);
|
|
7218
|
+
const sigBuf = await crypto.subtle.sign(
|
|
7219
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
7220
|
+
opts.privateKey,
|
|
7221
|
+
signingBuf.buffer
|
|
6217
7222
|
);
|
|
6218
|
-
|
|
6219
|
-
return
|
|
6220
|
-
},
|
|
6221
|
-
/**
|
|
6222
|
-
* Rename a platform session (device label).
|
|
6223
|
-
*
|
|
6224
|
-
* `sessionId` accepts either the prefixed TypeID or the raw UUID;
|
|
6225
|
-
* `name` is a user-supplied string (≤100 chars) surfaced in the
|
|
6226
|
-
* "Active sessions" Console UI.
|
|
6227
|
-
*
|
|
6228
|
-
* @example
|
|
6229
|
-
* ```typescript
|
|
6230
|
-
* await auth.sessions.rename({
|
|
6231
|
-
* baseUrl,
|
|
6232
|
-
* accessToken,
|
|
6233
|
-
* sessionId,
|
|
6234
|
-
* name: 'MacBook (work)',
|
|
6235
|
-
* })
|
|
6236
|
-
* ```
|
|
6237
|
-
*/
|
|
6238
|
-
async rename(opts) {
|
|
6239
|
-
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-sessions/rename`, {
|
|
6240
|
-
method: "POST",
|
|
6241
|
-
headers: buildPlatformSessionsHeaders(opts.accessToken, opts.userAgent),
|
|
6242
|
-
body: JSON.stringify({
|
|
6243
|
-
sessionId: opts.sessionId,
|
|
6244
|
-
name: opts.name
|
|
6245
|
-
})
|
|
6246
|
-
});
|
|
6247
|
-
if (!res.ok) throw await platformSessionError(res, "sessions.rename");
|
|
6248
|
-
return await res.json();
|
|
7223
|
+
const sigB64 = base64UrlEncode(new Uint8Array(sigBuf));
|
|
7224
|
+
return `${signingInput}.${sigB64}`;
|
|
6249
7225
|
}
|
|
6250
7226
|
};
|
|
6251
|
-
function
|
|
6252
|
-
|
|
6253
|
-
"
|
|
6254
|
-
|
|
6255
|
-
};
|
|
6256
|
-
|
|
6257
|
-
|
|
7227
|
+
function sanitisePublicJwk(jwk) {
|
|
7228
|
+
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || !jwk.x || !jwk.y) {
|
|
7229
|
+
throw new Error("DPoP expects ES256 (EC P-256) JWK");
|
|
7230
|
+
}
|
|
7231
|
+
return { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y };
|
|
7232
|
+
}
|
|
7233
|
+
async function thumbprintFromJwk(jwk) {
|
|
7234
|
+
const canonical = JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y });
|
|
7235
|
+
return sha256Base64Url(new TextEncoder().encode(canonical));
|
|
7236
|
+
}
|
|
7237
|
+
async function sha256Base64Url(data) {
|
|
7238
|
+
const copy = new Uint8Array(data.byteLength);
|
|
7239
|
+
copy.set(data);
|
|
7240
|
+
const digest2 = await crypto.subtle.digest("SHA-256", copy.buffer);
|
|
7241
|
+
return base64UrlEncode(new Uint8Array(digest2));
|
|
7242
|
+
}
|
|
7243
|
+
function base64UrlEncode(bytes) {
|
|
7244
|
+
let s = "";
|
|
7245
|
+
for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
|
|
7246
|
+
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
7247
|
+
}
|
|
7248
|
+
function stripQueryAndFragment(uri) {
|
|
7249
|
+
try {
|
|
7250
|
+
const u = new URL(uri);
|
|
7251
|
+
return `${u.protocol}//${u.host}${u.pathname}`;
|
|
7252
|
+
} catch {
|
|
7253
|
+
const q = uri.indexOf("?");
|
|
7254
|
+
const h = uri.indexOf("#");
|
|
7255
|
+
const cut = [q, h].filter((i) => i >= 0).sort((a, b) => a - b)[0];
|
|
7256
|
+
return cut === void 0 ? uri : uri.slice(0, cut);
|
|
7257
|
+
}
|
|
7258
|
+
}
|
|
7259
|
+
function randomJti() {
|
|
7260
|
+
const bytes = new Uint8Array(16);
|
|
7261
|
+
crypto.getRandomValues(bytes);
|
|
7262
|
+
return base64UrlEncode(bytes);
|
|
6258
7263
|
}
|
|
7264
|
+
|
|
7265
|
+
// src/platform-auth.ts
|
|
7266
|
+
init_errors();
|
|
6259
7267
|
var platformAuth = {
|
|
6260
|
-
/**
|
|
6261
|
-
* Rotate a Platform refresh token. The presented token is consumed
|
|
6262
|
-
* single-use; the response carries a fresh access JWT plus the
|
|
6263
|
-
* rotated refresh token that supersedes it.
|
|
6264
|
-
*
|
|
6265
|
-
* On reuse-detection / expiry the server returns 401 — the SDK
|
|
6266
|
-
* preserves the upstream message so callers can pattern-match
|
|
6267
|
-
* `"reuse"` per RFC 6819 §5.2.2.3 and scrub local credentials.
|
|
6268
|
-
*
|
|
6269
|
-
* @example
|
|
6270
|
-
* ```typescript
|
|
6271
|
-
* const tokens = await auth.platformAuth.refresh({
|
|
6272
|
-
* baseUrl: 'https://sylphx.com',
|
|
6273
|
-
* refreshToken: stored.refreshToken,
|
|
6274
|
-
* })
|
|
6275
|
-
* ```
|
|
6276
|
-
*/
|
|
6277
7268
|
async refresh(opts) {
|
|
6278
7269
|
const headers = { "Content-Type": "application/json" };
|
|
6279
7270
|
if (opts.userAgent) headers["User-Agent"] = opts.userAgent;
|
|
@@ -6286,19 +7277,6 @@ var platformAuth = {
|
|
|
6286
7277
|
if (!res.ok) throw await platformAuthError(res, "platformAuth.refresh");
|
|
6287
7278
|
return await res.json();
|
|
6288
7279
|
},
|
|
6289
|
-
/**
|
|
6290
|
-
* Revoke a Platform refresh token (logout). Server-side revocation
|
|
6291
|
-
* failure is the caller's call to surface — local-credential cleanup
|
|
6292
|
-
* is the CLI's responsibility (logout must succeed offline).
|
|
6293
|
-
*
|
|
6294
|
-
* @example
|
|
6295
|
-
* ```typescript
|
|
6296
|
-
* await auth.platformAuth.logout({
|
|
6297
|
-
* baseUrl: 'https://sylphx.com',
|
|
6298
|
-
* refreshToken: stored.refreshToken,
|
|
6299
|
-
* })
|
|
6300
|
-
* ```
|
|
6301
|
-
*/
|
|
6302
7280
|
async logout(opts) {
|
|
6303
7281
|
const headers = { "Content-Type": "application/json" };
|
|
6304
7282
|
if (opts.userAgent) headers["User-Agent"] = opts.userAgent;
|
|
@@ -6312,7 +7290,6 @@ var platformAuth = {
|
|
|
6312
7290
|
}
|
|
6313
7291
|
};
|
|
6314
7292
|
async function platformAuthError(res, operation) {
|
|
6315
|
-
const { SylphxError: SylphxError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
6316
7293
|
const body = await res.text().catch(() => "");
|
|
6317
7294
|
let code = "platform_auth_error";
|
|
6318
7295
|
let message2 = `${operation} failed: HTTP ${res.status}`;
|
|
@@ -6334,327 +7311,137 @@ async function platformAuthError(res, operation) {
|
|
|
6334
7311
|
else if (res.status === 429) errorCode = "TOO_MANY_REQUESTS";
|
|
6335
7312
|
else if (res.status >= 500) errorCode = "INTERNAL_SERVER_ERROR";
|
|
6336
7313
|
else errorCode = "BAD_REQUEST";
|
|
6337
|
-
return new
|
|
6338
|
-
code: errorCode,
|
|
6339
|
-
status: res.status,
|
|
6340
|
-
data: { operation, code }
|
|
6341
|
-
});
|
|
6342
|
-
}
|
|
6343
|
-
async function platformSessionError(res, operation) {
|
|
6344
|
-
const { SylphxError: SylphxError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
6345
|
-
const body = await res.text().catch(() => "");
|
|
6346
|
-
let code = "platform_sessions_error";
|
|
6347
|
-
let message2 = `${operation} failed: HTTP ${res.status}`;
|
|
6348
|
-
try {
|
|
6349
|
-
const parsed = JSON.parse(body);
|
|
6350
|
-
if (parsed.error) code = parsed.error;
|
|
6351
|
-
if (parsed.error_description) message2 = parsed.error_description;
|
|
6352
|
-
else if (parsed.message) message2 = parsed.message;
|
|
6353
|
-
} catch {
|
|
6354
|
-
}
|
|
6355
|
-
let errorCode;
|
|
6356
|
-
if (res.status === 401) errorCode = "UNAUTHORIZED";
|
|
6357
|
-
else if (res.status === 404) errorCode = "NOT_FOUND";
|
|
6358
|
-
else if (res.status === 429) errorCode = "TOO_MANY_REQUESTS";
|
|
6359
|
-
else if (res.status >= 500) errorCode = "INTERNAL_SERVER_ERROR";
|
|
6360
|
-
else errorCode = "BAD_REQUEST";
|
|
6361
|
-
return new SylphxError2(message2, {
|
|
7314
|
+
return new SylphxError(message2, {
|
|
6362
7315
|
code: errorCode,
|
|
6363
7316
|
status: res.status,
|
|
6364
7317
|
data: { operation, code }
|
|
6365
7318
|
});
|
|
6366
7319
|
}
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
/**
|
|
6386
|
-
* Check whether the authenticated platform user has a password set.
|
|
6387
|
-
*
|
|
6388
|
-
* Returns `{ hasPassword: true }` for users that signed up with
|
|
6389
|
-
* email+password (or later called `set`), `{ hasPassword: false }`
|
|
6390
|
-
* for OAuth-only users (e.g. signed up via Google/GitHub).
|
|
6391
|
-
*
|
|
6392
|
-
* @example
|
|
6393
|
-
* ```typescript
|
|
6394
|
-
* const { hasPassword } = await auth.password.status({
|
|
6395
|
-
* baseUrl: 'https://your-app.api.sylphx.com/v1',
|
|
6396
|
-
* accessToken: platformJwt,
|
|
6397
|
-
* })
|
|
6398
|
-
* ```
|
|
6399
|
-
*/
|
|
6400
|
-
async status(opts) {
|
|
6401
|
-
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-password/status`, {
|
|
6402
|
-
method: "GET",
|
|
6403
|
-
headers: buildPlatformPasswordHeaders(opts.accessToken, opts.userAgent)
|
|
6404
|
-
});
|
|
6405
|
-
if (!res.ok) throw await platformPasswordError(res, "password.status");
|
|
6406
|
-
return await res.json();
|
|
6407
|
-
},
|
|
6408
|
-
/**
|
|
6409
|
-
* Set an initial password for an OAuth-only user.
|
|
6410
|
-
*
|
|
6411
|
-
* Fails with 400 if the user already has a password (use `change`
|
|
6412
|
-
* instead), if the password is <8 characters, or if HIBP reports
|
|
6413
|
-
* the password as breached. BaaS invalidates every other session
|
|
6414
|
-
* for the user (keeping the caller's current one) after a
|
|
6415
|
-
* successful set.
|
|
6416
|
-
*
|
|
6417
|
-
* @example
|
|
6418
|
-
* ```typescript
|
|
6419
|
-
* await auth.password.set({
|
|
6420
|
-
* baseUrl,
|
|
6421
|
-
* accessToken,
|
|
6422
|
-
* password: 'correct-horse-battery-staple',
|
|
6423
|
-
* })
|
|
6424
|
-
* ```
|
|
6425
|
-
*/
|
|
6426
|
-
async set(opts) {
|
|
6427
|
-
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-password/set`, {
|
|
6428
|
-
method: "POST",
|
|
6429
|
-
headers: buildPlatformPasswordHeaders(opts.accessToken, opts.userAgent),
|
|
6430
|
-
body: JSON.stringify({
|
|
6431
|
-
password: opts.password
|
|
6432
|
-
})
|
|
6433
|
-
});
|
|
6434
|
-
if (!res.ok) throw await platformPasswordError(res, "password.set");
|
|
7320
|
+
|
|
7321
|
+
// src/platform-impersonation.ts
|
|
7322
|
+
init_errors();
|
|
7323
|
+
var impersonation = {
|
|
7324
|
+
async start(opts) {
|
|
7325
|
+
const res = await fetch(
|
|
7326
|
+
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-impersonation/start`,
|
|
7327
|
+
{
|
|
7328
|
+
method: "POST",
|
|
7329
|
+
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent),
|
|
7330
|
+
body: JSON.stringify({
|
|
7331
|
+
targetUserId: opts.targetUserId,
|
|
7332
|
+
...opts.ipAddress !== void 0 && { ipAddress: opts.ipAddress },
|
|
7333
|
+
...opts.userAgent !== void 0 && { userAgent: opts.userAgent }
|
|
7334
|
+
})
|
|
7335
|
+
}
|
|
7336
|
+
);
|
|
7337
|
+
if (!res.ok) throw await impersonationError(res, "impersonation.start");
|
|
6435
7338
|
return await res.json();
|
|
6436
7339
|
},
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
*
|
|
6440
|
-
* Verifies `currentPassword` server-side; a mismatch returns 401.
|
|
6441
|
-
* OAuth-only users (no existing password) get 400 — use `set`
|
|
6442
|
-
* instead. New password must be ≥8 characters and must not be in
|
|
6443
|
-
* HIBP's breach database. BaaS invalidates every other session
|
|
6444
|
-
* for the user (keeping the caller's current one) after a
|
|
6445
|
-
* successful change.
|
|
6446
|
-
*
|
|
6447
|
-
* @example
|
|
6448
|
-
* ```typescript
|
|
6449
|
-
* await auth.password.change({
|
|
6450
|
-
* baseUrl,
|
|
6451
|
-
* accessToken,
|
|
6452
|
-
* currentPassword: 'old-plaintext',
|
|
6453
|
-
* newPassword: 'new-plaintext',
|
|
6454
|
-
* })
|
|
6455
|
-
* ```
|
|
6456
|
-
*/
|
|
6457
|
-
async change(opts) {
|
|
6458
|
-
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-password/change`, {
|
|
7340
|
+
async end(opts) {
|
|
7341
|
+
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-impersonation/end`, {
|
|
6459
7342
|
method: "POST",
|
|
6460
|
-
headers:
|
|
6461
|
-
body: JSON.stringify({
|
|
6462
|
-
currentPassword: opts.currentPassword,
|
|
6463
|
-
newPassword: opts.newPassword
|
|
6464
|
-
})
|
|
6465
|
-
});
|
|
6466
|
-
if (!res.ok) throw await platformPasswordError(res, "password.change");
|
|
6467
|
-
return await res.json();
|
|
6468
|
-
}
|
|
6469
|
-
};
|
|
6470
|
-
function buildPlatformPasswordHeaders(accessToken, userAgent) {
|
|
6471
|
-
const headers = {
|
|
6472
|
-
"Content-Type": "application/json",
|
|
6473
|
-
Authorization: `Bearer ${accessToken}`
|
|
6474
|
-
};
|
|
6475
|
-
if (userAgent) headers["User-Agent"] = userAgent;
|
|
6476
|
-
return headers;
|
|
6477
|
-
}
|
|
6478
|
-
async function platformPasswordError(res, operation) {
|
|
6479
|
-
const { SylphxError: SylphxError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
6480
|
-
const body = await res.text().catch(() => "");
|
|
6481
|
-
let code = "platform_password_error";
|
|
6482
|
-
let message2 = `${operation} failed: HTTP ${res.status}`;
|
|
6483
|
-
try {
|
|
6484
|
-
const parsed = JSON.parse(body);
|
|
6485
|
-
if (parsed.error) code = parsed.error;
|
|
6486
|
-
if (parsed.error_description) message2 = parsed.error_description;
|
|
6487
|
-
else if (parsed.message) message2 = parsed.message;
|
|
6488
|
-
} catch {
|
|
6489
|
-
}
|
|
6490
|
-
let errorCode;
|
|
6491
|
-
if (res.status === 401) errorCode = "UNAUTHORIZED";
|
|
6492
|
-
else if (res.status === 404) errorCode = "NOT_FOUND";
|
|
6493
|
-
else if (res.status === 429) errorCode = "TOO_MANY_REQUESTS";
|
|
6494
|
-
else if (res.status >= 500) errorCode = "INTERNAL_SERVER_ERROR";
|
|
6495
|
-
else errorCode = "BAD_REQUEST";
|
|
6496
|
-
return new SylphxError2(message2, {
|
|
6497
|
-
code: errorCode,
|
|
6498
|
-
status: res.status,
|
|
6499
|
-
data: { operation, code }
|
|
6500
|
-
});
|
|
6501
|
-
}
|
|
6502
|
-
var user = {
|
|
6503
|
-
/**
|
|
6504
|
-
* Export every piece of personal data the platform holds about the
|
|
6505
|
-
* authenticated user (GDPR Article 20 — right to data portability).
|
|
6506
|
-
*
|
|
6507
|
-
* The returned record is deliberately loose — it contains the user
|
|
6508
|
-
* row, sessions, OAuth accounts, login history, security alerts,
|
|
6509
|
-
* organization memberships, subscriptions, per-project memberships,
|
|
6510
|
-
* and storage file metadata. Shape varies with customer provisioning.
|
|
6511
|
-
*
|
|
6512
|
-
* @example
|
|
6513
|
-
* ```typescript
|
|
6514
|
-
* const data = await auth.user.exportData({
|
|
6515
|
-
* baseUrl: 'https://your-app.api.sylphx.com/v1',
|
|
6516
|
-
* accessToken: platformJwt,
|
|
6517
|
-
* })
|
|
6518
|
-
* downloadAsJson(data, 'my-sylphx-data.json')
|
|
6519
|
-
* ```
|
|
6520
|
-
*/
|
|
6521
|
-
async exportData(opts) {
|
|
6522
|
-
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-user/export`, {
|
|
6523
|
-
method: "GET",
|
|
6524
|
-
headers: buildPlatformUserHeaders(opts.accessToken, opts.userAgent)
|
|
7343
|
+
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent),
|
|
7344
|
+
body: JSON.stringify(opts.sessionId !== void 0 ? { sessionId: opts.sessionId } : {})
|
|
6525
7345
|
});
|
|
6526
|
-
if (!res.ok) throw await
|
|
7346
|
+
if (!res.ok) throw await impersonationError(res, "impersonation.end");
|
|
6527
7347
|
return await res.json();
|
|
6528
7348
|
},
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
*
|
|
6539
|
-
* @remarks
|
|
6540
|
-
* This operation is irreversible. Production callers SHOULD require
|
|
6541
|
-
* a challenge step (2FA / password confirm / WebAuthn) before
|
|
6542
|
-
* invoking this — the BaaS route does NOT perform challenge
|
|
6543
|
-
* verification in Phase 2d. ADR-089 Phase 5.11 lands passkey-primary
|
|
6544
|
-
* with WebAuthn-required step-up and will add the check at the
|
|
6545
|
-
* BaaS boundary.
|
|
6546
|
-
*
|
|
6547
|
-
* @example
|
|
6548
|
-
* ```typescript
|
|
6549
|
-
* const result = await auth.user.deleteAccount({
|
|
6550
|
-
* baseUrl,
|
|
6551
|
-
* accessToken,
|
|
6552
|
-
* reason: 'user_request',
|
|
6553
|
-
* })
|
|
6554
|
-
* if (result.success) signOutAndRedirect('/goodbye')
|
|
6555
|
-
* ```
|
|
6556
|
-
*/
|
|
6557
|
-
async deleteAccount(opts) {
|
|
6558
|
-
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-user/account`, {
|
|
6559
|
-
method: "DELETE",
|
|
6560
|
-
headers: buildPlatformUserHeaders(opts.accessToken, opts.userAgent),
|
|
6561
|
-
body: JSON.stringify({
|
|
6562
|
-
...opts.reason !== void 0 && { reason: opts.reason }
|
|
6563
|
-
})
|
|
6564
|
-
});
|
|
6565
|
-
if (!res.ok) throw await platformUserError(res, "user.deleteAccount");
|
|
7349
|
+
async info(opts) {
|
|
7350
|
+
const res = await fetch(
|
|
7351
|
+
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-impersonation/info/${encodeURIComponent(opts.sessionId)}`,
|
|
7352
|
+
{
|
|
7353
|
+
method: "GET",
|
|
7354
|
+
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent)
|
|
7355
|
+
}
|
|
7356
|
+
);
|
|
7357
|
+
if (!res.ok) throw await impersonationError(res, "impersonation.info");
|
|
6566
7358
|
return await res.json();
|
|
6567
7359
|
},
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
* if (cur.status === 'complete') break
|
|
6584
|
-
* if (cur.status === 'failed') throw new Error(cur.errorMessage ?? 'export failed')
|
|
6585
|
-
* await new Promise(r => setTimeout(r, 2000))
|
|
6586
|
-
* }
|
|
6587
|
-
* const blob = await auth.user.exports.download({ baseUrl, accessToken, id: job.id })
|
|
6588
|
-
* saveAs(blob, 'sylphx-export.json')
|
|
6589
|
-
* ```
|
|
6590
|
-
*
|
|
6591
|
-
* Rate limit: 1 `initiate` per 24h per user. Polling + downloading
|
|
6592
|
-
* are NOT rate-limited through that bucket (they're cheap reads).
|
|
6593
|
-
*/
|
|
6594
|
-
exports: {
|
|
6595
|
-
/**
|
|
6596
|
-
* Kick off an export job. Returns the job row in `pending` status
|
|
6597
|
-
* with a 202-Accepted semantic — the HTTP layer has accepted the
|
|
6598
|
-
* request but the payload is not yet materialized. Poll
|
|
6599
|
-
* `status({ id })` until `status === 'complete'`.
|
|
6600
|
-
*/
|
|
6601
|
-
async initiate(opts) {
|
|
6602
|
-
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-user/export`, {
|
|
7360
|
+
async active(opts) {
|
|
7361
|
+
const res = await fetch(
|
|
7362
|
+
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-impersonation/active`,
|
|
7363
|
+
{
|
|
7364
|
+
method: "GET",
|
|
7365
|
+
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent)
|
|
7366
|
+
}
|
|
7367
|
+
);
|
|
7368
|
+
if (!res.ok) throw await impersonationError(res, "impersonation.active");
|
|
7369
|
+
return await res.json();
|
|
7370
|
+
},
|
|
7371
|
+
async startChallenge(opts) {
|
|
7372
|
+
const res = await fetch(
|
|
7373
|
+
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-impersonation/start-challenge`,
|
|
7374
|
+
{
|
|
6603
7375
|
method: "POST",
|
|
6604
|
-
headers:
|
|
6605
|
-
body: JSON.stringify(
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
7376
|
+
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent),
|
|
7377
|
+
body: JSON.stringify({
|
|
7378
|
+
targetUserId: opts.targetUserId,
|
|
7379
|
+
reason: opts.reason
|
|
7380
|
+
})
|
|
7381
|
+
}
|
|
7382
|
+
);
|
|
7383
|
+
if (!res.ok) throw await impersonationError(res, "impersonation.startChallenge");
|
|
7384
|
+
return await res.json();
|
|
7385
|
+
},
|
|
7386
|
+
async startStepup(opts) {
|
|
7387
|
+
const res = await fetch(
|
|
7388
|
+
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-impersonation/start`,
|
|
7389
|
+
{
|
|
7390
|
+
method: "POST",
|
|
7391
|
+
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent),
|
|
7392
|
+
body: JSON.stringify({
|
|
7393
|
+
requestId: opts.requestId,
|
|
7394
|
+
challengeKey: opts.challengeKey,
|
|
7395
|
+
assertion: opts.assertion,
|
|
7396
|
+
...opts.emergencyBypass ? { emergencyBypass: true } : {}
|
|
7397
|
+
})
|
|
7398
|
+
}
|
|
7399
|
+
);
|
|
7400
|
+
if (!res.ok) throw await impersonationError(res, "impersonation.startStepup");
|
|
7401
|
+
return await res.json();
|
|
7402
|
+
},
|
|
7403
|
+
async respondConsent(opts) {
|
|
7404
|
+
const res = await fetch(
|
|
7405
|
+
`${opts.baseUrl.replace(/\/$/, "")}/auth/impersonation-consent/${encodeURIComponent(opts.requestId)}`,
|
|
7406
|
+
{
|
|
7407
|
+
method: "POST",
|
|
7408
|
+
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent),
|
|
7409
|
+
body: JSON.stringify({ decision: opts.decision })
|
|
7410
|
+
}
|
|
7411
|
+
);
|
|
7412
|
+
if (!res.ok) throw await impersonationError(res, "impersonation.respondConsent");
|
|
7413
|
+
return await res.json();
|
|
7414
|
+
},
|
|
7415
|
+
async listRequests(opts) {
|
|
7416
|
+
const params = new URLSearchParams();
|
|
7417
|
+
if (opts.filter?.operatorId) params.set("operatorId", opts.filter.operatorId);
|
|
7418
|
+
if (opts.filter?.targetUserId) params.set("targetUserId", opts.filter.targetUserId);
|
|
7419
|
+
if (opts.filter?.status) params.set("status", opts.filter.status);
|
|
7420
|
+
if (opts.filter?.limit != null) params.set("limit", String(opts.filter.limit));
|
|
7421
|
+
const qs = params.toString();
|
|
7422
|
+
const res = await fetch(
|
|
7423
|
+
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-impersonation/requests${qs ? `?${qs}` : ""}`,
|
|
7424
|
+
{
|
|
7425
|
+
method: "GET",
|
|
7426
|
+
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent)
|
|
7427
|
+
}
|
|
7428
|
+
);
|
|
7429
|
+
if (!res.ok) throw await impersonationError(res, "impersonation.listRequests");
|
|
7430
|
+
return await res.json();
|
|
7431
|
+
},
|
|
7432
|
+
async endSession(opts) {
|
|
7433
|
+
const res = await fetch(
|
|
7434
|
+
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-impersonation/end/${encodeURIComponent(opts.requestId)}`,
|
|
7435
|
+
{
|
|
7436
|
+
method: "POST",
|
|
7437
|
+
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent)
|
|
7438
|
+
}
|
|
7439
|
+
);
|
|
7440
|
+
if (!res.ok) throw await impersonationError(res, "impersonation.endSession");
|
|
7441
|
+
return await res.json();
|
|
6655
7442
|
}
|
|
6656
7443
|
};
|
|
6657
|
-
function
|
|
7444
|
+
function buildImpersonationHeaders(accessToken, userAgent) {
|
|
6658
7445
|
const headers = {
|
|
6659
7446
|
"Content-Type": "application/json",
|
|
6660
7447
|
Authorization: `Bearer ${accessToken}`
|
|
@@ -6662,10 +7449,9 @@ function buildPlatformUserHeaders(accessToken, userAgent) {
|
|
|
6662
7449
|
if (userAgent) headers["User-Agent"] = userAgent;
|
|
6663
7450
|
return headers;
|
|
6664
7451
|
}
|
|
6665
|
-
async function
|
|
6666
|
-
const { SylphxError: SylphxError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
7452
|
+
async function impersonationError(res, operation) {
|
|
6667
7453
|
const body = await res.text().catch(() => "");
|
|
6668
|
-
let code = "
|
|
7454
|
+
let code = "platform_impersonation_error";
|
|
6669
7455
|
let message2 = `${operation} failed: HTTP ${res.status}`;
|
|
6670
7456
|
try {
|
|
6671
7457
|
const parsed = JSON.parse(body);
|
|
@@ -6676,16 +7462,20 @@ async function platformUserError(res, operation) {
|
|
|
6676
7462
|
}
|
|
6677
7463
|
let errorCode;
|
|
6678
7464
|
if (res.status === 401) errorCode = "UNAUTHORIZED";
|
|
7465
|
+
else if (res.status === 403) errorCode = "UNAUTHORIZED";
|
|
6679
7466
|
else if (res.status === 404) errorCode = "NOT_FOUND";
|
|
7467
|
+
else if (res.status === 409) errorCode = "CONFLICT";
|
|
6680
7468
|
else if (res.status === 429) errorCode = "TOO_MANY_REQUESTS";
|
|
6681
7469
|
else if (res.status >= 500) errorCode = "INTERNAL_SERVER_ERROR";
|
|
6682
7470
|
else errorCode = "BAD_REQUEST";
|
|
6683
|
-
return new
|
|
7471
|
+
return new SylphxError(message2, {
|
|
6684
7472
|
code: errorCode,
|
|
6685
7473
|
status: res.status,
|
|
6686
7474
|
data: { operation, code }
|
|
6687
7475
|
});
|
|
6688
7476
|
}
|
|
7477
|
+
|
|
7478
|
+
// src/platform-jwt.ts
|
|
6689
7479
|
var jwtJwksCache = null;
|
|
6690
7480
|
function resetPlatformJwksCache() {
|
|
6691
7481
|
jwtJwksCache = null;
|
|
@@ -6797,24 +7587,167 @@ var cookies = {
|
|
|
6797
7587
|
return body;
|
|
6798
7588
|
}
|
|
6799
7589
|
};
|
|
7590
|
+
|
|
7591
|
+
// src/platform-oauth.ts
|
|
7592
|
+
init_errors();
|
|
7593
|
+
|
|
7594
|
+
// src/oauth-token.ts
|
|
7595
|
+
init_errors();
|
|
7596
|
+
var OAUTH_TOKEN_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
7597
|
+
"invalid_request",
|
|
7598
|
+
"invalid_client",
|
|
7599
|
+
"invalid_grant",
|
|
7600
|
+
"unauthorized_client",
|
|
7601
|
+
"unsupported_grant_type",
|
|
7602
|
+
"invalid_scope",
|
|
7603
|
+
"authorization_pending",
|
|
7604
|
+
"slow_down",
|
|
7605
|
+
"access_denied",
|
|
7606
|
+
"expired_token"
|
|
7607
|
+
]);
|
|
7608
|
+
function isRecord(value) {
|
|
7609
|
+
return typeof value === "object" && value !== null;
|
|
7610
|
+
}
|
|
7611
|
+
function requireString(record, key) {
|
|
7612
|
+
const value = record[key];
|
|
7613
|
+
if (typeof value !== "string") throw new Error(`Invalid OAuth token field: ${key}`);
|
|
7614
|
+
return value;
|
|
7615
|
+
}
|
|
7616
|
+
function optionalString(record, key) {
|
|
7617
|
+
const value = record[key];
|
|
7618
|
+
if (value === void 0) return void 0;
|
|
7619
|
+
if (typeof value !== "string") throw new Error(`Invalid OAuth token field: ${key}`);
|
|
7620
|
+
return value;
|
|
7621
|
+
}
|
|
7622
|
+
function optionalField(key, value) {
|
|
7623
|
+
return value === void 0 ? {} : { [key]: value };
|
|
7624
|
+
}
|
|
7625
|
+
function requireNumber(record, key) {
|
|
7626
|
+
const value = record[key];
|
|
7627
|
+
if (typeof value !== "number") throw new Error(`Invalid OAuth token field: ${key}`);
|
|
7628
|
+
return value;
|
|
7629
|
+
}
|
|
7630
|
+
function assertGrantType(record, grantType) {
|
|
7631
|
+
if (record.grant_type !== grantType) {
|
|
7632
|
+
throw new Error(`Invalid OAuth grant_type: expected ${grantType}`);
|
|
7633
|
+
}
|
|
7634
|
+
}
|
|
7635
|
+
function decodeOAuthTokenRequest(input) {
|
|
7636
|
+
if (!isRecord(input)) throw new Error("Invalid OAuth token request");
|
|
7637
|
+
switch (input.grant_type) {
|
|
7638
|
+
case "authorization_code":
|
|
7639
|
+
assertGrantType(input, "authorization_code");
|
|
7640
|
+
return {
|
|
7641
|
+
grant_type: "authorization_code",
|
|
7642
|
+
code: requireString(input, "code"),
|
|
7643
|
+
redirect_uri: requireString(input, "redirect_uri"),
|
|
7644
|
+
client_id: requireString(input, "client_id"),
|
|
7645
|
+
code_verifier: requireString(input, "code_verifier"),
|
|
7646
|
+
...optionalField("client_secret", optionalString(input, "client_secret"))
|
|
7647
|
+
};
|
|
7648
|
+
case "refresh_token":
|
|
7649
|
+
assertGrantType(input, "refresh_token");
|
|
7650
|
+
return {
|
|
7651
|
+
grant_type: "refresh_token",
|
|
7652
|
+
refresh_token: requireString(input, "refresh_token"),
|
|
7653
|
+
client_id: requireString(input, "client_id"),
|
|
7654
|
+
...optionalField("client_secret", optionalString(input, "client_secret")),
|
|
7655
|
+
...optionalField("scope", optionalString(input, "scope"))
|
|
7656
|
+
};
|
|
7657
|
+
case "urn:ietf:params:oauth:grant-type:device_code":
|
|
7658
|
+
assertGrantType(input, "urn:ietf:params:oauth:grant-type:device_code");
|
|
7659
|
+
return {
|
|
7660
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
7661
|
+
device_code: requireString(input, "device_code"),
|
|
7662
|
+
client_id: requireString(input, "client_id"),
|
|
7663
|
+
...optionalField("client_secret", optionalString(input, "client_secret"))
|
|
7664
|
+
};
|
|
7665
|
+
case "client_credentials":
|
|
7666
|
+
assertGrantType(input, "client_credentials");
|
|
7667
|
+
return {
|
|
7668
|
+
grant_type: "client_credentials",
|
|
7669
|
+
client_id: requireString(input, "client_id"),
|
|
7670
|
+
client_secret: requireString(input, "client_secret"),
|
|
7671
|
+
...optionalField("scope", optionalString(input, "scope"))
|
|
7672
|
+
};
|
|
7673
|
+
default:
|
|
7674
|
+
throw new Error("Unsupported OAuth grant_type");
|
|
7675
|
+
}
|
|
7676
|
+
}
|
|
7677
|
+
function oauthTokenFormBody(input) {
|
|
7678
|
+
const request = decodeOAuthTokenRequest(input);
|
|
7679
|
+
return new URLSearchParams(
|
|
7680
|
+
Object.entries(request).filter(
|
|
7681
|
+
(entry) => typeof entry[1] === "string"
|
|
7682
|
+
)
|
|
7683
|
+
).toString();
|
|
7684
|
+
}
|
|
7685
|
+
function decodeOAuthTokenResult(value) {
|
|
7686
|
+
if (!isRecord(value)) throw new Error("Invalid OAuth token response");
|
|
7687
|
+
const tokenType = requireString(value, "token_type");
|
|
7688
|
+
if (tokenType !== "Bearer") throw new Error("Invalid OAuth token_type");
|
|
7689
|
+
return {
|
|
7690
|
+
access_token: requireString(value, "access_token"),
|
|
7691
|
+
token_type: "Bearer",
|
|
7692
|
+
expires_in: requireNumber(value, "expires_in"),
|
|
7693
|
+
refresh_token: requireString(value, "refresh_token"),
|
|
7694
|
+
refresh_expires_at: requireString(value, "refresh_expires_at"),
|
|
7695
|
+
scope: requireString(value, "scope")
|
|
7696
|
+
};
|
|
7697
|
+
}
|
|
7698
|
+
function decodeOAuthClientCredentialsResult(value) {
|
|
7699
|
+
if (!isRecord(value)) throw new Error("Invalid OAuth client credentials response");
|
|
7700
|
+
const tokenType = requireString(value, "token_type");
|
|
7701
|
+
if (tokenType !== "Bearer") throw new Error("Invalid OAuth token_type");
|
|
7702
|
+
return {
|
|
7703
|
+
access_token: requireString(value, "access_token"),
|
|
7704
|
+
token_type: "Bearer",
|
|
7705
|
+
expires_in: requireNumber(value, "expires_in"),
|
|
7706
|
+
scope: requireString(value, "scope")
|
|
7707
|
+
};
|
|
7708
|
+
}
|
|
7709
|
+
function decodeOAuthTokenError(value) {
|
|
7710
|
+
if (!isRecord(value)) throw new Error("Invalid OAuth token error response");
|
|
7711
|
+
const error = requireString(value, "error");
|
|
7712
|
+
if (!OAUTH_TOKEN_ERROR_CODES.has(error)) {
|
|
7713
|
+
throw new Error("Invalid OAuth token error code");
|
|
7714
|
+
}
|
|
7715
|
+
return {
|
|
7716
|
+
error,
|
|
7717
|
+
...optionalField("error_description", optionalString(value, "error_description")),
|
|
7718
|
+
...optionalField("error_uri", optionalString(value, "error_uri"))
|
|
7719
|
+
};
|
|
7720
|
+
}
|
|
7721
|
+
async function oauthTokenError(res, operation) {
|
|
7722
|
+
const body = await res.text().catch(() => "");
|
|
7723
|
+
let code = "oauth_error";
|
|
7724
|
+
let message2 = `${operation} failed: HTTP ${res.status}`;
|
|
7725
|
+
try {
|
|
7726
|
+
const parsed = decodeOAuthTokenError(JSON.parse(body));
|
|
7727
|
+
code = parsed.error;
|
|
7728
|
+
if (parsed.error_description) message2 = parsed.error_description;
|
|
7729
|
+
} catch {
|
|
7730
|
+
}
|
|
7731
|
+
let errorCode;
|
|
7732
|
+
if (res.status === 401) errorCode = "UNAUTHORIZED";
|
|
7733
|
+
else if (res.status >= 500) errorCode = "INTERNAL_SERVER_ERROR";
|
|
7734
|
+
else errorCode = "BAD_REQUEST";
|
|
7735
|
+
return new SylphxError(message2, {
|
|
7736
|
+
code: errorCode,
|
|
7737
|
+
status: res.status,
|
|
7738
|
+
data: { oauthError: code }
|
|
7739
|
+
});
|
|
7740
|
+
}
|
|
7741
|
+
|
|
7742
|
+
// src/platform-oauth.ts
|
|
6800
7743
|
var oauth = {
|
|
6801
7744
|
/**
|
|
6802
7745
|
* Mint a platform-audience access token from supplied claims.
|
|
6803
7746
|
*
|
|
6804
7747
|
* Service-to-service call — authenticated via
|
|
6805
|
-
* `SYLPHX_INTERNAL_TOKEN` shared secret
|
|
6806
|
-
*
|
|
6807
|
-
*
|
|
6808
|
-
* TODO: Phase 6 — prefer SPIFFE SVID over shared-secret auth.
|
|
6809
|
-
*
|
|
6810
|
-
* @example
|
|
6811
|
-
* ```typescript
|
|
6812
|
-
* const { accessToken, expiresIn } = await auth.oauth.mintAccessToken({
|
|
6813
|
-
* baseUrl: 'https://your-app.api.sylphx.com/v1',
|
|
6814
|
-
* internalToken: process.env.SYLPHX_INTERNAL_TOKEN!,
|
|
6815
|
-
* claims: { sub: user.id, email: user.email, app_id: 'platform', role: 'member', email_verified: true },
|
|
6816
|
-
* })
|
|
6817
|
-
* ```
|
|
7748
|
+
* `SYLPHX_INTERNAL_TOKEN` shared secret until ADR-068's
|
|
7749
|
+
* SPIFFE SVID mTLS platform-auth flip makes workload identity the
|
|
7750
|
+
* only accepted internal caller credential.
|
|
6818
7751
|
*/
|
|
6819
7752
|
async mintAccessToken(opts) {
|
|
6820
7753
|
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-jwt/mint`, {
|
|
@@ -6829,181 +7762,78 @@ var oauth = {
|
|
|
6829
7762
|
if (!res.ok) throw await platformJwtError(res, "oauth.mintAccessToken");
|
|
6830
7763
|
return await res.json();
|
|
6831
7764
|
},
|
|
6832
|
-
/**
|
|
6833
|
-
* Exchange an OAuth 2.0 authorization_code for an access + refresh token
|
|
6834
|
-
* pair (ADR-089 Phase 5.1b — RFC 6749 §4.1.3). PKCE S256 mandatory per
|
|
6835
|
-
* OAuth 2.1 baseline.
|
|
6836
|
-
*
|
|
6837
|
-
* @example
|
|
6838
|
-
* ```typescript
|
|
6839
|
-
* const { verifier, challenge } = await generatePkce()
|
|
6840
|
-
* // user redirected to /oauth/authorize?...&code_challenge=<challenge>
|
|
6841
|
-
* // ...user approves, browser hits your redirect_uri with ?code=<code>
|
|
6842
|
-
* const tokens = await auth.oauth.exchangeAuthorizationCode({
|
|
6843
|
-
* baseUrl: 'https://api.sylphx.com/v1',
|
|
6844
|
-
* clientId: 'sylphx-console',
|
|
6845
|
-
* clientSecret: process.env.CONSOLE_CLIENT_SECRET,
|
|
6846
|
-
* code,
|
|
6847
|
-
* redirectUri: 'https://console.sylphx.com/auth/callback',
|
|
6848
|
-
* codeVerifier: verifier,
|
|
6849
|
-
* })
|
|
6850
|
-
* ```
|
|
6851
|
-
*/
|
|
6852
7765
|
async exchangeAuthorizationCode(opts) {
|
|
6853
|
-
const body = {
|
|
7766
|
+
const body = oauthTokenFormBody({
|
|
6854
7767
|
grant_type: "authorization_code",
|
|
6855
7768
|
code: opts.code,
|
|
6856
7769
|
redirect_uri: opts.redirectUri,
|
|
6857
7770
|
client_id: opts.clientId,
|
|
6858
|
-
code_verifier: opts.codeVerifier
|
|
6859
|
-
|
|
6860
|
-
|
|
7771
|
+
code_verifier: opts.codeVerifier,
|
|
7772
|
+
...opts.clientSecret ? { client_secret: opts.clientSecret } : {}
|
|
7773
|
+
});
|
|
6861
7774
|
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/oauth/token`, {
|
|
6862
7775
|
method: "POST",
|
|
6863
7776
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
6864
|
-
body
|
|
7777
|
+
body
|
|
6865
7778
|
});
|
|
6866
7779
|
if (!res.ok) throw await oauthTokenError(res, "oauth.exchangeAuthorizationCode");
|
|
6867
|
-
return await res.json();
|
|
6868
|
-
},
|
|
6869
|
-
/**
|
|
6870
|
-
* Refresh a platform access token using a refresh_token
|
|
6871
|
-
* (ADR-089 Phase 5.1b — RFC 6749 §6). Rotation is mandatory — the
|
|
6872
|
-
* presented refresh token is consumed and a new one returned.
|
|
6873
|
-
*
|
|
6874
|
-
* @example
|
|
6875
|
-
* ```typescript
|
|
6876
|
-
* const tokens = await auth.oauth.refreshAccessToken({
|
|
6877
|
-
* baseUrl: 'https://api.sylphx.com/v1',
|
|
6878
|
-
* clientId: 'sylphx-console',
|
|
6879
|
-
* clientSecret: process.env.CONSOLE_CLIENT_SECRET,
|
|
6880
|
-
* refreshToken: stored.refresh_token,
|
|
6881
|
-
* })
|
|
6882
|
-
* ```
|
|
6883
|
-
*/
|
|
7780
|
+
return decodeOAuthTokenResult(await res.json());
|
|
7781
|
+
},
|
|
6884
7782
|
async refreshAccessToken(opts) {
|
|
6885
|
-
const body = {
|
|
7783
|
+
const body = oauthTokenFormBody({
|
|
6886
7784
|
grant_type: "refresh_token",
|
|
6887
7785
|
refresh_token: opts.refreshToken,
|
|
6888
|
-
client_id: opts.clientId
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
7786
|
+
client_id: opts.clientId,
|
|
7787
|
+
...opts.clientSecret ? { client_secret: opts.clientSecret } : {},
|
|
7788
|
+
...opts.scope ? { scope: opts.scope } : {}
|
|
7789
|
+
});
|
|
6892
7790
|
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/oauth/token`, {
|
|
6893
7791
|
method: "POST",
|
|
6894
7792
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
6895
|
-
body
|
|
7793
|
+
body
|
|
6896
7794
|
});
|
|
6897
7795
|
if (!res.ok) throw await oauthTokenError(res, "oauth.refreshAccessToken");
|
|
6898
|
-
return await res.json();
|
|
7796
|
+
return decodeOAuthTokenResult(await res.json());
|
|
6899
7797
|
},
|
|
6900
|
-
/**
|
|
6901
|
-
* Poll the OAuth token endpoint for a device-code grant (ADR-089 Phase
|
|
6902
|
-
* 5.1c — RFC 8628 §3.4). The preferred way to exchange an approved
|
|
6903
|
-
* device grant for tokens — returns an RFC 6749 error envelope on the
|
|
6904
|
-
* `{pending, slow_down, denied, expired}` states so callers can
|
|
6905
|
-
* distinguish precisely without parsing Phase 2a's `/auth/device/poll`
|
|
6906
|
-
* status string.
|
|
6907
|
-
*
|
|
6908
|
-
* Returns `{ ok: true, tokens }` on success or `{ ok: false, error }`
|
|
6909
|
-
* for every RFC-defined polling outcome. Callers MUST honour the
|
|
6910
|
-
* polling `interval` returned by `/auth/device` — polling faster yields
|
|
6911
|
-
* `{ ok: false, error: 'slow_down' }`.
|
|
6912
|
-
*
|
|
6913
|
-
* @example
|
|
6914
|
-
* ```typescript
|
|
6915
|
-
* while (true) {
|
|
6916
|
-
* await sleep(interval * 1000)
|
|
6917
|
-
* const r = await auth.oauth.pollDeviceToken({
|
|
6918
|
-
* baseUrl: 'https://api.sylphx.com/v1',
|
|
6919
|
-
* clientId: 'sylphx-cli',
|
|
6920
|
-
* deviceCode,
|
|
6921
|
-
* })
|
|
6922
|
-
* if (r.ok) return r.tokens
|
|
6923
|
-
* if (r.error === 'authorization_pending' || r.error === 'slow_down') continue
|
|
6924
|
-
* throw new Error(r.error) // access_denied | expired_token
|
|
6925
|
-
* }
|
|
6926
|
-
* ```
|
|
6927
|
-
*/
|
|
6928
7798
|
async pollDeviceToken(opts) {
|
|
6929
|
-
const body =
|
|
7799
|
+
const body = oauthTokenFormBody({
|
|
6930
7800
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
6931
7801
|
device_code: opts.deviceCode,
|
|
6932
7802
|
client_id: opts.clientId
|
|
6933
|
-
})
|
|
7803
|
+
});
|
|
6934
7804
|
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/oauth/token`, {
|
|
6935
7805
|
method: "POST",
|
|
6936
7806
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
6937
7807
|
body
|
|
6938
7808
|
});
|
|
6939
|
-
if (res.ok) {
|
|
6940
|
-
return { ok: true, tokens: await res.json() };
|
|
6941
|
-
}
|
|
7809
|
+
if (res.ok) return { ok: true, tokens: decodeOAuthTokenResult(await res.json()) };
|
|
6942
7810
|
const text = await res.text().catch(() => "");
|
|
6943
7811
|
let code = "oauth_error";
|
|
6944
7812
|
try {
|
|
6945
|
-
|
|
6946
|
-
if (parsed.error) code = parsed.error;
|
|
7813
|
+
code = decodeOAuthTokenError(JSON.parse(text)).error;
|
|
6947
7814
|
} catch {
|
|
6948
7815
|
}
|
|
6949
7816
|
return { ok: false, error: code, status: res.status };
|
|
6950
7817
|
},
|
|
6951
|
-
/**
|
|
6952
|
-
* Mint a service-principal access token via the `client_credentials`
|
|
6953
|
-
* grant (ADR-089 Phase 5.1c — RFC 6749 §4.4). Requires a confidential
|
|
6954
|
-
* client (public clients cannot use this grant). No refresh token is
|
|
6955
|
-
* issued per §4.4.3 — callers re-run this exchange on expiry.
|
|
6956
|
-
*
|
|
6957
|
-
* Typical use: CI integrations, server-to-server automation that has
|
|
6958
|
-
* no human owner and cannot run a device flow.
|
|
6959
|
-
*
|
|
6960
|
-
* @example
|
|
6961
|
-
* ```typescript
|
|
6962
|
-
* const { access_token } = await auth.oauth.clientCredentialsToken({
|
|
6963
|
-
* baseUrl: 'https://api.sylphx.com/v1',
|
|
6964
|
-
* clientId: process.env.SYLPHX_CLIENT_ID!,
|
|
6965
|
-
* clientSecret: process.env.SYLPHX_CLIENT_SECRET!,
|
|
6966
|
-
* scope: 'tenants:provision',
|
|
6967
|
-
* })
|
|
6968
|
-
* ```
|
|
6969
|
-
*/
|
|
6970
7818
|
async clientCredentialsToken(opts) {
|
|
6971
|
-
const body = {
|
|
7819
|
+
const body = oauthTokenFormBody({
|
|
6972
7820
|
grant_type: "client_credentials",
|
|
6973
7821
|
client_id: opts.clientId,
|
|
6974
|
-
client_secret: opts.clientSecret
|
|
6975
|
-
|
|
6976
|
-
|
|
7822
|
+
client_secret: opts.clientSecret,
|
|
7823
|
+
...opts.scope ? { scope: opts.scope } : {}
|
|
7824
|
+
});
|
|
6977
7825
|
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/oauth/token`, {
|
|
6978
7826
|
method: "POST",
|
|
6979
7827
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
6980
|
-
body
|
|
7828
|
+
body
|
|
6981
7829
|
});
|
|
6982
7830
|
if (!res.ok) throw await oauthTokenError(res, "oauth.clientCredentialsToken");
|
|
6983
|
-
return await res.json();
|
|
7831
|
+
return decodeOAuthClientCredentialsResult(await res.json());
|
|
6984
7832
|
},
|
|
6985
|
-
/**
|
|
6986
|
-
* Revoke an OAuth access or refresh token (RFC 7009 — ADR-089 Phase 5.1d).
|
|
6987
|
-
*
|
|
6988
|
-
* Per §2.2 this always resolves successfully — the server returns 200
|
|
6989
|
-
* whether the token existed, was already revoked, or belonged to a
|
|
6990
|
-
* different client. Only true protocol-level failures (malformed
|
|
6991
|
-
* request, bad client credentials) throw.
|
|
6992
|
-
*
|
|
6993
|
-
* @example
|
|
6994
|
-
* ```typescript
|
|
6995
|
-
* await auth.oauth.revokeToken({
|
|
6996
|
-
* baseUrl: 'https://your-app.api.sylphx.com/v1',
|
|
6997
|
-
* clientId: 'sylphx-cli',
|
|
6998
|
-
* token: refreshToken,
|
|
6999
|
-
* tokenTypeHint: 'refresh_token',
|
|
7000
|
-
* })
|
|
7001
|
-
* ```
|
|
7002
|
-
*/
|
|
7003
7833
|
async revokeToken(opts) {
|
|
7004
7834
|
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/oauth/revoke`, {
|
|
7005
7835
|
method: "POST",
|
|
7006
|
-
headers:
|
|
7836
|
+
headers: buildJsonHeaders(opts.userAgent),
|
|
7007
7837
|
body: JSON.stringify({
|
|
7008
7838
|
token: opts.token,
|
|
7009
7839
|
token_type_hint: opts.tokenTypeHint,
|
|
@@ -7016,29 +7846,10 @@ var oauth = {
|
|
|
7016
7846
|
throw new Error(`oauth.revokeToken failed (${res.status}): ${body}`);
|
|
7017
7847
|
}
|
|
7018
7848
|
},
|
|
7019
|
-
/**
|
|
7020
|
-
* Introspect an OAuth access or refresh token (RFC 7662 — ADR-089 Phase 5.1d).
|
|
7021
|
-
*
|
|
7022
|
-
* Returns `{ active: false }` for expired / revoked / unknown /
|
|
7023
|
-
* not-owned tokens (without revealing which); `{ active: true, ... }`
|
|
7024
|
-
* with full claims for live ones. Only protocol-level failures
|
|
7025
|
-
* (4xx on the revocation envelope itself) throw.
|
|
7026
|
-
*
|
|
7027
|
-
* @example
|
|
7028
|
-
* ```typescript
|
|
7029
|
-
* const result = await auth.oauth.introspectToken({
|
|
7030
|
-
* baseUrl: 'https://your-app.api.sylphx.com/v1',
|
|
7031
|
-
* clientId: 'gateway',
|
|
7032
|
-
* clientSecret: process.env.GATEWAY_SECRET,
|
|
7033
|
-
* token: accessToken,
|
|
7034
|
-
* })
|
|
7035
|
-
* if (!result.active) throw new Error('token not accepted')
|
|
7036
|
-
* ```
|
|
7037
|
-
*/
|
|
7038
7849
|
async introspectToken(opts) {
|
|
7039
7850
|
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/oauth/introspect`, {
|
|
7040
7851
|
method: "POST",
|
|
7041
|
-
headers:
|
|
7852
|
+
headers: buildJsonHeaders(opts.userAgent),
|
|
7042
7853
|
body: JSON.stringify({
|
|
7043
7854
|
token: opts.token,
|
|
7044
7855
|
token_type_hint: opts.tokenTypeHint,
|
|
@@ -7053,316 +7864,570 @@ var oauth = {
|
|
|
7053
7864
|
return await res.json();
|
|
7054
7865
|
}
|
|
7055
7866
|
};
|
|
7056
|
-
|
|
7867
|
+
function buildJsonHeaders(userAgent) {
|
|
7868
|
+
return {
|
|
7869
|
+
"Content-Type": "application/json",
|
|
7870
|
+
...userAgent ? { "User-Agent": userAgent } : {}
|
|
7871
|
+
};
|
|
7872
|
+
}
|
|
7873
|
+
async function platformJwtError(res, operation) {
|
|
7874
|
+
const body = await res.text().catch(() => "");
|
|
7875
|
+
let code = "platform_jwt_error";
|
|
7876
|
+
let message2 = `${operation} failed: HTTP ${res.status}`;
|
|
7877
|
+
try {
|
|
7878
|
+
const parsed = JSON.parse(body);
|
|
7879
|
+
if (parsed.error) code = parsed.error;
|
|
7880
|
+
if (parsed.error_description) message2 = parsed.error_description;
|
|
7881
|
+
else if (parsed.message) message2 = parsed.message;
|
|
7882
|
+
} catch {
|
|
7883
|
+
}
|
|
7884
|
+
let errorCode;
|
|
7885
|
+
if (res.status === 401) errorCode = "UNAUTHORIZED";
|
|
7886
|
+
else if (res.status === 429) errorCode = "TOO_MANY_REQUESTS";
|
|
7887
|
+
else if (res.status >= 500) errorCode = "INTERNAL_SERVER_ERROR";
|
|
7888
|
+
else errorCode = "BAD_REQUEST";
|
|
7889
|
+
return new SylphxError(message2, {
|
|
7890
|
+
code: errorCode,
|
|
7891
|
+
status: res.status,
|
|
7892
|
+
data: { operation, code }
|
|
7893
|
+
});
|
|
7894
|
+
}
|
|
7895
|
+
|
|
7896
|
+
// src/platform-password.ts
|
|
7897
|
+
init_errors();
|
|
7898
|
+
var password = {
|
|
7899
|
+
async status(opts) {
|
|
7900
|
+
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-password/status`, {
|
|
7901
|
+
method: "GET",
|
|
7902
|
+
headers: buildPlatformPasswordHeaders(opts.accessToken, opts.userAgent)
|
|
7903
|
+
});
|
|
7904
|
+
if (!res.ok) throw await platformPasswordError(res, "password.status");
|
|
7905
|
+
return await res.json();
|
|
7906
|
+
},
|
|
7907
|
+
async set(opts) {
|
|
7908
|
+
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-password/set`, {
|
|
7909
|
+
method: "POST",
|
|
7910
|
+
headers: buildPlatformPasswordHeaders(opts.accessToken, opts.userAgent),
|
|
7911
|
+
body: JSON.stringify({
|
|
7912
|
+
password: opts.password
|
|
7913
|
+
})
|
|
7914
|
+
});
|
|
7915
|
+
if (!res.ok) throw await platformPasswordError(res, "password.set");
|
|
7916
|
+
return await res.json();
|
|
7917
|
+
},
|
|
7918
|
+
async change(opts) {
|
|
7919
|
+
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-password/change`, {
|
|
7920
|
+
method: "POST",
|
|
7921
|
+
headers: buildPlatformPasswordHeaders(opts.accessToken, opts.userAgent),
|
|
7922
|
+
body: JSON.stringify({
|
|
7923
|
+
currentPassword: opts.currentPassword,
|
|
7924
|
+
newPassword: opts.newPassword
|
|
7925
|
+
})
|
|
7926
|
+
});
|
|
7927
|
+
if (!res.ok) throw await platformPasswordError(res, "password.change");
|
|
7928
|
+
return await res.json();
|
|
7929
|
+
}
|
|
7930
|
+
};
|
|
7931
|
+
function buildPlatformPasswordHeaders(accessToken, userAgent) {
|
|
7932
|
+
const headers = {
|
|
7933
|
+
"Content-Type": "application/json",
|
|
7934
|
+
Authorization: `Bearer ${accessToken}`
|
|
7935
|
+
};
|
|
7936
|
+
if (userAgent) headers["User-Agent"] = userAgent;
|
|
7937
|
+
return headers;
|
|
7938
|
+
}
|
|
7939
|
+
async function platformPasswordError(res, operation) {
|
|
7940
|
+
const body = await res.text().catch(() => "");
|
|
7941
|
+
let code = "platform_password_error";
|
|
7942
|
+
let message2 = `${operation} failed: HTTP ${res.status}`;
|
|
7943
|
+
try {
|
|
7944
|
+
const parsed = JSON.parse(body);
|
|
7945
|
+
if (parsed.error) code = parsed.error;
|
|
7946
|
+
if (parsed.error_description) message2 = parsed.error_description;
|
|
7947
|
+
else if (parsed.message) message2 = parsed.message;
|
|
7948
|
+
} catch {
|
|
7949
|
+
}
|
|
7950
|
+
let errorCode;
|
|
7951
|
+
if (res.status === 401) errorCode = "UNAUTHORIZED";
|
|
7952
|
+
else if (res.status === 404) errorCode = "NOT_FOUND";
|
|
7953
|
+
else if (res.status === 429) errorCode = "TOO_MANY_REQUESTS";
|
|
7954
|
+
else if (res.status >= 500) errorCode = "INTERNAL_SERVER_ERROR";
|
|
7955
|
+
else errorCode = "BAD_REQUEST";
|
|
7956
|
+
return new SylphxError(message2, {
|
|
7957
|
+
code: errorCode,
|
|
7958
|
+
status: res.status,
|
|
7959
|
+
data: { operation, code }
|
|
7960
|
+
});
|
|
7961
|
+
}
|
|
7962
|
+
|
|
7963
|
+
// src/platform-sessions.ts
|
|
7964
|
+
init_errors();
|
|
7965
|
+
var sessions = {
|
|
7966
|
+
async list(opts) {
|
|
7967
|
+
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-sessions`, {
|
|
7968
|
+
method: "GET",
|
|
7969
|
+
headers: buildPlatformSessionsHeaders(opts.accessToken, opts.userAgent)
|
|
7970
|
+
});
|
|
7971
|
+
if (!res.ok) throw await platformSessionError(res, "sessions.list");
|
|
7972
|
+
return await res.json();
|
|
7973
|
+
},
|
|
7974
|
+
async revoke(opts) {
|
|
7975
|
+
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-sessions/revoke`, {
|
|
7976
|
+
method: "POST",
|
|
7977
|
+
headers: buildPlatformSessionsHeaders(opts.accessToken, opts.userAgent),
|
|
7978
|
+
body: JSON.stringify({ sessionId: opts.sessionId })
|
|
7979
|
+
});
|
|
7980
|
+
if (!res.ok) throw await platformSessionError(res, "sessions.revoke");
|
|
7981
|
+
return await res.json();
|
|
7982
|
+
},
|
|
7983
|
+
async revokeOther(opts) {
|
|
7984
|
+
const res = await fetch(
|
|
7985
|
+
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-sessions/revoke-other`,
|
|
7986
|
+
{
|
|
7987
|
+
method: "POST",
|
|
7988
|
+
headers: buildPlatformSessionsHeaders(opts.accessToken, opts.userAgent)
|
|
7989
|
+
}
|
|
7990
|
+
);
|
|
7991
|
+
if (!res.ok) throw await platformSessionError(res, "sessions.revokeOther");
|
|
7992
|
+
return await res.json();
|
|
7993
|
+
},
|
|
7994
|
+
async revokeAll(opts) {
|
|
7995
|
+
const res = await fetch(
|
|
7996
|
+
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-sessions/revoke-all`,
|
|
7997
|
+
{
|
|
7998
|
+
method: "POST",
|
|
7999
|
+
headers: buildPlatformSessionsHeaders(opts.accessToken, opts.userAgent)
|
|
8000
|
+
}
|
|
8001
|
+
);
|
|
8002
|
+
if (!res.ok) throw await platformSessionError(res, "sessions.revokeAll");
|
|
8003
|
+
return await res.json();
|
|
8004
|
+
},
|
|
8005
|
+
async rename(opts) {
|
|
8006
|
+
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-sessions/rename`, {
|
|
8007
|
+
method: "POST",
|
|
8008
|
+
headers: buildPlatformSessionsHeaders(opts.accessToken, opts.userAgent),
|
|
8009
|
+
body: JSON.stringify({
|
|
8010
|
+
sessionId: opts.sessionId,
|
|
8011
|
+
name: opts.name
|
|
8012
|
+
})
|
|
8013
|
+
});
|
|
8014
|
+
if (!res.ok) throw await platformSessionError(res, "sessions.rename");
|
|
8015
|
+
return await res.json();
|
|
8016
|
+
}
|
|
8017
|
+
};
|
|
8018
|
+
function buildPlatformSessionsHeaders(accessToken, userAgent) {
|
|
8019
|
+
const headers = {
|
|
8020
|
+
"Content-Type": "application/json",
|
|
8021
|
+
Authorization: `Bearer ${accessToken}`
|
|
8022
|
+
};
|
|
8023
|
+
if (userAgent) headers["User-Agent"] = userAgent;
|
|
8024
|
+
return headers;
|
|
8025
|
+
}
|
|
8026
|
+
async function platformSessionError(res, operation) {
|
|
8027
|
+
const body = await res.text().catch(() => "");
|
|
8028
|
+
let code = "platform_sessions_error";
|
|
8029
|
+
let message2 = `${operation} failed: HTTP ${res.status}`;
|
|
8030
|
+
try {
|
|
8031
|
+
const parsed = JSON.parse(body);
|
|
8032
|
+
if (parsed.error) code = parsed.error;
|
|
8033
|
+
if (parsed.error_description) message2 = parsed.error_description;
|
|
8034
|
+
else if (parsed.message) message2 = parsed.message;
|
|
8035
|
+
} catch {
|
|
8036
|
+
}
|
|
8037
|
+
let errorCode;
|
|
8038
|
+
if (res.status === 401) errorCode = "UNAUTHORIZED";
|
|
8039
|
+
else if (res.status === 404) errorCode = "NOT_FOUND";
|
|
8040
|
+
else if (res.status === 429) errorCode = "TOO_MANY_REQUESTS";
|
|
8041
|
+
else if (res.status >= 500) errorCode = "INTERNAL_SERVER_ERROR";
|
|
8042
|
+
else errorCode = "BAD_REQUEST";
|
|
8043
|
+
return new SylphxError(message2, {
|
|
8044
|
+
code: errorCode,
|
|
8045
|
+
status: res.status,
|
|
8046
|
+
data: { operation, code }
|
|
8047
|
+
});
|
|
8048
|
+
}
|
|
8049
|
+
|
|
8050
|
+
// src/platform-user.ts
|
|
8051
|
+
init_errors();
|
|
8052
|
+
var user = {
|
|
7057
8053
|
/**
|
|
7058
|
-
*
|
|
7059
|
-
*
|
|
7060
|
-
*
|
|
7061
|
-
*
|
|
7062
|
-
*
|
|
7063
|
-
*
|
|
8054
|
+
* Export every piece of personal data the platform holds about the
|
|
8055
|
+
* authenticated user (GDPR Article 20 — right to data portability).
|
|
8056
|
+
*
|
|
8057
|
+
* The returned record is deliberately loose — it contains the user
|
|
8058
|
+
* row, sessions, OAuth accounts, login history, security alerts,
|
|
8059
|
+
* organization memberships, subscriptions, per-project memberships,
|
|
8060
|
+
* and storage file metadata. Shape varies with customer provisioning.
|
|
7064
8061
|
*/
|
|
7065
|
-
async
|
|
7066
|
-
const
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
);
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
8062
|
+
async exportData(opts) {
|
|
8063
|
+
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-user/export`, {
|
|
8064
|
+
method: "GET",
|
|
8065
|
+
headers: buildPlatformUserHeaders(opts.accessToken, opts.userAgent)
|
|
8066
|
+
});
|
|
8067
|
+
if (!res.ok) throw await platformUserError(res, "user.exportData");
|
|
8068
|
+
return await res.json();
|
|
8069
|
+
},
|
|
8070
|
+
/**
|
|
8071
|
+
* Permanently delete the authenticated user's account (GDPR Article
|
|
8072
|
+
* 17 — right to erasure). Cascades through every provisioned project
|
|
8073
|
+
* DB, cancels Stripe subscriptions, deletes S3 blobs, and anonymises
|
|
8074
|
+
* billing transactions.
|
|
8075
|
+
*/
|
|
8076
|
+
async deleteAccount(opts) {
|
|
8077
|
+
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-user/account`, {
|
|
8078
|
+
method: "DELETE",
|
|
8079
|
+
headers: buildPlatformUserHeaders(opts.accessToken, opts.userAgent),
|
|
8080
|
+
body: JSON.stringify({
|
|
8081
|
+
...opts.reason !== void 0 && { reason: opts.reason }
|
|
8082
|
+
})
|
|
8083
|
+
});
|
|
8084
|
+
if (!res.ok) throw await platformUserError(res, "user.deleteAccount");
|
|
8085
|
+
return await res.json();
|
|
7074
8086
|
},
|
|
7075
8087
|
/**
|
|
7076
|
-
*
|
|
7077
|
-
*
|
|
7078
|
-
*
|
|
7079
|
-
*
|
|
8088
|
+
* Async GDPR Article 20 export job API (ADR-089 Phase 5.5).
|
|
8089
|
+
*
|
|
8090
|
+
* `user.exportData` is the Phase 2d synchronous shortcut; production
|
|
8091
|
+
* callers should prefer the async flow for large accounts.
|
|
7080
8092
|
*/
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
8093
|
+
exports: {
|
|
8094
|
+
/**
|
|
8095
|
+
* Kick off an export job. Poll `status({ id })` until
|
|
8096
|
+
* `status === 'complete'`.
|
|
8097
|
+
*/
|
|
8098
|
+
async initiate(opts) {
|
|
8099
|
+
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-user/export`, {
|
|
8100
|
+
method: "POST",
|
|
8101
|
+
headers: buildPlatformUserHeaders(opts.accessToken, opts.userAgent),
|
|
8102
|
+
body: JSON.stringify(opts.format !== void 0 ? { format: opts.format } : {})
|
|
8103
|
+
});
|
|
8104
|
+
if (!res.ok) throw await platformUserError(res, "user.exports.initiate");
|
|
8105
|
+
return await res.json();
|
|
8106
|
+
},
|
|
8107
|
+
/**
|
|
8108
|
+
* Read the current state of an in-flight or completed export job.
|
|
8109
|
+
*/
|
|
8110
|
+
async status(opts) {
|
|
8111
|
+
const res = await fetch(
|
|
8112
|
+
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-user/export/${encodeURIComponent(
|
|
8113
|
+
opts.id
|
|
8114
|
+
)}`,
|
|
8115
|
+
{
|
|
8116
|
+
method: "GET",
|
|
8117
|
+
headers: buildPlatformUserHeaders(opts.accessToken, opts.userAgent)
|
|
8118
|
+
}
|
|
8119
|
+
);
|
|
8120
|
+
if (!res.ok) throw await platformUserError(res, "user.exports.status");
|
|
8121
|
+
return await res.json();
|
|
8122
|
+
},
|
|
8123
|
+
/**
|
|
8124
|
+
* Download the completed export payload. The BaaS route returns a
|
|
8125
|
+
* 302 to a freshly-signed object-storage URL; `fetch` follows it
|
|
8126
|
+
* and resolves to the raw `Blob`.
|
|
8127
|
+
*/
|
|
8128
|
+
async download(opts) {
|
|
8129
|
+
const res = await fetch(
|
|
8130
|
+
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-user/export/${encodeURIComponent(
|
|
8131
|
+
opts.id
|
|
8132
|
+
)}/download`,
|
|
8133
|
+
{
|
|
8134
|
+
method: "GET",
|
|
8135
|
+
headers: buildPlatformUserHeaders(opts.accessToken, opts.userAgent)
|
|
8136
|
+
}
|
|
8137
|
+
);
|
|
8138
|
+
if (!res.ok) throw await platformUserError(res, "user.exports.download");
|
|
8139
|
+
const sha256 = res.headers.get("X-Sylphx-Export-Sha256");
|
|
8140
|
+
const sizeHeader = res.headers.get("X-Sylphx-Export-Size");
|
|
8141
|
+
const sizeBytes = sizeHeader ? Number.parseInt(sizeHeader, 10) : null;
|
|
8142
|
+
const blob = await res.blob();
|
|
8143
|
+
return { blob, sha256, sizeBytes: Number.isFinite(sizeBytes) ? sizeBytes : null };
|
|
7093
8144
|
}
|
|
7094
|
-
if (opts.nonce) payload.nonce = opts.nonce;
|
|
7095
|
-
const headerB64 = base64UrlEncode(new TextEncoder().encode(JSON.stringify(header)));
|
|
7096
|
-
const payloadB64 = base64UrlEncode(new TextEncoder().encode(JSON.stringify(payload)));
|
|
7097
|
-
const signingInput = `${headerB64}.${payloadB64}`;
|
|
7098
|
-
const signingBytes = new TextEncoder().encode(signingInput);
|
|
7099
|
-
const signingBuf = new Uint8Array(signingBytes.byteLength);
|
|
7100
|
-
signingBuf.set(signingBytes);
|
|
7101
|
-
const sigBuf = await crypto.subtle.sign(
|
|
7102
|
-
{ name: "ECDSA", hash: "SHA-256" },
|
|
7103
|
-
opts.privateKey,
|
|
7104
|
-
signingBuf.buffer
|
|
7105
|
-
);
|
|
7106
|
-
const sigB64 = base64UrlEncode(new Uint8Array(sigBuf));
|
|
7107
|
-
return `${signingInput}.${sigB64}`;
|
|
7108
8145
|
}
|
|
7109
8146
|
};
|
|
7110
|
-
function
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
const canonical = JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y });
|
|
7118
|
-
return sha256Base64Url(new TextEncoder().encode(canonical));
|
|
7119
|
-
}
|
|
7120
|
-
async function sha256Base64Url(data) {
|
|
7121
|
-
const copy = new Uint8Array(data.byteLength);
|
|
7122
|
-
copy.set(data);
|
|
7123
|
-
const digest2 = await crypto.subtle.digest("SHA-256", copy.buffer);
|
|
7124
|
-
return base64UrlEncode(new Uint8Array(digest2));
|
|
7125
|
-
}
|
|
7126
|
-
function base64UrlEncode(bytes) {
|
|
7127
|
-
let s = "";
|
|
7128
|
-
for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
|
|
7129
|
-
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
7130
|
-
}
|
|
7131
|
-
function stripQueryAndFragment(uri) {
|
|
7132
|
-
try {
|
|
7133
|
-
const u = new URL(uri);
|
|
7134
|
-
return `${u.protocol}//${u.host}${u.pathname}`;
|
|
7135
|
-
} catch {
|
|
7136
|
-
const q = uri.indexOf("?");
|
|
7137
|
-
const h = uri.indexOf("#");
|
|
7138
|
-
const cut = [q, h].filter((i) => i >= 0).sort((a, b) => a - b)[0];
|
|
7139
|
-
return cut === void 0 ? uri : uri.slice(0, cut);
|
|
7140
|
-
}
|
|
7141
|
-
}
|
|
7142
|
-
function randomJti() {
|
|
7143
|
-
const bytes = new Uint8Array(16);
|
|
7144
|
-
crypto.getRandomValues(bytes);
|
|
7145
|
-
return base64UrlEncode(bytes);
|
|
8147
|
+
function buildPlatformUserHeaders(accessToken, userAgent) {
|
|
8148
|
+
const headers = {
|
|
8149
|
+
"Content-Type": "application/json",
|
|
8150
|
+
Authorization: `Bearer ${accessToken}`
|
|
8151
|
+
};
|
|
8152
|
+
if (userAgent) headers["User-Agent"] = userAgent;
|
|
8153
|
+
return headers;
|
|
7146
8154
|
}
|
|
7147
|
-
async function
|
|
7148
|
-
const { SylphxError: SylphxError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
8155
|
+
async function platformUserError(res, operation) {
|
|
7149
8156
|
const body = await res.text().catch(() => "");
|
|
7150
|
-
let code = "
|
|
8157
|
+
let code = "platform_user_error";
|
|
7151
8158
|
let message2 = `${operation} failed: HTTP ${res.status}`;
|
|
7152
8159
|
try {
|
|
7153
8160
|
const parsed = JSON.parse(body);
|
|
7154
8161
|
if (parsed.error) code = parsed.error;
|
|
7155
8162
|
if (parsed.error_description) message2 = parsed.error_description;
|
|
8163
|
+
else if (parsed.message) message2 = parsed.message;
|
|
7156
8164
|
} catch {
|
|
7157
8165
|
}
|
|
7158
8166
|
let errorCode;
|
|
7159
8167
|
if (res.status === 401) errorCode = "UNAUTHORIZED";
|
|
8168
|
+
else if (res.status === 404) errorCode = "NOT_FOUND";
|
|
8169
|
+
else if (res.status === 429) errorCode = "TOO_MANY_REQUESTS";
|
|
7160
8170
|
else if (res.status >= 500) errorCode = "INTERNAL_SERVER_ERROR";
|
|
7161
8171
|
else errorCode = "BAD_REQUEST";
|
|
7162
|
-
return new
|
|
8172
|
+
return new SylphxError(message2, {
|
|
7163
8173
|
code: errorCode,
|
|
7164
8174
|
status: res.status,
|
|
7165
|
-
data: {
|
|
8175
|
+
data: { operation, code }
|
|
8176
|
+
});
|
|
8177
|
+
}
|
|
8178
|
+
|
|
8179
|
+
// src/auth.ts
|
|
8180
|
+
async function signIn(config, input) {
|
|
8181
|
+
const body = input;
|
|
8182
|
+
const endpoint = authEndpoints.signIn;
|
|
8183
|
+
return callApi(config, endpoint.path, {
|
|
8184
|
+
method: endpoint.method,
|
|
8185
|
+
body
|
|
8186
|
+
});
|
|
8187
|
+
}
|
|
8188
|
+
async function signUp(config, input) {
|
|
8189
|
+
const endpoint = authEndpoints.signUp;
|
|
8190
|
+
return callApi(config, endpoint.path, {
|
|
8191
|
+
method: endpoint.method,
|
|
8192
|
+
body: input
|
|
8193
|
+
});
|
|
8194
|
+
}
|
|
8195
|
+
async function signOut(config) {
|
|
8196
|
+
const endpoint = authEndpoints.signOut;
|
|
8197
|
+
await callApi(config, endpoint.path, { method: endpoint.method });
|
|
8198
|
+
}
|
|
8199
|
+
async function refreshToken(config, token) {
|
|
8200
|
+
return callApi(config, "/auth/token", {
|
|
8201
|
+
method: "POST",
|
|
8202
|
+
body: {
|
|
8203
|
+
grant_type: "refresh_token",
|
|
8204
|
+
refresh_token: token,
|
|
8205
|
+
client_secret: config.secretKey
|
|
8206
|
+
}
|
|
8207
|
+
});
|
|
8208
|
+
}
|
|
8209
|
+
async function verifyEmail(config, token) {
|
|
8210
|
+
await callApi(config, "/auth/verify-email", {
|
|
8211
|
+
method: "POST",
|
|
8212
|
+
body: { token }
|
|
8213
|
+
});
|
|
8214
|
+
}
|
|
8215
|
+
async function forgotPassword(config, email, options = {}) {
|
|
8216
|
+
await callApi(config, "/auth/forgot-password", {
|
|
8217
|
+
method: "POST",
|
|
8218
|
+
body: {
|
|
8219
|
+
email,
|
|
8220
|
+
...options.redirectUrl ? { redirectUrl: options.redirectUrl } : {}
|
|
8221
|
+
}
|
|
8222
|
+
});
|
|
8223
|
+
}
|
|
8224
|
+
async function resendVerificationEmail(config, email) {
|
|
8225
|
+
const endpoint = authEndpoints.resendEmailVerification;
|
|
8226
|
+
const body = { email };
|
|
8227
|
+
await callApi(config, endpoint.path, {
|
|
8228
|
+
method: endpoint.method,
|
|
8229
|
+
body
|
|
8230
|
+
});
|
|
8231
|
+
}
|
|
8232
|
+
async function resetPassword(config, input) {
|
|
8233
|
+
await callApi(config, "/auth/reset-password", {
|
|
8234
|
+
method: "POST",
|
|
8235
|
+
body: { token: input.token, password: input.password }
|
|
8236
|
+
});
|
|
8237
|
+
}
|
|
8238
|
+
async function getSession(config) {
|
|
8239
|
+
if (!config.accessToken) {
|
|
8240
|
+
return { user: null };
|
|
8241
|
+
}
|
|
8242
|
+
const endpoint = authEndpoints.getSession;
|
|
8243
|
+
try {
|
|
8244
|
+
const user2 = await callApi(config, endpoint.path, {
|
|
8245
|
+
method: endpoint.method
|
|
8246
|
+
});
|
|
8247
|
+
return { user: user2 };
|
|
8248
|
+
} catch {
|
|
8249
|
+
return { user: null };
|
|
8250
|
+
}
|
|
8251
|
+
}
|
|
8252
|
+
async function verifyTwoFactor(config, userId, code) {
|
|
8253
|
+
return callApi(config, "/auth/verify-2fa", {
|
|
8254
|
+
method: "POST",
|
|
8255
|
+
body: { userId, code }
|
|
8256
|
+
});
|
|
8257
|
+
}
|
|
8258
|
+
async function introspectToken(config, token, tokenTypeHint) {
|
|
8259
|
+
const response = await fetch(buildApiUrl(config, "/auth/introspect"), {
|
|
8260
|
+
method: "POST",
|
|
8261
|
+
headers: {
|
|
8262
|
+
"Content-Type": "application/json",
|
|
8263
|
+
// RFC 7662 §2: server-to-server call — authenticate with secret key
|
|
8264
|
+
"x-app-secret": config.secretKey ?? ""
|
|
8265
|
+
},
|
|
8266
|
+
body: JSON.stringify({
|
|
8267
|
+
token,
|
|
8268
|
+
token_type_hint: tokenTypeHint
|
|
8269
|
+
})
|
|
8270
|
+
});
|
|
8271
|
+
if (!response.ok) {
|
|
8272
|
+
return { active: false };
|
|
8273
|
+
}
|
|
8274
|
+
return response.json();
|
|
8275
|
+
}
|
|
8276
|
+
async function revokeToken(config, token, options) {
|
|
8277
|
+
await fetch(buildApiUrl(config, "/auth/revoke"), {
|
|
8278
|
+
method: "POST",
|
|
8279
|
+
headers: { "Content-Type": "application/json" },
|
|
8280
|
+
body: JSON.stringify({
|
|
8281
|
+
token: options?.revokeAll ? void 0 : token,
|
|
8282
|
+
client_secret: config.secretKey,
|
|
8283
|
+
user_id: options?.userId,
|
|
8284
|
+
revoke_all: options?.revokeAll
|
|
8285
|
+
})
|
|
8286
|
+
});
|
|
8287
|
+
}
|
|
8288
|
+
async function revokeAllTokens(config, userId) {
|
|
8289
|
+
await revokeToken(config, "", { revokeAll: true, userId });
|
|
8290
|
+
}
|
|
8291
|
+
async function extendedSignUp(config, input) {
|
|
8292
|
+
return callApi(config, "/auth/register", {
|
|
8293
|
+
method: "POST",
|
|
8294
|
+
body: input
|
|
7166
8295
|
});
|
|
7167
8296
|
}
|
|
7168
|
-
async function
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
} catch {
|
|
8297
|
+
async function inviteUser(config, input) {
|
|
8298
|
+
return callApi(config, "/auth/invite", {
|
|
8299
|
+
method: "POST",
|
|
8300
|
+
body: input
|
|
8301
|
+
});
|
|
8302
|
+
}
|
|
8303
|
+
function normalizeOrgScopedTokenResponse(data) {
|
|
8304
|
+
const accessToken = data.accessToken ?? data.access_token;
|
|
8305
|
+
if (!accessToken) {
|
|
8306
|
+
throw new Error("Invalid org-scoped token response: missing access token");
|
|
7179
8307
|
}
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
8308
|
+
return {
|
|
8309
|
+
token: accessToken,
|
|
8310
|
+
accessToken,
|
|
8311
|
+
expiresIn: data.expiresIn ?? data.expires_in,
|
|
8312
|
+
tokenType: data.tokenType ?? data.token_type,
|
|
8313
|
+
user: data.user
|
|
8314
|
+
};
|
|
8315
|
+
}
|
|
8316
|
+
async function getOrgScopedToken(config, orgId) {
|
|
8317
|
+
const data = await callApi(config, "/auth/switch-org", {
|
|
8318
|
+
method: "POST",
|
|
8319
|
+
body: { orgId }
|
|
7189
8320
|
});
|
|
8321
|
+
return normalizeOrgScopedTokenResponse(data);
|
|
7190
8322
|
}
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
{
|
|
7196
|
-
method: "POST",
|
|
7197
|
-
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent),
|
|
7198
|
-
body: JSON.stringify({
|
|
7199
|
-
targetUserId: opts.targetUserId,
|
|
7200
|
-
...opts.ipAddress !== void 0 && { ipAddress: opts.ipAddress },
|
|
7201
|
-
...opts.userAgent !== void 0 && { userAgent: opts.userAgent }
|
|
7202
|
-
})
|
|
7203
|
-
}
|
|
7204
|
-
);
|
|
7205
|
-
if (!res.ok) throw await impersonationError(res, "impersonation.start");
|
|
7206
|
-
return await res.json();
|
|
7207
|
-
},
|
|
7208
|
-
async end(opts) {
|
|
7209
|
-
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-impersonation/end`, {
|
|
7210
|
-
method: "POST",
|
|
7211
|
-
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent),
|
|
7212
|
-
body: JSON.stringify(opts.sessionId !== void 0 ? { sessionId: opts.sessionId } : {})
|
|
7213
|
-
});
|
|
7214
|
-
if (!res.ok) throw await impersonationError(res, "impersonation.end");
|
|
7215
|
-
return await res.json();
|
|
7216
|
-
},
|
|
7217
|
-
async info(opts) {
|
|
7218
|
-
const res = await fetch(
|
|
7219
|
-
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-impersonation/info/${encodeURIComponent(opts.sessionId)}`,
|
|
7220
|
-
{
|
|
7221
|
-
method: "GET",
|
|
7222
|
-
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent)
|
|
7223
|
-
}
|
|
7224
|
-
);
|
|
7225
|
-
if (!res.ok) throw await impersonationError(res, "impersonation.info");
|
|
7226
|
-
return await res.json();
|
|
7227
|
-
},
|
|
7228
|
-
async active(opts) {
|
|
7229
|
-
const res = await fetch(
|
|
7230
|
-
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-impersonation/active`,
|
|
7231
|
-
{
|
|
7232
|
-
method: "GET",
|
|
7233
|
-
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent)
|
|
7234
|
-
}
|
|
7235
|
-
);
|
|
7236
|
-
if (!res.ok) throw await impersonationError(res, "impersonation.active");
|
|
7237
|
-
return await res.json();
|
|
7238
|
-
},
|
|
7239
|
-
// ── Phase 5.9 ──────────────────────────────────────────────────────
|
|
8323
|
+
async function switchOrg(config, orgId) {
|
|
8324
|
+
return getOrgScopedToken(config, orgId);
|
|
8325
|
+
}
|
|
8326
|
+
var device = {
|
|
7240
8327
|
/**
|
|
7241
|
-
*
|
|
7242
|
-
*
|
|
7243
|
-
*
|
|
7244
|
-
*
|
|
8328
|
+
* Start a device authorization grant.
|
|
8329
|
+
*
|
|
8330
|
+
* Returns a `DeviceGrant` with `verification_uri_complete` (open this
|
|
8331
|
+
* in the user's browser) and `device_code` (use for polling).
|
|
8332
|
+
*
|
|
8333
|
+
* @example
|
|
8334
|
+
* ```typescript
|
|
8335
|
+
* const grant = await device.init({
|
|
8336
|
+
* baseUrl: 'https://your-app.api.sylphx.com/v1',
|
|
8337
|
+
* clientId: 'sylphx-cli',
|
|
8338
|
+
* scope: ['org:read', 'project:*'],
|
|
8339
|
+
* })
|
|
8340
|
+
* openBrowser(grant.verification_uri_complete)
|
|
8341
|
+
* ```
|
|
7245
8342
|
*/
|
|
7246
|
-
async
|
|
7247
|
-
const res = await fetch(
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
}
|
|
7257
|
-
);
|
|
7258
|
-
if (!res.ok) throw await impersonationError(res, "impersonation.startChallenge");
|
|
8343
|
+
async init(opts) {
|
|
8344
|
+
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/device`, {
|
|
8345
|
+
method: "POST",
|
|
8346
|
+
headers: buildDeviceHeaders(opts.userAgent),
|
|
8347
|
+
body: JSON.stringify({
|
|
8348
|
+
client_id: opts.clientId,
|
|
8349
|
+
scope: opts.scope ?? []
|
|
8350
|
+
})
|
|
8351
|
+
});
|
|
8352
|
+
if (!res.ok) throw await deviceError(res, "device.init");
|
|
7259
8353
|
return await res.json();
|
|
7260
8354
|
},
|
|
7261
8355
|
/**
|
|
7262
|
-
*
|
|
7263
|
-
*
|
|
7264
|
-
*
|
|
7265
|
-
*
|
|
7266
|
-
* `
|
|
8356
|
+
* Poll a pending grant. Returns `status: 'pending' | 'approved' |
|
|
8357
|
+
* 'denied' | 'expired'`. On `approved`, the result carries the OAuth
|
|
8358
|
+
* pair (access_token + refresh_token).
|
|
8359
|
+
*
|
|
8360
|
+
* Callers MUST respect the `interval` returned by `init()` — polling
|
|
8361
|
+
* faster than that may return 429 slow_down (RFC 8628 §5.5).
|
|
7267
8362
|
*/
|
|
7268
|
-
async
|
|
7269
|
-
const
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
challengeKey: opts.challengeKey,
|
|
7277
|
-
assertion: opts.assertion,
|
|
7278
|
-
...opts.emergencyBypass ? { emergencyBypass: true } : {}
|
|
7279
|
-
})
|
|
7280
|
-
}
|
|
7281
|
-
);
|
|
7282
|
-
if (!res.ok) throw await impersonationError(res, "impersonation.startStepup");
|
|
8363
|
+
async poll(opts) {
|
|
8364
|
+
const url = new URL(`${opts.baseUrl.replace(/\/$/, "")}/auth/device/poll`);
|
|
8365
|
+
url.searchParams.set("device_code", opts.deviceCode);
|
|
8366
|
+
const res = await fetch(url.toString(), {
|
|
8367
|
+
method: "GET",
|
|
8368
|
+
headers: buildDeviceHeaders(opts.userAgent)
|
|
8369
|
+
});
|
|
8370
|
+
if (!res.ok) throw await deviceError(res, "device.poll");
|
|
7283
8371
|
return await res.json();
|
|
7284
8372
|
},
|
|
7285
8373
|
/**
|
|
7286
|
-
*
|
|
7287
|
-
*
|
|
8374
|
+
* Browser leg — the approving user confirms the grant.
|
|
8375
|
+
*
|
|
8376
|
+
* Requires a valid platform-issued access token (`Authorization:
|
|
8377
|
+
* Bearer <accessToken>`) proving the user is logged in on the
|
|
8378
|
+
* Console. Typically called by the Console's `/device` verification
|
|
8379
|
+
* page server-side, forwarding the user's session JWT.
|
|
7288
8380
|
*/
|
|
7289
|
-
async
|
|
7290
|
-
const res = await fetch(
|
|
7291
|
-
|
|
7292
|
-
{
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
}
|
|
7297
|
-
);
|
|
7298
|
-
if (!res.ok) throw await
|
|
7299
|
-
return await res.json();
|
|
7300
|
-
},
|
|
7301
|
-
/** List impersonation requests. Non-super_admin sees only their own. */
|
|
7302
|
-
async listRequests(opts) {
|
|
7303
|
-
const params = new URLSearchParams();
|
|
7304
|
-
if (opts.filter?.operatorId) params.set("operatorId", opts.filter.operatorId);
|
|
7305
|
-
if (opts.filter?.targetUserId) params.set("targetUserId", opts.filter.targetUserId);
|
|
7306
|
-
if (opts.filter?.status) params.set("status", opts.filter.status);
|
|
7307
|
-
if (opts.filter?.limit != null) params.set("limit", String(opts.filter.limit));
|
|
7308
|
-
const qs = params.toString();
|
|
7309
|
-
const res = await fetch(
|
|
7310
|
-
`${opts.baseUrl.replace(/\/$/, "")}/auth/platform-impersonation/requests${qs ? `?${qs}` : ""}`,
|
|
7311
|
-
{
|
|
7312
|
-
method: "GET",
|
|
7313
|
-
headers: buildImpersonationHeaders(opts.accessToken, opts.userAgent)
|
|
7314
|
-
}
|
|
7315
|
-
);
|
|
7316
|
-
if (!res.ok) throw await impersonationError(res, "impersonation.listRequests");
|
|
8381
|
+
async approve(opts) {
|
|
8382
|
+
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/device/approve`, {
|
|
8383
|
+
method: "POST",
|
|
8384
|
+
headers: {
|
|
8385
|
+
...buildDeviceHeaders(opts.userAgent),
|
|
8386
|
+
Authorization: `Bearer ${opts.accessToken}`
|
|
8387
|
+
},
|
|
8388
|
+
body: JSON.stringify({ user_code: opts.userCode })
|
|
8389
|
+
});
|
|
8390
|
+
if (!res.ok) throw await deviceError(res, "device.approve");
|
|
7317
8391
|
return await res.json();
|
|
7318
8392
|
},
|
|
7319
8393
|
/**
|
|
7320
|
-
*
|
|
7321
|
-
*
|
|
7322
|
-
*
|
|
8394
|
+
* Browser leg — the user declines the grant.
|
|
8395
|
+
*
|
|
8396
|
+
* Requires a valid platform-issued access token just like `approve`.
|
|
7323
8397
|
*/
|
|
7324
|
-
async
|
|
7325
|
-
const res = await fetch(
|
|
7326
|
-
|
|
7327
|
-
{
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
}
|
|
7331
|
-
|
|
7332
|
-
|
|
8398
|
+
async deny(opts) {
|
|
8399
|
+
const res = await fetch(`${opts.baseUrl.replace(/\/$/, "")}/auth/device/deny`, {
|
|
8400
|
+
method: "POST",
|
|
8401
|
+
headers: {
|
|
8402
|
+
...buildDeviceHeaders(opts.userAgent),
|
|
8403
|
+
Authorization: `Bearer ${opts.accessToken}`
|
|
8404
|
+
},
|
|
8405
|
+
body: JSON.stringify({ user_code: opts.userCode })
|
|
8406
|
+
});
|
|
8407
|
+
if (!res.ok) throw await deviceError(res, "device.deny");
|
|
7333
8408
|
return await res.json();
|
|
7334
8409
|
}
|
|
7335
8410
|
};
|
|
7336
|
-
function
|
|
8411
|
+
function buildDeviceHeaders(userAgent) {
|
|
7337
8412
|
const headers = {
|
|
7338
|
-
"Content-Type": "application/json"
|
|
7339
|
-
Authorization: `Bearer ${accessToken}`
|
|
8413
|
+
"Content-Type": "application/json"
|
|
7340
8414
|
};
|
|
7341
8415
|
if (userAgent) headers["User-Agent"] = userAgent;
|
|
7342
8416
|
return headers;
|
|
7343
8417
|
}
|
|
7344
|
-
async function
|
|
8418
|
+
async function deviceError(res, operation) {
|
|
7345
8419
|
const { SylphxError: SylphxError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
7346
8420
|
const body = await res.text().catch(() => "");
|
|
7347
|
-
let code = "
|
|
8421
|
+
let code = "device_flow_error";
|
|
7348
8422
|
let message2 = `${operation} failed: HTTP ${res.status}`;
|
|
7349
8423
|
try {
|
|
7350
8424
|
const parsed = JSON.parse(body);
|
|
7351
8425
|
if (parsed.error) code = parsed.error;
|
|
7352
8426
|
if (parsed.error_description) message2 = parsed.error_description;
|
|
7353
|
-
else if (parsed.message) message2 = parsed.message;
|
|
7354
8427
|
} catch {
|
|
7355
8428
|
}
|
|
7356
|
-
let errorCode;
|
|
7357
|
-
if (res.status === 401) errorCode = "UNAUTHORIZED";
|
|
7358
|
-
else if (res.status === 403) errorCode = "UNAUTHORIZED";
|
|
7359
|
-
else if (res.status === 404) errorCode = "NOT_FOUND";
|
|
7360
|
-
else if (res.status === 409) errorCode = "CONFLICT";
|
|
7361
|
-
else if (res.status === 429) errorCode = "TOO_MANY_REQUESTS";
|
|
7362
|
-
else if (res.status >= 500) errorCode = "INTERNAL_SERVER_ERROR";
|
|
7363
|
-
else errorCode = "BAD_REQUEST";
|
|
7364
8429
|
return new SylphxError2(message2, {
|
|
7365
|
-
code:
|
|
8430
|
+
code: res.status === 429 ? "TOO_MANY_REQUESTS" : "BAD_REQUEST",
|
|
7366
8431
|
status: res.status,
|
|
7367
8432
|
data: { operation, code }
|
|
7368
8433
|
});
|
|
@@ -7646,8 +8711,8 @@ var realtimeAdmin = {
|
|
|
7646
8711
|
/**
|
|
7647
8712
|
* List registered channels for a project.
|
|
7648
8713
|
*
|
|
7649
|
-
* Returns an empty list when the project's hidden
|
|
7650
|
-
* been provisioned yet — BaaS side soft-fails because absent
|
|
8714
|
+
* Returns an empty list when the project's hidden realtime store has
|
|
8715
|
+
* not been provisioned yet — BaaS side soft-fails because absent
|
|
7651
8716
|
* registrations are semantically equivalent to "none registered".
|
|
7652
8717
|
*/
|
|
7653
8718
|
async list(opts) {
|
|
@@ -9417,8 +10482,9 @@ async function getOrganization(config, orgIdOrSlug) {
|
|
|
9417
10482
|
return callApi(config, `/orgs/${orgIdOrSlug}`);
|
|
9418
10483
|
}
|
|
9419
10484
|
async function createOrganization(config, input) {
|
|
9420
|
-
|
|
9421
|
-
|
|
10485
|
+
const endpoint = organizationsEndpoints.create;
|
|
10486
|
+
return callApi(config, endpoint.path, {
|
|
10487
|
+
method: endpoint.method,
|
|
9422
10488
|
body: input
|
|
9423
10489
|
});
|
|
9424
10490
|
}
|
|
@@ -10272,7 +11338,7 @@ var SandboxClient = class _SandboxClient {
|
|
|
10272
11338
|
body: {
|
|
10273
11339
|
image: options?.image,
|
|
10274
11340
|
idleTimeoutMs: options?.idleTimeoutMs ?? 3e5,
|
|
10275
|
-
|
|
11341
|
+
machine: options?.machine ?? "standard",
|
|
10276
11342
|
env: options?.env,
|
|
10277
11343
|
storage: options?.storageGi !== void 0 ? { enabled: true, sizeGi: options.storageGi } : void 0,
|
|
10278
11344
|
volumeMounts: options?.volumeMounts
|
|
@@ -10515,7 +11581,7 @@ var RunHandle = class {
|
|
|
10515
11581
|
*
|
|
10516
11582
|
* @example
|
|
10517
11583
|
* ```typescript
|
|
10518
|
-
* const result = await
|
|
11584
|
+
* const result = await run.wait()
|
|
10519
11585
|
* if (result.exitCode !== 0) {
|
|
10520
11586
|
* throw new Error(`Worker failed: ${result.errorMessage ?? result.stderr}`)
|
|
10521
11587
|
* }
|
|
@@ -10590,13 +11656,13 @@ var RunsClient = {
|
|
|
10590
11656
|
*
|
|
10591
11657
|
* @example
|
|
10592
11658
|
* ```typescript
|
|
10593
|
-
* const run = await RunsClient.
|
|
10594
|
-
* image: '
|
|
11659
|
+
* const run = await RunsClient.run(config, {
|
|
11660
|
+
* image: 'ghcr.io/acme/trainer:sha-abc123',
|
|
10595
11661
|
* command: ['python', 'train.py', '--fold', '3'],
|
|
10596
|
-
*
|
|
11662
|
+
* machine: 'large',
|
|
10597
11663
|
* volumeMounts: [{ volumeId: cacheVolumeId, mountPath: '/cache' }],
|
|
10598
11664
|
* })
|
|
10599
|
-
* const result = await
|
|
11665
|
+
* const result = await run.wait()
|
|
10600
11666
|
* ```
|
|
10601
11667
|
*/
|
|
10602
11668
|
async run(config, options) {
|
|
@@ -10606,7 +11672,7 @@ var RunsClient = {
|
|
|
10606
11672
|
image: options.image,
|
|
10607
11673
|
command: options.command,
|
|
10608
11674
|
env: options.env,
|
|
10609
|
-
|
|
11675
|
+
machine: options.machine ?? "standard",
|
|
10610
11676
|
timeoutSeconds: options.timeoutSeconds ?? 3600,
|
|
10611
11677
|
volumeMounts: options.volumeMounts
|
|
10612
11678
|
}
|
|
@@ -10639,8 +11705,8 @@ var RunsClient = {
|
|
|
10639
11705
|
*
|
|
10640
11706
|
* @example
|
|
10641
11707
|
* ```typescript
|
|
10642
|
-
* const {
|
|
10643
|
-
* console.log(`${
|
|
11708
|
+
* const { data } = await RunsClient.list(config, { status: 'running' })
|
|
11709
|
+
* console.log(`${data.length} runs currently running`)
|
|
10644
11710
|
* ```
|
|
10645
11711
|
*/
|
|
10646
11712
|
async list(config, options) {
|
|
@@ -10655,12 +11721,12 @@ var RunsClient = {
|
|
|
10655
11721
|
/**
|
|
10656
11722
|
* Spawn a worker and wait for it to complete in one call.
|
|
10657
11723
|
*
|
|
10658
|
-
* Equivalent to `(await RunsClient.
|
|
11724
|
+
* Equivalent to `(await RunsClient.run(config, options)).wait(waitOptions)`.
|
|
10659
11725
|
*
|
|
10660
11726
|
* @example
|
|
10661
11727
|
* ```typescript
|
|
10662
11728
|
* const result = await RunsClient.runAndWait(config, {
|
|
10663
|
-
* image: '
|
|
11729
|
+
* image: 'ghcr.io/acme/process:sha-abc123',
|
|
10664
11730
|
* command: ['node', 'dist/process.js'],
|
|
10665
11731
|
* })
|
|
10666
11732
|
* if (result.exitCode !== 0) throw new Error(result.errorMessage ?? 'worker failed')
|
|
@@ -10988,15 +12054,61 @@ export {
|
|
|
10988
12054
|
ACHIEVEMENT_TIER_CONFIG,
|
|
10989
12055
|
AuthenticationError,
|
|
10990
12056
|
AuthorizationError,
|
|
12057
|
+
BILLING_ALLOWED_ROLES,
|
|
12058
|
+
BUILD_MINUTES_INCLUDED,
|
|
12059
|
+
BUILD_MINUTE_PRICES,
|
|
12060
|
+
BUILD_SIZE_MULTIPLIERS,
|
|
12061
|
+
BYTES_PER_GB,
|
|
12062
|
+
CI_BUILD_MINUTE_PRICE_MICRODOLLARS,
|
|
12063
|
+
CI_FREE_MINUTES_PER_MONTH,
|
|
12064
|
+
CI_MACOS_MULTIPLIER,
|
|
12065
|
+
CI_MACOS_SIZE_MULTIPLIERS,
|
|
12066
|
+
CI_SIZE_MULTIPLIERS,
|
|
12067
|
+
COMPUTE_PRICE_PER_HOUR_MICRODOLLARS,
|
|
12068
|
+
COMPUTE_RAM_RATE_MICRODOLLARS,
|
|
12069
|
+
COMPUTE_VCPU_ACTIVE_RATE_MICRODOLLARS,
|
|
12070
|
+
COMPUTE_VCPU_IDLE_RATE_MICRODOLLARS,
|
|
12071
|
+
CONSOLE_APP_SLUG,
|
|
12072
|
+
CREDENTIAL_REGEX,
|
|
12073
|
+
CREDIT_EXPIRY_MONTHS,
|
|
10991
12074
|
CircuitBreakerOpenError,
|
|
12075
|
+
DEFAULT_MACHINE_SIZE,
|
|
12076
|
+
DEFAULT_MAX_REPLICAS,
|
|
12077
|
+
DEFAULT_POINTS_REWARD,
|
|
12078
|
+
DISCOUNT_DURATION_MONTHS,
|
|
12079
|
+
DISCOUNT_PERCENT,
|
|
10992
12080
|
ERROR_CODE_STATUS,
|
|
12081
|
+
FREE_COMPUTE_HOURS,
|
|
12082
|
+
FREE_STORAGE_GB,
|
|
12083
|
+
HOURS_PER_MONTH,
|
|
12084
|
+
INSTANCE_TYPES,
|
|
12085
|
+
INSTANCE_TYPE_ALIASES,
|
|
12086
|
+
INSTANCE_TYPE_ORDER,
|
|
12087
|
+
INVOICE_DUE_DAYS,
|
|
10993
12088
|
InvalidConnectionUrlError,
|
|
12089
|
+
KV_FREE_STORAGE_GB,
|
|
12090
|
+
LEGACY_INSTANCE_TYPE_ORDER,
|
|
12091
|
+
MACHINE_CONFIGS,
|
|
12092
|
+
MACHINE_MAX_INSTANCES,
|
|
12093
|
+
MACHINE_RESOURCE_REQUIREMENTS,
|
|
12094
|
+
MACHINE_SIZES,
|
|
12095
|
+
MAX_PASSWORD_LENGTH,
|
|
12096
|
+
MAX_PAYMENT_ATTEMPTS,
|
|
12097
|
+
MICRODOLLARS_PER_CENT,
|
|
12098
|
+
MIN_PASSWORD_LENGTH,
|
|
10994
12099
|
NetworkError,
|
|
10995
12100
|
NotFoundError,
|
|
12101
|
+
PASSWORD_REQUIREMENTS,
|
|
12102
|
+
PLATFORM_PLANS,
|
|
12103
|
+
PLATFORM_PLAN_ORDER,
|
|
12104
|
+
PLATFORM_PLAN_ORDER_ALL,
|
|
12105
|
+
PREMIUM_TRIAL_DAYS,
|
|
10996
12106
|
RETRYABLE_CODES,
|
|
10997
12107
|
RateLimitError,
|
|
10998
12108
|
RunHandle,
|
|
10999
12109
|
RunsClient,
|
|
12110
|
+
SERVICE_METRICS,
|
|
12111
|
+
STORAGE_PRICE_PER_GB_MONTH_MICRODOLLARS,
|
|
11000
12112
|
SandboxClient,
|
|
11001
12113
|
SandboxFiles,
|
|
11002
12114
|
SandboxProcesses,
|
|
@@ -11004,6 +12116,7 @@ export {
|
|
|
11004
12116
|
StepCompleteSignal,
|
|
11005
12117
|
StepSleepSignal,
|
|
11006
12118
|
SylphxError,
|
|
12119
|
+
TRANSFER_PRICE_PER_GB_MICRODOLLARS,
|
|
11007
12120
|
TimeoutError,
|
|
11008
12121
|
TriggersClient,
|
|
11009
12122
|
ValidationError,
|
|
@@ -11015,6 +12128,8 @@ export {
|
|
|
11015
12128
|
audit,
|
|
11016
12129
|
authorizeOAuth,
|
|
11017
12130
|
batchIndex,
|
|
12131
|
+
buildConnectionUrl,
|
|
12132
|
+
calculatePercentage,
|
|
11018
12133
|
canDeleteOrganization,
|
|
11019
12134
|
canManageMembers,
|
|
11020
12135
|
canManageSettings,
|
|
@@ -11023,6 +12138,7 @@ export {
|
|
|
11023
12138
|
captureException,
|
|
11024
12139
|
captureExceptionRaw,
|
|
11025
12140
|
captureMessage,
|
|
12141
|
+
centsToDollars,
|
|
11026
12142
|
chat,
|
|
11027
12143
|
chatStream,
|
|
11028
12144
|
checkFlag,
|
|
@@ -11067,22 +12183,45 @@ export {
|
|
|
11067
12183
|
dpop,
|
|
11068
12184
|
embed,
|
|
11069
12185
|
enableDebug,
|
|
12186
|
+
escapeCsvField,
|
|
12187
|
+
escapeHtml,
|
|
11070
12188
|
exchangeOAuthCode,
|
|
11071
12189
|
exponentialBackoff,
|
|
11072
12190
|
exportUserData,
|
|
11073
12191
|
extendedSignUp,
|
|
12192
|
+
getErrorDetails as extractErrorDetails,
|
|
12193
|
+
getErrorMessage as extractErrorMessage,
|
|
11074
12194
|
forgotPassword,
|
|
12195
|
+
formatBytes,
|
|
12196
|
+
formatCents,
|
|
12197
|
+
formatCurrency,
|
|
12198
|
+
formatDate,
|
|
12199
|
+
formatDateTime,
|
|
12200
|
+
formatDuration,
|
|
12201
|
+
formatMicrodollars,
|
|
12202
|
+
formatMonthYear,
|
|
12203
|
+
formatNumber,
|
|
12204
|
+
formatPercent,
|
|
12205
|
+
formatRelativeTime,
|
|
12206
|
+
formatRelativeTimeShort,
|
|
12207
|
+
formatTime,
|
|
11075
12208
|
functions,
|
|
11076
12209
|
generateAnonymousId,
|
|
11077
12210
|
generatePkce,
|
|
12211
|
+
generateReferralCode,
|
|
12212
|
+
generateSlug,
|
|
11078
12213
|
getAchievement,
|
|
11079
12214
|
getAchievementPoints,
|
|
11080
12215
|
getAchievements,
|
|
12216
|
+
getActivePlans,
|
|
11081
12217
|
getAllFlags,
|
|
11082
12218
|
getAllSecrets,
|
|
11083
12219
|
getAllStreaks,
|
|
12220
|
+
getAvailableInstanceTypes,
|
|
11084
12221
|
getBackupCodes,
|
|
12222
|
+
getBaseUrl,
|
|
11085
12223
|
getBillingBalance,
|
|
12224
|
+
getBillingStatusVariant,
|
|
11086
12225
|
getBillingUsage,
|
|
11087
12226
|
getBuildLogHistory,
|
|
11088
12227
|
getCircuitBreakerState,
|
|
@@ -11091,13 +12230,17 @@ export {
|
|
|
11091
12230
|
getDatabaseConnectionString,
|
|
11092
12231
|
getDatabaseStatus,
|
|
11093
12232
|
getDebugMode,
|
|
12233
|
+
getDefaultInstanceType,
|
|
11094
12234
|
getDeployHistory,
|
|
11095
12235
|
getDeployStatus,
|
|
11096
|
-
|
|
11097
|
-
|
|
12236
|
+
getEnvPrefix,
|
|
12237
|
+
getErrorCode2 as getErrorCode,
|
|
12238
|
+
getErrorDetails2 as getErrorDetails,
|
|
12239
|
+
getErrorMessage2 as getErrorMessage,
|
|
11098
12240
|
getFacets,
|
|
11099
12241
|
getFlagPayload,
|
|
11100
12242
|
getFlags,
|
|
12243
|
+
getInvoiceStatusVariant,
|
|
11101
12244
|
getLeaderboard,
|
|
11102
12245
|
getMemberPermissions,
|
|
11103
12246
|
getMyReferralCode,
|
|
@@ -11107,6 +12250,7 @@ export {
|
|
|
11107
12250
|
getOrganizationInvitations,
|
|
11108
12251
|
getOrganizationMembers,
|
|
11109
12252
|
getOrganizations,
|
|
12253
|
+
getPlanMonthlyPrice,
|
|
11110
12254
|
getPlans,
|
|
11111
12255
|
getProjectMetadata,
|
|
11112
12256
|
getPromo,
|
|
@@ -11116,6 +12260,7 @@ export {
|
|
|
11116
12260
|
getReferralStats,
|
|
11117
12261
|
getRestErrorMessage,
|
|
11118
12262
|
getRole,
|
|
12263
|
+
getSafeErrorMessage,
|
|
11119
12264
|
getScheduledEmail,
|
|
11120
12265
|
getScheduledEmailStats,
|
|
11121
12266
|
getSearchStats,
|
|
@@ -11139,6 +12284,7 @@ export {
|
|
|
11139
12284
|
getWebhookStats,
|
|
11140
12285
|
hasAllPermissions,
|
|
11141
12286
|
hasAnyPermission,
|
|
12287
|
+
hasBillingAccess,
|
|
11142
12288
|
hasConsent,
|
|
11143
12289
|
hasError,
|
|
11144
12290
|
hasPermission,
|
|
@@ -11154,10 +12300,14 @@ export {
|
|
|
11154
12300
|
introspectToken,
|
|
11155
12301
|
inviteOrganizationMember,
|
|
11156
12302
|
inviteUser,
|
|
12303
|
+
isChallengeRequired,
|
|
11157
12304
|
isEmailConfigured,
|
|
11158
12305
|
isEnabled,
|
|
12306
|
+
isMachineSize,
|
|
12307
|
+
isPlanDeprecated,
|
|
11159
12308
|
isRetryableError,
|
|
11160
12309
|
isSylphxError,
|
|
12310
|
+
isValidInstanceType,
|
|
11161
12311
|
kvDelete,
|
|
11162
12312
|
kvExists,
|
|
11163
12313
|
kvExpire,
|
|
@@ -11195,9 +12345,13 @@ export {
|
|
|
11195
12345
|
listUsers,
|
|
11196
12346
|
markAllSecurityAlertsRead,
|
|
11197
12347
|
markSecurityAlertRead,
|
|
12348
|
+
microsToDollars,
|
|
11198
12349
|
oauth,
|
|
11199
12350
|
page,
|
|
12351
|
+
parseConnectionUrl,
|
|
12352
|
+
parseMachineSize,
|
|
11200
12353
|
parseOAuthCallback,
|
|
12354
|
+
parseUserAgent,
|
|
11201
12355
|
password,
|
|
11202
12356
|
pauseCron,
|
|
11203
12357
|
platformAuth,
|
|
@@ -11228,12 +12382,20 @@ export {
|
|
|
11228
12382
|
resetPassword,
|
|
11229
12383
|
resetPlatformCookieCache,
|
|
11230
12384
|
resetPlatformJwksCache,
|
|
12385
|
+
resolveCanonicalInstanceType,
|
|
12386
|
+
resolveMachineConfig,
|
|
12387
|
+
resolveMachineMaxInstances,
|
|
12388
|
+
resolveMachineResources,
|
|
12389
|
+
resolveMachineTierResources,
|
|
12390
|
+
resolveMaxReplicas,
|
|
12391
|
+
resolveResources,
|
|
11231
12392
|
resumeCron,
|
|
11232
12393
|
revokeAllTokens,
|
|
11233
12394
|
revokeOrganizationInvitation,
|
|
11234
12395
|
revokeToken,
|
|
11235
12396
|
revokeUserSession,
|
|
11236
12397
|
rollbackDeploy,
|
|
12398
|
+
safeJsonParse,
|
|
11237
12399
|
scheduleEmail,
|
|
11238
12400
|
scheduleTask,
|
|
11239
12401
|
search,
|
|
@@ -11255,6 +12417,7 @@ export {
|
|
|
11255
12417
|
submitScore,
|
|
11256
12418
|
suspendUser,
|
|
11257
12419
|
switchOrg,
|
|
12420
|
+
toPublicMachineSize,
|
|
11258
12421
|
toSylphxError,
|
|
11259
12422
|
track,
|
|
11260
12423
|
trackBatch,
|
|
@@ -11274,6 +12437,7 @@ export {
|
|
|
11274
12437
|
upsertDocument,
|
|
11275
12438
|
user,
|
|
11276
12439
|
userInfo,
|
|
12440
|
+
validateInstanceTypeForPlan,
|
|
11277
12441
|
validatePromo,
|
|
11278
12442
|
verifyAccessToken,
|
|
11279
12443
|
verifyChallenge,
|