@homecloud-platform/sdk 0.4.9 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -65
- package/package.json +30 -30
- package/src/client.js +458 -0
- package/src/credentials.js +101 -0
- package/src/defaults.js +47 -0
- package/src/errors.js +166 -0
- package/src/index.d.ts +67 -35
- package/src/index.js +54 -274
- package/src/mail.js +89 -0
- package/src/management.js +119 -0
- package/src/mq.js +29 -0
- package/src/secrets.js +27 -0
- package/src/signing.js +54 -0
- package/src/so.js +225 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { DEFAULT_APEX, DEFAULT_PROFILE, envFirst } = require("./defaults");
|
|
7
|
+
|
|
8
|
+
function homecloudDir() {
|
|
9
|
+
const override = envFirst("HOMECLOUD_CONFIG_DIR", "HC_CONFIG_DIR");
|
|
10
|
+
if (override) return path.resolve(override.replace(/^~(?=\/|\\)/, os.homedir()));
|
|
11
|
+
return path.join(os.homedir(), ".homecloud");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function credentialsPath() {
|
|
15
|
+
const override = envFirst("HOMECLOUD_CREDENTIALS_FILE", "HC_CREDENTIALS_FILE");
|
|
16
|
+
if (override) return path.resolve(override.replace(/^~(?=\/|\\)/, os.homedir()));
|
|
17
|
+
return path.join(homecloudDir(), "credentials");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function sessionPath() {
|
|
21
|
+
const override = envFirst("HOMECLOUD_SESSION_FILE", "HC_SESSION_FILE");
|
|
22
|
+
if (override) return path.resolve(override.replace(/^~(?=\/|\\)/, os.homedir()));
|
|
23
|
+
return path.join(homecloudDir(), "session");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function loadCredentials(filePath = credentialsPath()) {
|
|
27
|
+
if (!fs.existsSync(filePath)) {
|
|
28
|
+
return { version: 2, default_profile: DEFAULT_PROFILE, profiles: {} };
|
|
29
|
+
}
|
|
30
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
31
|
+
if (!raw || typeof raw !== "object") throw new Error("Invalid credentials file");
|
|
32
|
+
if (raw.profiles && typeof raw.profiles === "object") {
|
|
33
|
+
return {
|
|
34
|
+
version: Number(raw.version || 2),
|
|
35
|
+
default_profile: raw.default_profile || DEFAULT_PROFILE,
|
|
36
|
+
profiles: raw.profiles,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// legacy flat
|
|
40
|
+
return {
|
|
41
|
+
version: 2,
|
|
42
|
+
default_profile: DEFAULT_PROFILE,
|
|
43
|
+
profiles: { [DEFAULT_PROFILE]: raw },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function loadSession(filePath = sessionPath()) {
|
|
48
|
+
if (!fs.existsSync(filePath)) return { version: 1, profiles: {} };
|
|
49
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
50
|
+
return {
|
|
51
|
+
version: Number(raw.version || 1),
|
|
52
|
+
profiles: raw.profiles || {},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function saveSession(session, filePath = sessionPath()) {
|
|
57
|
+
const dir = path.dirname(filePath);
|
|
58
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
59
|
+
fs.writeFileSync(filePath, JSON.stringify(session, null, 2) + "\n", "utf8");
|
|
60
|
+
try {
|
|
61
|
+
fs.chmodSync(filePath, 0o600);
|
|
62
|
+
} catch (_) {}
|
|
63
|
+
return filePath;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveProfile(name) {
|
|
67
|
+
const creds = loadCredentials();
|
|
68
|
+
const profileName =
|
|
69
|
+
name ||
|
|
70
|
+
envFirst("HOMECLOUD_PROFILE", "HC_PROFILE") ||
|
|
71
|
+
creds.default_profile ||
|
|
72
|
+
DEFAULT_PROFILE;
|
|
73
|
+
const profile = creds.profiles[profileName] || {};
|
|
74
|
+
const session = loadSession().profiles[profileName] || {};
|
|
75
|
+
return {
|
|
76
|
+
profileName,
|
|
77
|
+
apex: envFirst("HOMECLOUD_APEX", "HC_APEX") || profile.apex || DEFAULT_APEX,
|
|
78
|
+
accountId:
|
|
79
|
+
envFirst("HOMECLOUD_ACCOUNT_ID", "HC_ACCOUNT_ID") ||
|
|
80
|
+
profile.default_account_id ||
|
|
81
|
+
session.active_account_id ||
|
|
82
|
+
null,
|
|
83
|
+
accessKeyId:
|
|
84
|
+
envFirst("HOMECLOUD_ACCESS_KEY_ID", "HC_ACCESS_KEY_ID") || profile.access_key_id || null,
|
|
85
|
+
secretAccessKey:
|
|
86
|
+
envFirst("HOMECLOUD_SECRET_ACCESS_KEY", "HC_SECRET_ACCESS_KEY") ||
|
|
87
|
+
profile.secret_access_key ||
|
|
88
|
+
null,
|
|
89
|
+
accessToken: session.access_token || null,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
homecloudDir,
|
|
95
|
+
credentialsPath,
|
|
96
|
+
sessionPath,
|
|
97
|
+
loadCredentials,
|
|
98
|
+
loadSession,
|
|
99
|
+
saveSession,
|
|
100
|
+
resolveProfile,
|
|
101
|
+
};
|
package/src/defaults.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_APEX = "holab.abrdns.com";
|
|
4
|
+
const DEFAULT_PROFILE = "default";
|
|
5
|
+
const WHOAMI_PATH = "/access-key/whoami";
|
|
6
|
+
const WHOAMI_ACCOUNT_SENTINEL = "-";
|
|
7
|
+
|
|
8
|
+
function soUrl(apex) {
|
|
9
|
+
return `https://so.${apex || DEFAULT_APEX}`;
|
|
10
|
+
}
|
|
11
|
+
function mqUrl(apex) {
|
|
12
|
+
return `https://mq.${apex || DEFAULT_APEX}`;
|
|
13
|
+
}
|
|
14
|
+
function secretsUrl(apex) {
|
|
15
|
+
return `https://secrets.${apex || DEFAULT_APEX}`;
|
|
16
|
+
}
|
|
17
|
+
function mailApiUrl(apex) {
|
|
18
|
+
return `https://mailapi.${apex || DEFAULT_APEX}`;
|
|
19
|
+
}
|
|
20
|
+
function consoleUrl(apex) {
|
|
21
|
+
return `https://console.${apex || DEFAULT_APEX}/api/v1`;
|
|
22
|
+
}
|
|
23
|
+
function functionUrl(name, apex) {
|
|
24
|
+
return `https://${String(name).trim().toLowerCase()}.func.${apex || DEFAULT_APEX}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function envFirst(...names) {
|
|
28
|
+
for (const name of names) {
|
|
29
|
+
const v = process.env[name];
|
|
30
|
+
if (v != null && String(v).trim() !== "") return v;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
DEFAULT_APEX,
|
|
37
|
+
DEFAULT_PROFILE,
|
|
38
|
+
WHOAMI_PATH,
|
|
39
|
+
WHOAMI_ACCOUNT_SENTINEL,
|
|
40
|
+
soUrl,
|
|
41
|
+
mqUrl,
|
|
42
|
+
secretsUrl,
|
|
43
|
+
mailApiUrl,
|
|
44
|
+
consoleUrl,
|
|
45
|
+
functionUrl,
|
|
46
|
+
envFirst,
|
|
47
|
+
};
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
class HomeCloudError extends Error {
|
|
4
|
+
constructor(message, { statusCode = null, detail = null } = {}) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "HomeCloudError";
|
|
7
|
+
this.statusCode = statusCode;
|
|
8
|
+
this.detail = detail;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get errorPayload() {
|
|
12
|
+
if (!this.detail || typeof this.detail !== "object") return null;
|
|
13
|
+
const error = this.detail.error;
|
|
14
|
+
if (error && typeof error === "object" && error.code) {
|
|
15
|
+
return {
|
|
16
|
+
code: String(error.code),
|
|
17
|
+
message: String(error.message || error.code),
|
|
18
|
+
details: error.details && typeof error.details === "object" ? error.details : {},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (this.detail.code) {
|
|
22
|
+
const details = { ...this.detail };
|
|
23
|
+
delete details.code;
|
|
24
|
+
delete details.message;
|
|
25
|
+
return {
|
|
26
|
+
code: String(this.detail.code),
|
|
27
|
+
message: String(this.detail.message || this.detail.code),
|
|
28
|
+
details,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get errorCode() {
|
|
35
|
+
const p = this.errorPayload;
|
|
36
|
+
return p ? p.code : null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class NotConfiguredError extends HomeCloudError {
|
|
41
|
+
constructor(message, opts) {
|
|
42
|
+
super(message, opts);
|
|
43
|
+
this.name = "NotConfiguredError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class NotLoggedInError extends HomeCloudError {
|
|
48
|
+
constructor(message, opts) {
|
|
49
|
+
super(message || "Not logged in. Run: homecloud login", opts);
|
|
50
|
+
this.name = "NotLoggedInError";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class ApiError extends HomeCloudError {
|
|
55
|
+
constructor(message, opts) {
|
|
56
|
+
super(message, opts);
|
|
57
|
+
this.name = "ApiError";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class BadRequestError extends ApiError {
|
|
62
|
+
constructor(message, opts) {
|
|
63
|
+
super(message, opts);
|
|
64
|
+
this.name = "BadRequestError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class UnauthorizedError extends ApiError {
|
|
69
|
+
constructor(message, opts) {
|
|
70
|
+
super(message, opts);
|
|
71
|
+
this.name = "UnauthorizedError";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class PermissionDeniedError extends ApiError {
|
|
76
|
+
constructor(message, opts) {
|
|
77
|
+
super(message, opts);
|
|
78
|
+
this.name = "PermissionDeniedError";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class NotFoundError extends ApiError {
|
|
83
|
+
constructor(message, opts = {}) {
|
|
84
|
+
super(message, { statusCode: 404, ...opts });
|
|
85
|
+
this.name = "NotFoundError";
|
|
86
|
+
this.resourceType = opts.resourceType || null;
|
|
87
|
+
this.resource = opts.resource || null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class ConflictError extends ApiError {
|
|
92
|
+
constructor(message, opts) {
|
|
93
|
+
super(message, opts);
|
|
94
|
+
this.name = "ConflictError";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
class RateLimitError extends ApiError {
|
|
99
|
+
constructor(message, opts) {
|
|
100
|
+
super(message, opts);
|
|
101
|
+
this.name = "RateLimitError";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
class ServiceUnavailableError extends ApiError {
|
|
106
|
+
constructor(message, opts) {
|
|
107
|
+
super(message, opts);
|
|
108
|
+
this.name = "ServiceUnavailableError";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function detailMessage(detail) {
|
|
113
|
+
if (detail == null) return null;
|
|
114
|
+
if (typeof detail === "string" && detail.trim()) return detail.trim();
|
|
115
|
+
if (typeof detail === "object" && !Array.isArray(detail)) {
|
|
116
|
+
if (detail.error && typeof detail.error === "object") {
|
|
117
|
+
return String(detail.error.message || detail.error.code || "");
|
|
118
|
+
}
|
|
119
|
+
if (detail.message) return String(detail.message);
|
|
120
|
+
if (detail.code) return String(detail.code);
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function errorFromStatus(statusCode, { detail = null, url = null } = {}) {
|
|
126
|
+
const apiMsg = detailMessage(detail);
|
|
127
|
+
if (statusCode === 400) return new BadRequestError(apiMsg || "Bad request", { statusCode, detail });
|
|
128
|
+
if (statusCode === 401) {
|
|
129
|
+
return new UnauthorizedError(apiMsg || "Unauthorized — check Access Key or console session", {
|
|
130
|
+
statusCode,
|
|
131
|
+
detail,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (statusCode === 403) {
|
|
135
|
+
return new PermissionDeniedError(apiMsg || "Permission denied", { statusCode, detail });
|
|
136
|
+
}
|
|
137
|
+
if (statusCode === 404) {
|
|
138
|
+
return new NotFoundError(apiMsg || "Resource not found", { statusCode, detail });
|
|
139
|
+
}
|
|
140
|
+
if (statusCode === 409) return new ConflictError(apiMsg || "Conflict", { statusCode, detail });
|
|
141
|
+
if (statusCode === 429) {
|
|
142
|
+
return new RateLimitError(apiMsg || "Rate limit exceeded", { statusCode, detail });
|
|
143
|
+
}
|
|
144
|
+
if ([502, 503, 504].includes(statusCode)) {
|
|
145
|
+
return new ServiceUnavailableError(apiMsg || `Service unavailable (${statusCode})`, {
|
|
146
|
+
statusCode,
|
|
147
|
+
detail,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return new ApiError(apiMsg || `Request failed (${statusCode})`, { statusCode, detail });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
HomeCloudError,
|
|
155
|
+
NotConfiguredError,
|
|
156
|
+
NotLoggedInError,
|
|
157
|
+
ApiError,
|
|
158
|
+
BadRequestError,
|
|
159
|
+
UnauthorizedError,
|
|
160
|
+
PermissionDeniedError,
|
|
161
|
+
NotFoundError,
|
|
162
|
+
ConflictError,
|
|
163
|
+
RateLimitError,
|
|
164
|
+
ServiceUnavailableError,
|
|
165
|
+
errorFromStatus,
|
|
166
|
+
};
|
package/src/index.d.ts
CHANGED
|
@@ -1,35 +1,67 @@
|
|
|
1
|
-
export type StsEntry = {
|
|
2
|
-
access_key_id: string;
|
|
3
|
-
secret_access_key: string;
|
|
4
|
-
session_token?: string;
|
|
5
|
-
base_url?: string;
|
|
6
|
-
mail_base_url?: string;
|
|
7
|
-
resource_type?: string;
|
|
8
|
-
resource_name?: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export declare class HomeCloudError extends Error {
|
|
12
|
-
statusCode?: number;
|
|
13
|
-
detail?: unknown;
|
|
14
|
-
constructor(message: string, opts?: { statusCode?: number; detail?: unknown });
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export declare class
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
1
|
+
export type StsEntry = {
|
|
2
|
+
access_key_id: string;
|
|
3
|
+
secret_access_key: string;
|
|
4
|
+
session_token?: string;
|
|
5
|
+
base_url?: string;
|
|
6
|
+
mail_base_url?: string;
|
|
7
|
+
resource_type?: string;
|
|
8
|
+
resource_name?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export declare class HomeCloudError extends Error {
|
|
12
|
+
statusCode?: number | null;
|
|
13
|
+
detail?: unknown;
|
|
14
|
+
constructor(message: string, opts?: { statusCode?: number | null; detail?: unknown });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export declare class NotConfiguredError extends HomeCloudError {}
|
|
18
|
+
export declare class NotLoggedInError extends HomeCloudError {}
|
|
19
|
+
export declare class ApiError extends HomeCloudError {}
|
|
20
|
+
export declare class BadRequestError extends ApiError {}
|
|
21
|
+
export declare class UnauthorizedError extends ApiError {}
|
|
22
|
+
export declare class PermissionDeniedError extends ApiError {}
|
|
23
|
+
export declare class NotFoundError extends ApiError {
|
|
24
|
+
resourceType?: string | null;
|
|
25
|
+
resource?: string | null;
|
|
26
|
+
}
|
|
27
|
+
export declare class ConflictError extends ApiError {}
|
|
28
|
+
export declare class RateLimitError extends ApiError {}
|
|
29
|
+
export declare class ServiceUnavailableError extends ApiError {}
|
|
30
|
+
|
|
31
|
+
export declare class HomeCloud {
|
|
32
|
+
so: any;
|
|
33
|
+
storage: any;
|
|
34
|
+
mq: any;
|
|
35
|
+
secrets: any;
|
|
36
|
+
mail: any;
|
|
37
|
+
accounts: any;
|
|
38
|
+
apps: any;
|
|
39
|
+
queues: any;
|
|
40
|
+
functions: any;
|
|
41
|
+
accountId: string | null;
|
|
42
|
+
accessToken: string | null;
|
|
43
|
+
constructor(opts?: Record<string, unknown>);
|
|
44
|
+
static fromEnv(opts?: Record<string, unknown>): HomeCloud;
|
|
45
|
+
static fromProfile(profile?: string | null, opts?: Record<string, unknown>): HomeCloud;
|
|
46
|
+
static fromCredentials(
|
|
47
|
+
accessKeyId: string,
|
|
48
|
+
secretAccessKey: string,
|
|
49
|
+
opts?: Record<string, unknown>
|
|
50
|
+
): HomeCloud;
|
|
51
|
+
static fromSts(sts: StsEntry, opts?: { accountId?: string; apex?: string }): HomeCloud;
|
|
52
|
+
static fromFunctionContext(
|
|
53
|
+
context: Record<string, unknown>,
|
|
54
|
+
opts: { binding: string }
|
|
55
|
+
): HomeCloud;
|
|
56
|
+
ensureAccountId(): Promise<string>;
|
|
57
|
+
login(username: string, password: string, opts?: { mfaCode?: string }): Promise<void>;
|
|
58
|
+
loginBrowser(opts?: {
|
|
59
|
+
openBrowser?: boolean;
|
|
60
|
+
onWaiting?: (uri: string) => void;
|
|
61
|
+
mfaToken?: string;
|
|
62
|
+
}): Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export declare const AsyncHomeCloud: typeof HomeCloud;
|
|
66
|
+
export declare const DEFAULT_APEX: string;
|
|
67
|
+
export declare function signRequestHeaders(opts: Record<string, unknown>): Record<string, string>;
|