@attlaz/client 1.79.0 → 1.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Client.js +4 -10
- package/dist/Http/OAuthClientOptions.d.ts +2 -2
- package/dist/Http/OAuthClientOptions.js +4 -1
- package/dist/Http/Transport/OAuthClient.d.ts +17 -1
- package/dist/Http/Transport/OAuthClient.js +59 -21
- package/dist/Model/ApiRecord.d.ts +5 -1
- package/dist/Model/Collections/CollectionRecord.d.ts +2 -1
- package/dist/Model/Configuration/Configuration.d.ts +3 -2
- package/dist/Model/Deployment/CodeSourceBuildStrategy.d.ts +4 -1
- package/dist/Model/Deployment/CodeSourceBuildStrategy.js +6 -0
- package/dist/Model/Deployment/CodeSourceRunStrategy.d.ts +3 -2
- package/dist/Model/Deployment/SourcesAccountRepository.d.ts +2 -1
- package/dist/Model/Deployment/SourcesAccountRepositoryBranch.d.ts +2 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/Client.js
CHANGED
|
@@ -99,22 +99,16 @@ export class Client {
|
|
|
99
99
|
return client;
|
|
100
100
|
}
|
|
101
101
|
setClientCredentials(clientId, clientSecret = '') {
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
// TODO: do we need to create a new oauth client, or can we update with these credentials?
|
|
106
|
-
this.httpClient = new OAuthClient(options);
|
|
107
|
-
this.transport = this.httpClient;
|
|
102
|
+
// Store the credentials in place; the token (if any) is preserved and used while valid. When
|
|
103
|
+
// no valid token exists, the client mints one from these credentials on the next request.
|
|
104
|
+
this.httpClient.setClientCredentials(clientId, clientSecret, ['all']);
|
|
108
105
|
}
|
|
109
106
|
/**
|
|
110
107
|
* Configure this client as a public OAuth client (no client_secret).
|
|
111
108
|
* Used for SPAs and other public clients that cannot keep a secret.
|
|
112
109
|
*/
|
|
113
110
|
setPublicClient(clientId) {
|
|
114
|
-
|
|
115
|
-
options.scopes = ['all'];
|
|
116
|
-
this.httpClient = new OAuthClient(options);
|
|
117
|
-
this.transport = this.httpClient;
|
|
111
|
+
this.httpClient.setClientCredentials(clientId, null, ['all']);
|
|
118
112
|
}
|
|
119
113
|
setVersion(version) {
|
|
120
114
|
this.httpClient.setVersion(version);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare class OAuthClientOptions {
|
|
2
2
|
apiEndpoint: string;
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
clientId: string | null;
|
|
4
|
+
clientSecret: string | null;
|
|
5
5
|
accessTokenUri: string;
|
|
6
6
|
authorizationUri: string;
|
|
7
7
|
redirectUri: string;
|
|
@@ -7,7 +7,10 @@ export class OAuthClientOptions {
|
|
|
7
7
|
redirectUri = 'https://example.com/auth/github/callback';
|
|
8
8
|
scopes = [];
|
|
9
9
|
state = '';
|
|
10
|
-
constructor(apiEndpoint,
|
|
10
|
+
constructor(apiEndpoint,
|
|
11
|
+
// Mutable so client credentials can be updated in place (see OAuthClient.setClientCredentials)
|
|
12
|
+
// without recreating the client and losing its token.
|
|
13
|
+
clientId = null, clientSecret = null) {
|
|
11
14
|
this.apiEndpoint = apiEndpoint;
|
|
12
15
|
this.clientId = clientId;
|
|
13
16
|
this.clientSecret = clientSecret;
|
|
@@ -7,13 +7,29 @@ export declare class OAuthClient implements ITransport {
|
|
|
7
7
|
private readonly options;
|
|
8
8
|
private debug;
|
|
9
9
|
private oauthClientToken;
|
|
10
|
-
private
|
|
10
|
+
private tokenAcquisitionPromise;
|
|
11
11
|
private version;
|
|
12
12
|
private parseErrorHandler;
|
|
13
13
|
constructor(options: OAuthClientOptions);
|
|
14
14
|
authenticate(username: string, password: string): Promise<boolean>;
|
|
15
15
|
authenticate(): Promise<boolean>;
|
|
16
16
|
refreshToken(): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Update the client credentials in place — without recreating the client or touching the current
|
|
19
|
+
* token. Mirrors the PHP client: token and credentials are independent. A valid token keeps being
|
|
20
|
+
* used; the credentials are only the fallback to mint/renew a token when none is valid.
|
|
21
|
+
*/
|
|
22
|
+
setClientCredentials(clientId: string | null, clientSecret: string | null, scopes?: string[]): void;
|
|
23
|
+
/**
|
|
24
|
+
* Ensure a usable access token exists before a signed request. Token-first with credentials as a
|
|
25
|
+
* fallback (mirrors the PHP client's lazy authenticate()): a valid token is left untouched; an
|
|
26
|
+
* expired token carrying a refresh_token is refreshed; otherwise, if confidential client
|
|
27
|
+
* credentials are configured, a token is minted via the client_credentials grant. Single-flighted
|
|
28
|
+
* so concurrent requests share one acquisition.
|
|
29
|
+
*/
|
|
30
|
+
private ensureAccessToken;
|
|
31
|
+
private acquireToken;
|
|
32
|
+
private static hasRefreshToken;
|
|
17
33
|
private isPublicClient;
|
|
18
34
|
/**
|
|
19
35
|
* Performs an OAuth token request (password, client_credentials or refresh_token grant) and
|
|
@@ -9,7 +9,9 @@ export class OAuthClient {
|
|
|
9
9
|
options;
|
|
10
10
|
debug = false;
|
|
11
11
|
oauthClientToken = null;
|
|
12
|
-
|
|
12
|
+
// Single-flight guard for token acquisition (initial client-credentials auth or refresh), so
|
|
13
|
+
// concurrent requests share one in-flight acquisition instead of each hitting the token endpoint.
|
|
14
|
+
tokenAcquisitionPromise = null;
|
|
13
15
|
version = null;
|
|
14
16
|
parseErrorHandler = null;
|
|
15
17
|
constructor(options) {
|
|
@@ -93,6 +95,58 @@ export class OAuthClient {
|
|
|
93
95
|
throw ClientError.fromError(e);
|
|
94
96
|
}
|
|
95
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Update the client credentials in place — without recreating the client or touching the current
|
|
100
|
+
* token. Mirrors the PHP client: token and credentials are independent. A valid token keeps being
|
|
101
|
+
* used; the credentials are only the fallback to mint/renew a token when none is valid.
|
|
102
|
+
*/
|
|
103
|
+
setClientCredentials(clientId, clientSecret, scopes = ['all']) {
|
|
104
|
+
this.options.clientId = clientId;
|
|
105
|
+
this.options.clientSecret = clientSecret;
|
|
106
|
+
this.options.scopes = scopes;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Ensure a usable access token exists before a signed request. Token-first with credentials as a
|
|
110
|
+
* fallback (mirrors the PHP client's lazy authenticate()): a valid token is left untouched; an
|
|
111
|
+
* expired token carrying a refresh_token is refreshed; otherwise, if confidential client
|
|
112
|
+
* credentials are configured, a token is minted via the client_credentials grant. Single-flighted
|
|
113
|
+
* so concurrent requests share one acquisition.
|
|
114
|
+
*/
|
|
115
|
+
async ensureAccessToken() {
|
|
116
|
+
if (this.oauthClientToken !== null && !OAuthClientToken.isExpired(this.oauthClientToken)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (this.tokenAcquisitionPromise === null) {
|
|
120
|
+
this.tokenAcquisitionPromise = this.acquireToken();
|
|
121
|
+
try {
|
|
122
|
+
await this.tokenAcquisitionPromise;
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
// Always clear, even on failure, so one failed acquisition doesn't poison every later
|
|
126
|
+
// request with the same rejected promise.
|
|
127
|
+
this.tokenAcquisitionPromise = null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
await this.tokenAcquisitionPromise;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async acquireToken() {
|
|
135
|
+
// Expired token that carries a refresh_token → refresh grant (keeps the existing session).
|
|
136
|
+
if (this.oauthClientToken !== null && OAuthClientToken.isExpired(this.oauthClientToken) && OAuthClient.hasRefreshToken(this.oauthClientToken)) {
|
|
137
|
+
await this.refreshToken();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// No usable token (missing, or expired without a refresh_token): mint one from client
|
|
141
|
+
// credentials if we have them. Public clients (no secret) can't use the client_credentials
|
|
142
|
+
// grant, so they fall through and the caller gets a 401 — they must obtain a token another way.
|
|
143
|
+
if (this.options.clientId !== null && !this.isPublicClient()) {
|
|
144
|
+
await this.authenticate();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
static hasRefreshToken(token) {
|
|
148
|
+
return token.refresh_token !== undefined && token.refresh_token !== null && token.refresh_token !== '';
|
|
149
|
+
}
|
|
96
150
|
isPublicClient() {
|
|
97
151
|
return this.options.clientSecret === null || this.options.clientSecret === '';
|
|
98
152
|
}
|
|
@@ -141,29 +195,13 @@ export class OAuthClient {
|
|
|
141
195
|
}
|
|
142
196
|
async request(action, parameters = null, method = 'GET', signWithOauthToken = true) {
|
|
143
197
|
if (signWithOauthToken) {
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
// a rejected refresh.
|
|
198
|
+
// Token-first, credentials-as-fallback (mirrors the PHP client): use a valid token as-is,
|
|
199
|
+
// otherwise refresh or mint one from client credentials. Only when nothing is available do
|
|
200
|
+
// we 401 — the same sign-out path consumers already handle for a rejected refresh.
|
|
201
|
+
await this.ensureAccessToken();
|
|
147
202
|
if (this.oauthClientToken === null) {
|
|
148
203
|
throw new ClientError('Unable to perform request, access token not provided', HttpStatus.HTTP_UNAUTHORIZED);
|
|
149
204
|
}
|
|
150
|
-
if (OAuthClientToken.isExpired(this.oauthClientToken)) {
|
|
151
|
-
if (this.refreshTokenPromise === null) {
|
|
152
|
-
this.refreshTokenPromise = this.refreshToken();
|
|
153
|
-
try {
|
|
154
|
-
await this.refreshTokenPromise;
|
|
155
|
-
}
|
|
156
|
-
finally {
|
|
157
|
-
// Always clear, even on failure, so a single failed refresh doesn't
|
|
158
|
-
// poison every later request with the same rejected promise (e.g.
|
|
159
|
-
// after the user re-authenticates).
|
|
160
|
-
this.refreshTokenPromise = null;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
await this.refreshTokenPromise;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
205
|
}
|
|
168
206
|
const requestData = this.createRequestData(action, parameters, method, signWithOauthToken);
|
|
169
207
|
if (this.debug) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { ApiRecord } from '../ApiRecord.js';
|
|
1
2
|
export declare class CollectionRecord {
|
|
2
3
|
id: string;
|
|
3
4
|
collection: string;
|
|
4
5
|
properties: Record<string, unknown>;
|
|
5
|
-
static parse(raw:
|
|
6
|
+
static parse(raw: ApiRecord): CollectionRecord;
|
|
6
7
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ApiRecord } from '../ApiRecord.js';
|
|
2
2
|
import { DataValueValue } from '../DataValue.js';
|
|
3
|
+
import { State } from '../State.js';
|
|
3
4
|
export declare class Configuration {
|
|
4
5
|
id: string;
|
|
5
6
|
path: string;
|
|
6
7
|
value: DataValueValue;
|
|
7
8
|
state: State;
|
|
8
9
|
scope: string;
|
|
9
|
-
static parse(rawConfig:
|
|
10
|
+
static parse(rawConfig: ApiRecord): Configuration;
|
|
10
11
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ApiRecord } from '../ApiRecord.js';
|
|
2
|
+
import { DataValueCollection } from '../DataValueCollection.js';
|
|
1
3
|
import { State } from '../State.js';
|
|
2
4
|
import { StateAware } from '../StateAware.js';
|
|
3
5
|
export declare class CodeSourceBuildStrategy implements StateAware {
|
|
@@ -6,5 +8,6 @@ export declare class CodeSourceBuildStrategy implements StateAware {
|
|
|
6
8
|
description: string;
|
|
7
9
|
languageId: string;
|
|
8
10
|
state: State;
|
|
9
|
-
|
|
11
|
+
data: DataValueCollection;
|
|
12
|
+
static parse(raw: ApiRecord): CodeSourceBuildStrategy;
|
|
10
13
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DataValueCollection } from '../DataValueCollection.js';
|
|
1
2
|
import { State } from '../State.js';
|
|
2
3
|
export class CodeSourceBuildStrategy {
|
|
3
4
|
id;
|
|
@@ -5,6 +6,10 @@ export class CodeSourceBuildStrategy {
|
|
|
5
6
|
description;
|
|
6
7
|
languageId;
|
|
7
8
|
state = State.Active;
|
|
9
|
+
// The strategy's parameter bag. Today it holds `command` (the shell command run inside the
|
|
10
|
+
// platform build image). Kept as the whole bag so new keys need no model change. Read with
|
|
11
|
+
// `data.get('command')`. Mirrors CodeSourceRunStrategy.
|
|
12
|
+
data = new DataValueCollection();
|
|
8
13
|
static parse(raw) {
|
|
9
14
|
const codeDeployStrategy = new CodeSourceBuildStrategy();
|
|
10
15
|
codeDeployStrategy.id = raw.id;
|
|
@@ -12,6 +17,7 @@ export class CodeSourceBuildStrategy {
|
|
|
12
17
|
codeDeployStrategy.description = raw.description;
|
|
13
18
|
codeDeployStrategy.languageId = raw.language;
|
|
14
19
|
codeDeployStrategy.state = State.fromString(raw.state);
|
|
20
|
+
codeDeployStrategy.data = DataValueCollection.fromObject(raw.data);
|
|
15
21
|
return codeDeployStrategy;
|
|
16
22
|
}
|
|
17
23
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { ApiRecord } from '../ApiRecord.js';
|
|
1
2
|
import { DataValueCollection } from '../DataValueCollection.js';
|
|
2
|
-
import { StateAware } from '../StateAware.js';
|
|
3
3
|
import { State } from '../State.js';
|
|
4
|
+
import { StateAware } from '../StateAware.js';
|
|
4
5
|
export declare class CodeSourceRunStrategy implements StateAware {
|
|
5
6
|
id: string;
|
|
6
7
|
name: string;
|
|
@@ -8,5 +9,5 @@ export declare class CodeSourceRunStrategy implements StateAware {
|
|
|
8
9
|
languageId: string;
|
|
9
10
|
state: State;
|
|
10
11
|
data: DataValueCollection;
|
|
11
|
-
static parse(raw:
|
|
12
|
+
static parse(raw: ApiRecord): CodeSourceRunStrategy;
|
|
12
13
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { ApiRecord } from '../ApiRecord.js';
|
|
1
2
|
export declare class SourcesAccountRepository {
|
|
2
3
|
key: string;
|
|
3
4
|
name: string;
|
|
4
5
|
url: string;
|
|
5
6
|
description: string;
|
|
6
|
-
static parse(rawSourcesAccountRepository:
|
|
7
|
+
static parse(rawSourcesAccountRepository: ApiRecord): SourcesAccountRepository;
|
|
7
8
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.
|
|
1
|
+
export declare const VERSION = "1.80.0";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "1.
|
|
1
|
+
export const VERSION = "1.80.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@attlaz/client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.81.0",
|
|
4
4
|
"description": "Javascript Client to access Attlaz API",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/jest": "^30.0.0",
|
|
54
|
-
"@types/node": "^26.
|
|
54
|
+
"@types/node": "^26.1.1",
|
|
55
55
|
"@typescript-eslint/eslint-plugin": "^8.59.3",
|
|
56
56
|
"@typescript-eslint/parser": "^8.59.3",
|
|
57
57
|
"eslint": "^9.39.4",
|