@reactionary/provider-commercetools 0.3.16 → 0.3.17
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/core/client.js +83 -9
- package/core/token-cache.js +5 -6
- package/package.json +2 -2
- package/providers/inventory.provider.js +5 -2
- package/providers/price.provider.js +18 -16
- package/schema/configuration.schema.js +5 -1
- package/src/core/client.d.ts +17 -1
- package/src/core/token-cache.d.ts +2 -2
- package/src/providers/price.provider.d.ts +0 -4
- package/src/schema/configuration.schema.d.ts +4 -0
- package/src/schema/session.schema.d.ts +1 -0
package/core/client.js
CHANGED
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
import * as crypto from "crypto";
|
|
11
11
|
import createDebug from "debug";
|
|
12
12
|
import { RequestContextTokenCache } from "./token-cache.js";
|
|
13
|
+
import { CommercetoolsSessionSchema } from "../schema/session.schema.js";
|
|
13
14
|
const debug = createDebug("reactionary:commercetools");
|
|
15
|
+
const PROVIDER_SESSION_KEY = "COMMERCETOOLS_PROVIDER";
|
|
14
16
|
class CommercetoolsAPI {
|
|
15
17
|
constructor(config, context) {
|
|
16
18
|
this.config = config;
|
|
17
19
|
this.context = context;
|
|
18
|
-
this.
|
|
20
|
+
this.tokenCache = new RequestContextTokenCache(this.context, PROVIDER_SESSION_KEY);
|
|
19
21
|
}
|
|
20
22
|
async getClient() {
|
|
21
23
|
if (!this.client) {
|
|
@@ -29,6 +31,77 @@ class CommercetoolsAPI {
|
|
|
29
31
|
}
|
|
30
32
|
return this.adminClient;
|
|
31
33
|
}
|
|
34
|
+
getSessionData() {
|
|
35
|
+
return this.context.session[PROVIDER_SESSION_KEY] ? this.context.session[PROVIDER_SESSION_KEY] : CommercetoolsSessionSchema.parse({});
|
|
36
|
+
}
|
|
37
|
+
setSessionData(sessionData) {
|
|
38
|
+
const existingData = this.context.session[PROVIDER_SESSION_KEY];
|
|
39
|
+
this.context.session[PROVIDER_SESSION_KEY] = {
|
|
40
|
+
...existingData,
|
|
41
|
+
...sessionData
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Only caches it pr session for now...... but still better than every call
|
|
46
|
+
* @param key
|
|
47
|
+
* @returns
|
|
48
|
+
*/
|
|
49
|
+
async resolveChannelIdByKey(key) {
|
|
50
|
+
const sessionData = this.getSessionData();
|
|
51
|
+
const cacheKey = `___channel_${key}`;
|
|
52
|
+
const cachedValue = sessionData[cacheKey];
|
|
53
|
+
if (cachedValue) {
|
|
54
|
+
if (debug.enabled) {
|
|
55
|
+
debug(`Resolved channel ${key} from cache`);
|
|
56
|
+
}
|
|
57
|
+
return cachedValue + "";
|
|
58
|
+
}
|
|
59
|
+
const client = await this.getAdminClient();
|
|
60
|
+
const response = await client.withProjectKey({ projectKey: this.config.projectKey }).channels().withKey({ key }).get().execute();
|
|
61
|
+
const channel = response.body;
|
|
62
|
+
this.setSessionData({
|
|
63
|
+
cacheKey: channel.id
|
|
64
|
+
});
|
|
65
|
+
if (debug.enabled) {
|
|
66
|
+
debug(`Resolved channel ${key} from API and cached it`);
|
|
67
|
+
}
|
|
68
|
+
return channel.id;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Only caches it pr session for now...... but still better than every call
|
|
72
|
+
* @param key
|
|
73
|
+
* @returns
|
|
74
|
+
*/
|
|
75
|
+
async resolveChannelIdByRole(role) {
|
|
76
|
+
const sessionData = this.getSessionData();
|
|
77
|
+
const cacheKey = `___channel_role_${role}`;
|
|
78
|
+
const cachedValue = sessionData[cacheKey];
|
|
79
|
+
if (cachedValue) {
|
|
80
|
+
if (debug.enabled) {
|
|
81
|
+
debug(`Resolved channel ${role} from cache`);
|
|
82
|
+
}
|
|
83
|
+
return cachedValue + "";
|
|
84
|
+
}
|
|
85
|
+
const client = await this.getAdminClient();
|
|
86
|
+
const response = await client.withProjectKey({ projectKey: this.config.projectKey }).channels().get({
|
|
87
|
+
queryArgs: {
|
|
88
|
+
where: `roles contains any (:role)`,
|
|
89
|
+
"var.role": role
|
|
90
|
+
}
|
|
91
|
+
}).execute();
|
|
92
|
+
const channels = response.body;
|
|
93
|
+
if (channels.results.length === 0) {
|
|
94
|
+
throw new Error(`No channel found with role ${role}`);
|
|
95
|
+
}
|
|
96
|
+
const channel = channels.results[0];
|
|
97
|
+
this.setSessionData({
|
|
98
|
+
[cacheKey]: channel.id
|
|
99
|
+
});
|
|
100
|
+
if (debug.enabled) {
|
|
101
|
+
debug(`Resolved channel ${role} from API and cached it`);
|
|
102
|
+
}
|
|
103
|
+
return channel.id;
|
|
104
|
+
}
|
|
32
105
|
async createAdminClient() {
|
|
33
106
|
let builder = this.createBaseClientBuilder();
|
|
34
107
|
builder = builder.withAnonymousSessionFlow({
|
|
@@ -42,11 +115,11 @@ class CommercetoolsAPI {
|
|
|
42
115
|
return createApiBuilderFromCtpClient(builder.build());
|
|
43
116
|
}
|
|
44
117
|
async createClient() {
|
|
45
|
-
let session = await this.
|
|
118
|
+
let session = await this.tokenCache.get();
|
|
46
119
|
const isNewSession = !session || !session.refreshToken;
|
|
47
120
|
if (isNewSession) {
|
|
48
121
|
await this.becomeGuest();
|
|
49
|
-
session = await this.
|
|
122
|
+
session = await this.tokenCache.get();
|
|
50
123
|
}
|
|
51
124
|
let builder = this.createBaseClientBuilder();
|
|
52
125
|
builder = builder.withRefreshTokenFlow({
|
|
@@ -57,7 +130,7 @@ class CommercetoolsAPI {
|
|
|
57
130
|
host: this.config.authUrl,
|
|
58
131
|
projectKey: this.config.projectKey,
|
|
59
132
|
refreshToken: session?.refreshToken || "",
|
|
60
|
-
tokenCache: this.
|
|
133
|
+
tokenCache: this.tokenCache
|
|
61
134
|
});
|
|
62
135
|
return createApiBuilderFromCtpClient(builder.build());
|
|
63
136
|
}
|
|
@@ -92,7 +165,7 @@ class CommercetoolsAPI {
|
|
|
92
165
|
clientSecret: this.config.clientSecret,
|
|
93
166
|
user: { username, password }
|
|
94
167
|
},
|
|
95
|
-
tokenCache: this.
|
|
168
|
+
tokenCache: this.tokenCache,
|
|
96
169
|
scopes: this.config.scopes
|
|
97
170
|
});
|
|
98
171
|
const loginClient = createApiBuilderFromCtpClient(loginBuilder.build());
|
|
@@ -111,7 +184,7 @@ class CommercetoolsAPI {
|
|
|
111
184
|
});
|
|
112
185
|
}
|
|
113
186
|
async logout() {
|
|
114
|
-
await this.
|
|
187
|
+
await this.tokenCache.set({ token: "", refreshToken: "", expirationTime: 0 });
|
|
115
188
|
const identity = {
|
|
116
189
|
type: "Anonymous"
|
|
117
190
|
};
|
|
@@ -119,7 +192,7 @@ class CommercetoolsAPI {
|
|
|
119
192
|
}
|
|
120
193
|
// FIXME: This can fail if the short-lived access token has expired. In other words, probably missing a token refresh.
|
|
121
194
|
async introspect() {
|
|
122
|
-
const session = await this.
|
|
195
|
+
const session = await this.tokenCache.get();
|
|
123
196
|
if (!session || !session.token) {
|
|
124
197
|
const identity = {
|
|
125
198
|
type: "Anonymous"
|
|
@@ -191,7 +264,7 @@ class CommercetoolsAPI {
|
|
|
191
264
|
}
|
|
192
265
|
);
|
|
193
266
|
const result = await response.json();
|
|
194
|
-
this.
|
|
267
|
+
this.tokenCache.set({
|
|
195
268
|
expirationTime: Date.now() + Number(result.expires_in) * 1e3 - 5 * 60 * 1e3,
|
|
196
269
|
token: result.access_token,
|
|
197
270
|
refreshToken: result.refresh_token
|
|
@@ -235,5 +308,6 @@ class CommercetoolsAPI {
|
|
|
235
308
|
}
|
|
236
309
|
}
|
|
237
310
|
export {
|
|
238
|
-
CommercetoolsAPI
|
|
311
|
+
CommercetoolsAPI,
|
|
312
|
+
PROVIDER_SESSION_KEY
|
|
239
313
|
};
|
package/core/token-cache.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { CommercetoolsSessionSchema } from "../schema/session.schema.js";
|
|
2
|
-
const PROVIDER_COMMERCETOOLS_SESSION_KEY = "PROVIDER_COMMERCETOOLS";
|
|
3
2
|
class RequestContextTokenCache {
|
|
4
|
-
constructor(context) {
|
|
3
|
+
constructor(context, sessionProviderKey) {
|
|
5
4
|
this.context = context;
|
|
5
|
+
this.sessionProviderKey = sessionProviderKey;
|
|
6
6
|
}
|
|
7
7
|
async get(tokenCacheOptions) {
|
|
8
8
|
const session = CommercetoolsSessionSchema.parse(
|
|
9
|
-
this.context.session[
|
|
9
|
+
this.context.session[this.sessionProviderKey] || {}
|
|
10
10
|
);
|
|
11
11
|
if (!session) {
|
|
12
12
|
return {
|
|
@@ -23,15 +23,14 @@ class RequestContextTokenCache {
|
|
|
23
23
|
}
|
|
24
24
|
async set(cache, tokenCacheOptions) {
|
|
25
25
|
const session = CommercetoolsSessionSchema.parse(
|
|
26
|
-
this.context.session[
|
|
26
|
+
this.context.session[this.sessionProviderKey] || {}
|
|
27
27
|
);
|
|
28
|
-
this.context.session[
|
|
28
|
+
this.context.session[this.sessionProviderKey] = session;
|
|
29
29
|
session.refreshToken = cache.refreshToken;
|
|
30
30
|
session.token = cache.token;
|
|
31
31
|
session.expirationTime = cache.expirationTime;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
export {
|
|
35
|
-
PROVIDER_COMMERCETOOLS_SESSION_KEY,
|
|
36
35
|
RequestContextTokenCache
|
|
37
36
|
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactionary/provider-commercetools",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.17",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"types": "src/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@reactionary/core": "0.3.
|
|
7
|
+
"@reactionary/core": "0.3.17",
|
|
8
8
|
"debug": "^4.4.3",
|
|
9
9
|
"zod": "4.1.9",
|
|
10
10
|
"@commercetools/ts-client": "^4.2.1",
|
|
@@ -23,8 +23,7 @@ class CommercetoolsInventoryProvider extends InventoryProvider {
|
|
|
23
23
|
async getBySKU(payload) {
|
|
24
24
|
const client = await this.getClient();
|
|
25
25
|
try {
|
|
26
|
-
const
|
|
27
|
-
const channelId = channel.body.id;
|
|
26
|
+
const channelId = await this.commercetools.resolveChannelIdByKey(payload.fulfilmentCenter.key);
|
|
28
27
|
const remote = await client.inventory().get({
|
|
29
28
|
queryArgs: {
|
|
30
29
|
where: "sku=:sku AND supplyChannel(id=:channel)",
|
|
@@ -66,6 +65,10 @@ class CommercetoolsInventoryProvider extends InventoryProvider {
|
|
|
66
65
|
}
|
|
67
66
|
__decorateClass([
|
|
68
67
|
Reactionary({
|
|
68
|
+
cache: true,
|
|
69
|
+
cacheTimeToLiveInSeconds: 300,
|
|
70
|
+
currencyDependentCaching: false,
|
|
71
|
+
localeDependentCaching: false,
|
|
69
72
|
inputSchema: InventoryQueryBySKUSchema,
|
|
70
73
|
outputSchema: InventorySchema
|
|
71
74
|
})
|
|
@@ -25,7 +25,12 @@ class CommercetoolsPriceProvider extends PriceProvider {
|
|
|
25
25
|
}
|
|
26
26
|
async getCustomerPrice(payload) {
|
|
27
27
|
const client = await this.getClient();
|
|
28
|
-
|
|
28
|
+
let priceChannelId;
|
|
29
|
+
if (this.config.customerPriceChannelKey) {
|
|
30
|
+
priceChannelId = await this.commercetools.resolveChannelIdByKey(this.config.customerPriceChannelKey);
|
|
31
|
+
} else {
|
|
32
|
+
priceChannelId = await this.commercetools.resolveChannelIdByRole("Primary");
|
|
33
|
+
}
|
|
29
34
|
const response = await client.productProjections().get({
|
|
30
35
|
queryArgs: {
|
|
31
36
|
staged: false,
|
|
@@ -46,7 +51,12 @@ class CommercetoolsPriceProvider extends PriceProvider {
|
|
|
46
51
|
}
|
|
47
52
|
async getListPrice(payload) {
|
|
48
53
|
const client = await this.getClient();
|
|
49
|
-
|
|
54
|
+
let priceChannelId;
|
|
55
|
+
if (this.config.listPriceChannelKey) {
|
|
56
|
+
priceChannelId = await this.commercetools.resolveChannelIdByKey(this.config.listPriceChannelKey);
|
|
57
|
+
} else {
|
|
58
|
+
priceChannelId = await this.commercetools.resolveChannelIdByRole("Primary");
|
|
59
|
+
}
|
|
50
60
|
const response = await client.productProjections().get({
|
|
51
61
|
queryArgs: {
|
|
52
62
|
staged: false,
|
|
@@ -79,8 +89,12 @@ class CommercetoolsPriceProvider extends PriceProvider {
|
|
|
79
89
|
value: price.value.centAmount / 100,
|
|
80
90
|
currency: price.value.currencyCode
|
|
81
91
|
};
|
|
92
|
+
let isOnSale = false;
|
|
82
93
|
if (options.includeDiscounts) {
|
|
83
94
|
const discountedPrice = price.discounted?.value || price.value;
|
|
95
|
+
if (price.discounted) {
|
|
96
|
+
isOnSale = true;
|
|
97
|
+
}
|
|
84
98
|
unitPrice = {
|
|
85
99
|
value: discountedPrice.centAmount / 100,
|
|
86
100
|
currency: price.value.currencyCode
|
|
@@ -94,23 +108,11 @@ class CommercetoolsPriceProvider extends PriceProvider {
|
|
|
94
108
|
const result = {
|
|
95
109
|
identifier,
|
|
96
110
|
tieredPrices: [],
|
|
97
|
-
unitPrice
|
|
111
|
+
unitPrice,
|
|
112
|
+
onSale: isOnSale
|
|
98
113
|
};
|
|
99
114
|
return result;
|
|
100
115
|
}
|
|
101
|
-
async getChannels() {
|
|
102
|
-
const adminClient = await this.commercetools.getAdminClient();
|
|
103
|
-
const offerPriceChannelPromise = adminClient.withProjectKey({ projectKey: this.config.projectKey }).channels().withKey({ key: "Offer Price" }).get().execute();
|
|
104
|
-
const listPriceChannelPromise = adminClient.withProjectKey({ projectKey: this.config.projectKey }).channels().withKey({ key: "List Price" }).get().execute();
|
|
105
|
-
const [offerChannel, listChannel] = await Promise.all([
|
|
106
|
-
offerPriceChannelPromise,
|
|
107
|
-
listPriceChannelPromise
|
|
108
|
-
]);
|
|
109
|
-
return {
|
|
110
|
-
offer: offerChannel.body.id,
|
|
111
|
-
list: listChannel.body.id
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
116
|
}
|
|
115
117
|
__decorateClass([
|
|
116
118
|
Reactionary({
|
|
@@ -7,8 +7,12 @@ const CommercetoolsConfigurationSchema = z.looseObject({
|
|
|
7
7
|
clientId: z.string(),
|
|
8
8
|
clientSecret: z.string(),
|
|
9
9
|
scopes: z.array(z.string()).default(() => []),
|
|
10
|
+
adminClientId: z.string().optional(),
|
|
11
|
+
adminClientSecret: z.string().optional(),
|
|
10
12
|
paymentMethods: PaymentMethodSchema.array().optional().default(() => []),
|
|
11
|
-
facetFieldsForSearch: z.array(z.string()).default(() => [])
|
|
13
|
+
facetFieldsForSearch: z.array(z.string()).default(() => []),
|
|
14
|
+
listPriceChannelKey: z.string().optional(),
|
|
15
|
+
customerPriceChannelKey: z.string().optional()
|
|
12
16
|
});
|
|
13
17
|
export {
|
|
14
18
|
CommercetoolsConfigurationSchema
|
package/src/core/client.d.ts
CHANGED
|
@@ -3,15 +3,31 @@ import { type ApiRoot } from '@commercetools/platform-sdk';
|
|
|
3
3
|
import type { CommercetoolsConfiguration } from '../schema/configuration.schema.js';
|
|
4
4
|
import { type AnonymousIdentity, type GuestIdentity, type RegisteredIdentity, type RequestContext } from '@reactionary/core';
|
|
5
5
|
import { RequestContextTokenCache } from './token-cache.js';
|
|
6
|
+
import { type CommercetoolsSession } from '../schema/session.schema.js';
|
|
7
|
+
export declare const PROVIDER_SESSION_KEY = "COMMERCETOOLS_PROVIDER";
|
|
6
8
|
export declare class CommercetoolsAPI {
|
|
7
9
|
protected config: CommercetoolsConfiguration;
|
|
8
10
|
protected context: RequestContext;
|
|
9
|
-
protected
|
|
11
|
+
protected tokenCache: RequestContextTokenCache;
|
|
10
12
|
protected client: Promise<ApiRoot> | undefined;
|
|
11
13
|
protected adminClient: Promise<ApiRoot> | undefined;
|
|
12
14
|
constructor(config: CommercetoolsConfiguration, context: RequestContext);
|
|
13
15
|
getClient(): Promise<ApiRoot>;
|
|
14
16
|
getAdminClient(): Promise<ApiRoot>;
|
|
17
|
+
getSessionData(): CommercetoolsSession;
|
|
18
|
+
setSessionData(sessionData: Partial<CommercetoolsSession>): void;
|
|
19
|
+
/**
|
|
20
|
+
* Only caches it pr session for now...... but still better than every call
|
|
21
|
+
* @param key
|
|
22
|
+
* @returns
|
|
23
|
+
*/
|
|
24
|
+
resolveChannelIdByKey(key: string): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Only caches it pr session for now...... but still better than every call
|
|
27
|
+
* @param key
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
30
|
+
resolveChannelIdByRole(role: string): Promise<string>;
|
|
15
31
|
protected createAdminClient(): Promise<ApiRoot>;
|
|
16
32
|
protected createClient(): Promise<ApiRoot>;
|
|
17
33
|
register(username: string, password: string): Promise<{
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { TokenCache, TokenCacheOptions, TokenStore } from "@commercetools/ts-client";
|
|
2
2
|
import type { RequestContext } from "@reactionary/core";
|
|
3
|
-
export declare const PROVIDER_COMMERCETOOLS_SESSION_KEY = "PROVIDER_COMMERCETOOLS";
|
|
4
3
|
export declare class RequestContextTokenCache implements TokenCache {
|
|
5
4
|
protected context: RequestContext;
|
|
6
|
-
|
|
5
|
+
protected sessionProviderKey: string;
|
|
6
|
+
constructor(context: RequestContext, sessionProviderKey: string);
|
|
7
7
|
get(tokenCacheOptions?: TokenCacheOptions): Promise<TokenStore | undefined>;
|
|
8
8
|
set(cache: TokenStore, tokenCacheOptions?: TokenCacheOptions): Promise<void>;
|
|
9
9
|
}
|
|
@@ -6,6 +6,8 @@ export declare const CommercetoolsConfigurationSchema: z.ZodObject<{
|
|
|
6
6
|
clientId: z.ZodString;
|
|
7
7
|
clientSecret: z.ZodString;
|
|
8
8
|
scopes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
9
|
+
adminClientId: z.ZodOptional<z.ZodString>;
|
|
10
|
+
adminClientSecret: z.ZodOptional<z.ZodString>;
|
|
9
11
|
paymentMethods: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
10
12
|
identifier: z.ZodObject<{
|
|
11
13
|
method: z.ZodString;
|
|
@@ -22,5 +24,7 @@ export declare const CommercetoolsConfigurationSchema: z.ZodObject<{
|
|
|
22
24
|
isPunchOut: z.ZodBoolean;
|
|
23
25
|
}, z.core.$loose>>>>;
|
|
24
26
|
facetFieldsForSearch: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
27
|
+
listPriceChannelKey: z.ZodOptional<z.ZodString>;
|
|
28
|
+
customerPriceChannelKey: z.ZodOptional<z.ZodString>;
|
|
25
29
|
}, z.core.$loose>;
|
|
26
30
|
export type CommercetoolsConfiguration = z.infer<typeof CommercetoolsConfigurationSchema>;
|