@builder.io/plugin-commercelayer 0.0.6 → 0.0.8-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 CHANGED
@@ -6,7 +6,8 @@ Easily connect your Commerce Layer products to your Builder.io content!
6
6
 
7
7
  Go to [builder.io/account/organization](https://builder.io/account/organization) and press on `@builder.io/plugin-commercelayer` in the list of plugins, then hit save. You'll be prompted for your Commerce Layer credentials:
8
8
  - Client ID
9
- - Market Scope (e.g., 'market:id:YOUR_MARKET_ID')
9
+ - Client Secret (optional - required for integration tokens to access all markets)
10
+ - Market Scope (e.g., 'market:all' for integrations, 'market:id:YOUR_MARKET_ID' for sales channels)
10
11
 
11
12
  ## Features
12
13
 
@@ -68,10 +69,21 @@ Go to [builder.io/account/organization](https://builder.io/account/organization)
68
69
 
69
70
  ## Authentication
70
71
 
71
- The plugin uses Commerce Layer's Sales Channel authentication. You'll need:
72
+ The plugin supports both Commerce Layer authentication methods:
73
+
74
+ ### Sales Channel Authentication (Market-Specific)
75
+ For accessing products from a specific market:
72
76
  - A Commerce Layer account
73
- - Sales Channel API credentials
74
- - A valid market scope
77
+ - Sales Channel API credentials (Client ID only)
78
+ - Market-specific scope (e.g., `market:id:YOUR_MARKET_ID`)
79
+
80
+ ### Integration Authentication (All Markets)
81
+ For accessing products from all markets:
82
+ - A Commerce Layer account
83
+ - Integration API credentials (Client ID + Client Secret)
84
+ - Market scope set to `market:all`
85
+
86
+ **Note:** Sales channel tokens with `market:all` scope will not return products due to Commerce Layer restrictions. Use integration tokens for cross-market access.
75
87
 
76
88
  ## Contributing
77
89
 
@@ -146881,7 +146881,7 @@ System.register(['@builder.io/sdk', '@emotion/core', '@material-ui/core', 'react
146881
146881
  });
146882
146882
 
146883
146883
  var name = "@builder.io/plugin-commercelayer";
146884
- var version = "0.0.5";
146884
+ var version = "0.0.8-0";
146885
146885
  var description = "Commerce Layer plugin for Builder.io";
146886
146886
  var keywords = [
146887
146887
  ];
@@ -146902,20 +146902,11 @@ System.register(['@builder.io/sdk', '@emotion/core', '@material-ui/core', 'react
146902
146902
  var type = "module";
146903
146903
  var types = "dist/index.d.ts";
146904
146904
  var scripts = {
146905
- lint: "tslint --project tsconfig.json -t codeFrame 'src/**/*.ts' 'test/**/*.ts'",
146906
146905
  prebuild: "rimraf dist",
146907
146906
  build: "rollup -c rollup.config.ts --configPlugin @rollup/plugin-typescript",
146908
146907
  start: "SERVE=true rollup -c rollup.config.ts --configPlugin @rollup/plugin-typescript -w",
146909
146908
  "release:dev": "npm run build && npm version prerelease --no-git-tag-version && npm publish --tag dev",
146910
- "release:patch": "npm run build && npm version patch --no-git-tag-version && npm publish",
146911
- test: "jest --coverage",
146912
- "test:watch": "jest --coverage --watch",
146913
- "test:prod": "npm run lint && npm run test -- --no-cache",
146914
- "report-coverage": "cat ./coverage/lcov.info | coveralls",
146915
- commit: "git-cz",
146916
- "semantic-release": "semantic-release",
146917
- "semantic-release-prepare": "ts-node tools/semantic-release-prepare",
146918
- "travis-deploy-once": "travis-deploy-once"
146909
+ "release:patch": "npm run build && npm version patch --no-git-tag-version && npm publish"
146919
146910
  };
146920
146911
  var devDependencies = {
146921
146912
  "@rollup/plugin-json": "^6.1.0",
@@ -146923,12 +146914,13 @@ System.register(['@builder.io/sdk', '@emotion/core', '@material-ui/core', 'react
146923
146914
  "@rollup/plugin-replace": "^5.0.5",
146924
146915
  "@rollup/plugin-typescript": "^11.1.6",
146925
146916
  "@types/node": "^20.11.19",
146917
+ rimraf: "^2.6.2",
146926
146918
  rollup: "^4.12.0",
146927
146919
  "rollup-plugin-esbuild": "^6.1.1",
146928
146920
  "rollup-plugin-serve": "^1.1.1",
146921
+ tslib: "^2.8.1",
146929
146922
  tsx: "^4.7.1",
146930
- typescript: "^5.2.2",
146931
- rimraf: "^2.6.2"
146923
+ typescript: "^5.2.2"
146932
146924
  };
146933
146925
  var dependencies = {
146934
146926
  "@builder.io/plugin-tools": "^0.0.6"
@@ -146952,26 +146944,44 @@ System.register(['@builder.io/sdk', '@emotion/core', '@material-ui/core', 'react
146952
146944
  dependencies: dependencies
146953
146945
  };
146954
146946
 
146955
- const authenticateClient = async ({ clientId, scope }) => {
146956
- const response = await fetch("https://auth.commercelayer.io/oauth/token", {
146947
+ let cachedAuth = null;
146948
+ const getValidToken = async (clientId, clientSecret, scope) => {
146949
+ const now = Date.now();
146950
+ if (cachedAuth && cachedAuth.clientId === clientId && cachedAuth.clientSecret === clientSecret && cachedAuth.scope === scope && now < cachedAuth.expiresAt) {
146951
+ return cachedAuth.token;
146952
+ }
146953
+ const auth = await authenticateClient({ clientId, clientSecret, scope });
146954
+ cachedAuth = {
146955
+ token: auth.accessToken,
146956
+ expiresAt: now + (auth.expiresIn - 300) * 1e3,
146957
+ // 5 min buffer
146958
+ clientId,
146959
+ clientSecret,
146960
+ scope
146961
+ };
146962
+ return auth.accessToken;
146963
+ };
146964
+ const authenticateClient = async ({ clientId, clientSecret, scope }) => {
146965
+ const authUrl = "https://auth.commercelayer.io/oauth/token";
146966
+ const requestBody = {
146967
+ grant_type: "client_credentials",
146968
+ client_id: clientId,
146969
+ scope
146970
+ };
146971
+ if (clientSecret) {
146972
+ requestBody.client_secret = clientSecret;
146973
+ }
146974
+ const response = await fetch(authUrl, {
146957
146975
  method: "POST",
146958
146976
  headers: {
146959
146977
  "Accept": "application/json",
146960
- "Content-Type": "application/json",
146961
- "Access-Control-Allow-Origin": "*",
146962
- "Access-Control-Allow-Methods": "POST, OPTIONS",
146963
- "Access-Control-Allow-Headers": "Content-Type, Authorization"
146978
+ "Content-Type": "application/json"
146964
146979
  },
146965
- body: JSON.stringify({
146966
- grant_type: "client_credentials",
146967
- client_id: clientId,
146968
- scope
146969
- })
146980
+ body: JSON.stringify(requestBody)
146970
146981
  });
146971
146982
  if (!response.ok) {
146972
146983
  const errorText = await response.text();
146973
- console.error("Auth Error:", errorText);
146974
- throw new Error(`Authentication failed: ${response.statusText}`);
146984
+ throw new Error(`Authentication failed: ${response.status} ${response.statusText} - ${errorText}`);
146975
146985
  }
146976
146986
  const data = await response.json();
146977
146987
  return {
@@ -146982,6 +146992,23 @@ System.register(['@builder.io/sdk', '@emotion/core', '@material-ui/core', 'react
146982
146992
  createdAt: data.created_at
146983
146993
  };
146984
146994
  };
146995
+ const makeApiRequest = async (url, accessToken, options = {}) => {
146996
+ const response = await fetch(url, {
146997
+ ...options,
146998
+ headers: {
146999
+ "Authorization": `Bearer ${accessToken}`,
147000
+ "Accept": "application/vnd.api+json",
147001
+ "Content-Type": "application/vnd.api+json",
147002
+ ...options.headers
147003
+ }
147004
+ });
147005
+ if (!response.ok) {
147006
+ const errorText = await response.text();
147007
+ throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`);
147008
+ }
147009
+ const data = await response.json();
147010
+ return data;
147011
+ };
146985
147012
  const getOrganizationInfo = async (accessToken) => {
146986
147013
  const [, payload] = accessToken.split(".");
146987
147014
  const decodedPayload = JSON.parse(atob(payload));
@@ -146994,42 +147021,25 @@ System.register(['@builder.io/sdk', '@emotion/core', '@material-ui/core', 'react
146994
147021
  throw new Error("Invalid token: no organization found");
146995
147022
  };
146996
147023
  const getProduct = async (id, accessToken, baseEndpoint) => {
146997
- const response = await fetch(`${baseEndpoint}/api/skus/${id}`, {
146998
- headers: {
146999
- "Authorization": `Bearer ${accessToken}`,
147000
- "Content-Type": "application/json"
147001
- }
147002
- });
147003
- if (!response.ok) {
147004
- throw new Error(`Failed to fetch product: ${response.statusText}`);
147005
- }
147006
- const data = await response.json();
147024
+ const url = `${baseEndpoint}/api/skus/${id}`;
147025
+ const data = await makeApiRequest(url, accessToken);
147007
147026
  return data.data;
147008
147027
  };
147009
147028
  const searchProducts = async (search, accessToken, baseEndpoint) => {
147010
147029
  const params = new URLSearchParams({
147011
- "filter[q][code_or_name_cont]": search,
147012
- "include": "prices,stock_items"
147030
+ "filter[q][name_or_code_cont]": search,
147031
+ "page[size]": "25"
147013
147032
  });
147014
- const response = await fetch(`${baseEndpoint}/api/skus?${params}`, {
147015
- headers: {
147016
- "Authorization": `Bearer ${accessToken}`,
147017
- "Accept": "application/vnd.api+json",
147018
- "Content-Type": "application/vnd.api+json"
147019
- }
147020
- });
147021
- if (!response.ok) {
147022
- console.error("Search Error:", await response.text());
147023
- throw new Error(`Failed to search products: ${response.statusText}`);
147024
- }
147025
- const data = await response.json();
147026
- return data.data;
147033
+ const url = `${baseEndpoint}/api/skus?${params}`;
147034
+ const data = await makeApiRequest(url, accessToken);
147035
+ return data.data || [];
147027
147036
  };
147028
147037
  const transformProduct = (product) => ({
147029
147038
  id: product.id,
147030
147039
  type: "product",
147031
147040
  title: product.attributes.name,
147032
147041
  handle: product.attributes.code,
147042
+ price: product.attributes.price,
147033
147043
  image: {
147034
147044
  src: product.attributes.image_url
147035
147045
  }
@@ -147038,18 +147048,8 @@ System.register(['@builder.io/sdk', '@emotion/core', '@material-ui/core', 'react
147038
147048
  const params = new URLSearchParams({
147039
147049
  "filter[q][code_eq]": handle
147040
147050
  });
147041
- const response = await fetch(`${baseEndpoint}/api/skus?${params}`, {
147042
- headers: {
147043
- "Authorization": `Bearer ${accessToken}`,
147044
- "Accept": "application/vnd.api+json",
147045
- "Content-Type": "application/vnd.api+json"
147046
- }
147047
- });
147048
- if (!response.ok) {
147049
- console.error("Product Handle Error:", await response.text());
147050
- throw new Error(`Failed to fetch product by handle: ${response.statusText}`);
147051
- }
147052
- const data = await response.json();
147051
+ const url = `${baseEndpoint}/api/skus?${params}`;
147052
+ const data = await makeApiRequest(url, accessToken);
147053
147053
  if (!data.data?.[0]) {
147054
147054
  throw new Error(`Product not found with handle: ${handle}`);
147055
147055
  }
@@ -147064,44 +147064,57 @@ System.register(['@builder.io/sdk', '@emotion/core', '@material-ui/core', 'react
147064
147064
  {
147065
147065
  name: "clientId",
147066
147066
  type: "string",
147067
- required: true
147067
+ required: true,
147068
+ helperText: "Your Commerce Layer application Client ID"
147069
+ },
147070
+ {
147071
+ name: "clientSecret",
147072
+ type: "string",
147073
+ required: false,
147074
+ helperText: "Your Commerce Layer Client Secret (required for integration tokens to access all markets)"
147068
147075
  },
147069
147076
  {
147070
147077
  name: "scope",
147071
147078
  type: "string",
147072
- required: true
147079
+ required: true,
147080
+ helperText: 'Commerce Layer scope (e.g., "market:all" for integrations, "market:id:YOUR_MARKET_ID" for sales channels)'
147073
147081
  }
147074
147082
  ],
147075
- ctaText: "Connect Commerce Layer"
147083
+ ctaText: "Connect your Commerce Layer store"
147076
147084
  },
147077
147085
  async (settings) => {
147078
147086
  try {
147079
147087
  const clientId = settings.get("clientId");
147088
+ const clientSecret = settings.get("clientSecret");
147080
147089
  const scope = settings.get("scope");
147081
- const auth = await authenticateClient({ clientId, scope });
147090
+ const auth = await authenticateClient({ clientId, clientSecret, scope });
147082
147091
  const { baseEndpoint } = await getOrganizationInfo(auth.accessToken);
147083
147092
  return {
147084
147093
  product: {
147085
147094
  async findById(id) {
147086
- const product = await getProduct(id, auth.accessToken, baseEndpoint);
147095
+ const token = await getValidToken(clientId, clientSecret, scope);
147096
+ const product = await getProduct(id, token, baseEndpoint);
147087
147097
  return transformProduct(product);
147088
147098
  },
147089
147099
  async findByHandle(handle) {
147090
- const product = await getProductByHandle(handle, auth.accessToken, baseEndpoint);
147100
+ const token = await getValidToken(clientId, clientSecret, scope);
147101
+ const product = await getProductByHandle(handle, token, baseEndpoint);
147091
147102
  return transformProduct(product);
147092
147103
  },
147093
147104
  async search(search) {
147094
- const products = await searchProducts(search, auth.accessToken, baseEndpoint);
147105
+ const token = await getValidToken(clientId, clientSecret, scope);
147106
+ const products = await searchProducts(search, token, baseEndpoint);
147095
147107
  return products.map(transformProduct);
147096
147108
  },
147097
147109
  getRequestObject(id) {
147110
+ const token = cachedAuth?.token || auth.accessToken;
147098
147111
  return {
147099
147112
  "@type": "@builder.io/core:Request",
147100
147113
  request: {
147101
147114
  url: `${baseEndpoint}/api/skus/${id}`,
147102
147115
  method: "GET",
147103
147116
  headers: {
147104
- "Authorization": `Bearer ${auth.accessToken}`
147117
+ "Authorization": `Bearer ${token}`
147105
147118
  }
147106
147119
  },
147107
147120
  options: {