@joonweb/joonweb-sdk 1.0.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.
@@ -0,0 +1,66 @@
1
+ const axios = require('axios');
2
+
3
+ class BaseClient {
4
+ constructor(accessToken = null, siteDomain = null) {
5
+ this.accessToken = accessToken;
6
+ this.siteDomain = siteDomain;
7
+ this.timeout = 30000;
8
+ this.apiVersion = process.env.JOONWEB_API_VERSION || '26.0';
9
+ }
10
+
11
+ setAccessToken(token) {
12
+ this.accessToken = token;
13
+ return this;
14
+ }
15
+
16
+ setSiteDomain(domain) {
17
+ this.siteDomain = domain;
18
+ return this;
19
+ }
20
+
21
+ async request(endpoint, method = 'GET', data = null, params = {}) {
22
+ if (!this.siteDomain) {
23
+ throw new Error('Site domain is not set. Call setSiteDomain() first.');
24
+ }
25
+
26
+ if (!this.accessToken) {
27
+ throw new Error('Access token is not set. Call setAccessToken() first.');
28
+ }
29
+
30
+ const url = `https://${this.siteDomain}/api/admin/${this.apiVersion}${endpoint}`;
31
+
32
+ const config = {
33
+ method: method.toLowerCase(),
34
+ url: url,
35
+ headers: {
36
+ 'Content-Type': 'application/json',
37
+ 'Accept': 'application/json',
38
+ 'X-JoonWeb-Access-Token': this.accessToken
39
+ },
40
+ timeout: this.timeout,
41
+ params: Object.keys(params).length > 0 ? params : undefined
42
+ };
43
+
44
+ if (data && ['post', 'put', 'patch'].includes(config.method)) {
45
+ config.data = data;
46
+ }
47
+
48
+ try {
49
+ const response = await axios(config);
50
+ return response.data;
51
+ } catch (error) {
52
+ if (error.response) {
53
+ const apiError = new Error(
54
+ `JoonWeb API Error ${error.response.status}: ${JSON.stringify(error.response.data)}`
55
+ );
56
+ apiError.status = error.response.status;
57
+ apiError.data = error.response.data;
58
+ throw apiError;
59
+ } else {
60
+ throw new Error(`JoonWeb API Request Failed: ${error.message}`);
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ module.exports = BaseClient;
package/src/Context.js ADDED
@@ -0,0 +1,82 @@
1
+ // src/Context.js
2
+ const path = require('path');
3
+
4
+ class Context {
5
+ static API_KEY = null;
6
+ static API_SECRET_KEY = null;
7
+ static API_VERSION = '26.0';
8
+ static IS_EMBEDDED_APP = true;
9
+ static SESSION_STORAGE = null;
10
+ static APP_NAME = 'JoonWeb App';
11
+ static HOST_SCHEME = 'https';
12
+ static HOST_NAME = null;
13
+
14
+ // New: Storage configuration
15
+ static SESSION_STORAGE_TYPE = 'memory'; // 'memory', 'sqlite', 'mongodb', 'redis'
16
+ static SESSION_STORAGE_OPTIONS = {};
17
+
18
+ static init(options = {}) {
19
+ this.API_KEY = options.api_key || process.env.JOONWEB_API_KEY || this.API_KEY;
20
+ this.API_SECRET_KEY = options.api_secret || process.env.JOONWEB_API_SECRET || this.API_SECRET_KEY;
21
+ this.API_VERSION = options.api_version || process.env.JOONWEB_API_VERSION || this.API_VERSION;
22
+ this.IS_EMBEDDED_APP = options.is_embedded !== undefined ? options.is_embedded : this.IS_EMBEDDED_APP;
23
+ this.APP_NAME = options.app_name || process.env.APP_NAME || this.APP_NAME;
24
+ this.HOST_NAME = options.host_name || process.env.HOST_NAME || this.HOST_NAME;
25
+
26
+ // Storage configuration
27
+ this.SESSION_STORAGE_TYPE = options.session_storage_type ||
28
+ process.env.JOONWEB_SESSION_STORAGE_TYPE ||
29
+ 'memory';
30
+ this.SESSION_STORAGE_OPTIONS = options.session_storage_options || {};
31
+
32
+ // Initialize session storage
33
+ this.initSessionStorage();
34
+ }
35
+
36
+ static initSessionStorage() {
37
+ try {
38
+ const { createStorageAdapter } = require('./Auth/session/storage');
39
+ const storageAdapter = createStorageAdapter(
40
+ this.SESSION_STORAGE_TYPE,
41
+ this.SESSION_STORAGE_OPTIONS
42
+ );
43
+
44
+ const SessionManager = require('./SessionManager');
45
+ this.SESSION_STORAGE = new SessionManager(storageAdapter);
46
+
47
+ console.log(`✅ Session storage initialized: ${this.SESSION_STORAGE_TYPE}`);
48
+ } catch (error) {
49
+ console.error('❌ Failed to initialize session storage:', error.message);
50
+
51
+ // Fallback to memory storage
52
+ try {
53
+ const { MemoryStorage } = require('./Auth/session/storage');
54
+ const SessionManager = require('./Auth/SessionManager');
55
+ this.SESSION_STORAGE = new SessionManager(new MemoryStorage());
56
+ console.log('✅ Fallback to memory storage');
57
+ } catch (e) {
58
+ this.SESSION_STORAGE = null;
59
+ }
60
+ }
61
+ }
62
+
63
+ static throwIfUninitialized() {
64
+ if (!this.API_KEY || !this.API_SECRET_KEY) {
65
+ throw new Error('JoonWeb Context not initialized. Call Context::init() with api_key and api_secret.');
66
+ }
67
+ }
68
+
69
+ static isEmbeddedApp() {
70
+ return this.IS_EMBEDDED_APP;
71
+ }
72
+
73
+ // Get session storage instance
74
+ static getSessionStorage() {
75
+ if (!this.SESSION_STORAGE) {
76
+ throw new Error('Session storage not initialized. Call Context::init() first.');
77
+ }
78
+ return this.SESSION_STORAGE;
79
+ }
80
+ }
81
+
82
+ module.exports = Context;
package/src/Helper.js ADDED
@@ -0,0 +1,92 @@
1
+ const crypto = require('crypto');
2
+
3
+ class Helper {
4
+ /**
5
+ * Sanitize MyJoonWeb domain
6
+ */
7
+ static sanitizeDomain(domain) {
8
+ if (!domain) return null;
9
+
10
+ domain = domain.trim();
11
+
12
+ // Remove protocol
13
+ domain = domain.replace(/^https?:\/\//, '');
14
+
15
+ // Remove trailing slash
16
+ domain = domain.replace(/\/$/, '');
17
+
18
+ // Check if it's a JoonWeb domain
19
+ if (domain.match(/[a-zA-Z0-9][a-zA-Z0-9\-]*\.myjoonweb\.com/) ||
20
+ domain.match(/[a-zA-Z0-9][a-zA-Z0-9\-]*\.joonweb\.com/)) {
21
+ return domain;
22
+ }
23
+
24
+ // If it's just a site name, append .myjoonweb.com
25
+ if (domain.match(/^[a-zA-Z0-9][a-zA-Z0-9\-]*$/)) {
26
+ return `${domain}.myjoonweb.com`;
27
+ }
28
+
29
+ return null;
30
+ }
31
+
32
+ /**
33
+ * Verify HMAC
34
+ */
35
+ static verifyHmac(params, secret) {
36
+ const hmac = params.hmac;
37
+ if (!hmac) return false;
38
+
39
+ delete params.hmac;
40
+
41
+ // Sort keys
42
+ const sortedParams = Object.keys(params)
43
+ .sort()
44
+ .map(key => `${key}=${params[key]}`)
45
+ .join('&');
46
+
47
+ const calculatedHmac = crypto
48
+ .createHmac('sha256', secret)
49
+ .update(sortedParams)
50
+ .digest('hex');
51
+
52
+ return crypto.timingSafeEqual(
53
+ Buffer.from(hmac),
54
+ Buffer.from(calculatedHmac)
55
+ );
56
+ }
57
+
58
+ /**
59
+ * Generate OAuth redirect URL
60
+ */
61
+ static getAuthorizationUrl(site, scopes, redirectUri, apiKey, nonce = null) {
62
+ const state = nonce || crypto.randomBytes(16).toString('hex');
63
+ const scope = Array.isArray(scopes) ? scopes.join(',') : scopes;
64
+
65
+ const params = new URLSearchParams({
66
+ client_id: apiKey,
67
+ scope: scope,
68
+ redirect_uri: redirectUri,
69
+ state: state,
70
+ 'grant_options[]': 'per-user'
71
+ });
72
+
73
+ return {
74
+ url: `https://${site}/admin/oauth/authorize?${params.toString()}`,
75
+ state: state
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Decode JWT session token (for embedded apps)
81
+ */
82
+ static decodeSessionToken(token) {
83
+ try {
84
+ const json = Buffer.from(token, 'base64').toString('utf8');
85
+ return JSON.parse(json);
86
+ } catch (e) {
87
+ return null;
88
+ }
89
+ }
90
+ }
91
+
92
+ module.exports = Helper;
@@ -0,0 +1,81 @@
1
+ const Product = require('./Resources/Product');
2
+ const Order = require('./Resources/Order');
3
+ const Customer = require('./Resources/Customer');
4
+ const Webhook = require('./Resources/Webhook');
5
+ const Site = require('./Resources/Site');
6
+ const Theme = require('./Resources/Theme');
7
+
8
+ class JoonWebAPI {
9
+ constructor(accessToken = null, siteDomain = null) {
10
+ this.accessToken = accessToken;
11
+ this.siteDomain = siteDomain;
12
+
13
+ // Initialize resources
14
+ this.product = new Product(accessToken, siteDomain);
15
+ this.order = new Order(accessToken, siteDomain);
16
+ this.customer = new Customer(accessToken, siteDomain);
17
+ this.webhook = new Webhook(accessToken, siteDomain);
18
+ this.site = new Site(accessToken, siteDomain);
19
+ this.theme = new Theme(accessToken, siteDomain);
20
+ }
21
+
22
+ setAccessToken(token) {
23
+ this.accessToken = token;
24
+
25
+ // Update all resources
26
+ Object.values(this).forEach(resource => {
27
+ if (resource && typeof resource.setAccessToken === 'function') {
28
+ resource.setAccessToken(token);
29
+ }
30
+ });
31
+
32
+ return this;
33
+ }
34
+
35
+ setSiteDomain(domain) {
36
+ this.siteDomain = domain;
37
+
38
+ // Update all resources
39
+ Object.values(this).forEach(resource => {
40
+ if (resource && typeof resource.setSiteDomain === 'function') {
41
+ resource.setSiteDomain(domain);
42
+ }
43
+ });
44
+
45
+ return this;
46
+ }
47
+
48
+ /**
49
+ * Exchange authorization code for access token
50
+ */
51
+ async exchangeCodeForToken(code, siteDomain) {
52
+ const apiVersion = process.env.JOONWEB_API_VERSION || '26.0';
53
+ const url = `https://${siteDomain}/api/admin/${apiVersion}/oauth/access_token`;
54
+
55
+ // Use axios (already in dependencies)
56
+ const axios = require('axios');
57
+
58
+ try {
59
+ const response = await axios.post(url, {
60
+ client_id: process.env.JOONWEB_CLIENT_ID,
61
+ client_secret: process.env.JOONWEB_CLIENT_SECRET,
62
+ code: code
63
+ }, {
64
+ headers: {
65
+ 'Content-Type': 'application/json',
66
+ 'Accept': 'application/json'
67
+ }
68
+ });
69
+
70
+ return response.data;
71
+ } catch (error) {
72
+ if (error.response) {
73
+ throw new Error(`Token exchange failed: HTTP ${error.response.status} - ${JSON.stringify(error.response.data)}`);
74
+ } else {
75
+ throw new Error(`Token exchange failed: ${error.message}`);
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ module.exports = JoonWebAPI;
@@ -0,0 +1,32 @@
1
+ class BaseResource {
2
+ constructor(httpClient, resourcePath) {
3
+ this.httpClient = httpClient;
4
+ this.resourcePath = resourcePath;
5
+ }
6
+
7
+ async all(params = {}) {
8
+ return this.httpClient.get(this.resourcePath, params);
9
+ }
10
+
11
+ async get(id, params = {}) {
12
+ return this.httpClient.get(`${this.resourcePath}/${id}`, params);
13
+ }
14
+
15
+ async create(data) {
16
+ return this.httpClient.post(this.resourcePath, data);
17
+ }
18
+
19
+ async update(id, data) {
20
+ return this.httpClient.put(`${this.resourcePath}/${id}`, data);
21
+ }
22
+
23
+ async delete(id) {
24
+ return this.httpClient.delete(`${this.resourcePath}/${id}`);
25
+ }
26
+
27
+ async count(params = {}) {
28
+ return this.httpClient.get(`${this.resourcePath}/count`, params);
29
+ }
30
+ }
31
+
32
+ module.exports = BaseResource;
@@ -0,0 +1,34 @@
1
+ const BaseClient = require('../Clients/BaseClient');
2
+
3
+ class Customer extends BaseClient {
4
+
5
+ async all(params = {}) {
6
+ return this.request('/customers.json', 'GET', null, params);
7
+ }
8
+
9
+ async get(id) {
10
+ return this.request(`/customers/${id}.json`, 'GET');
11
+ }
12
+
13
+ async create(customerData) {
14
+ return this.request('/customers.json', 'POST', { customer: customerData });
15
+ }
16
+
17
+ async update(id, customerData) {
18
+ return this.request(`/customers/${id}.json`, 'PUT', { customer: customerData });
19
+ }
20
+
21
+ async delete(id) {
22
+ return this.request(`/customers/${id}.json`, 'DELETE');
23
+ }
24
+
25
+ async orders(id, params = {}) {
26
+ return this.request(`/customers/${id}/orders.json`, 'GET', null, params);
27
+ }
28
+
29
+ async addresses(id, params = {}) {
30
+ return this.request(`/customers/${id}/addresses.json`, 'GET', null, params);
31
+ }
32
+ }
33
+
34
+ module.exports = Customer;
@@ -0,0 +1,18 @@
1
+ const BaseClient = require('../Clients/BaseClient');
2
+
3
+ class Order extends BaseClient {
4
+
5
+ async all(params = {}) {
6
+ return this.request('/orders.json', 'GET', null, params);
7
+ }
8
+
9
+ async get(id) {
10
+ return this.request(`/orders/${id}.json`, 'GET');
11
+ }
12
+
13
+ async count(params = {}) {
14
+ return this.request('/orders/count.json', 'GET', null, params);
15
+ }
16
+ }
17
+
18
+ module.exports = Order;
@@ -0,0 +1,30 @@
1
+ const BaseClient = require('../Clients/BaseClient');
2
+
3
+ class Product extends BaseClient {
4
+
5
+ async all(params = {}) {
6
+ return this.request('/products.json', 'GET', null, params);
7
+ }
8
+
9
+ async get(id) {
10
+ return this.request(`/products/${id}.json`, 'GET');
11
+ }
12
+
13
+ async create(productData) {
14
+ return this.request('/products.json', 'POST', { product: productData });
15
+ }
16
+
17
+ async update(id, productData) {
18
+ return this.request(`/products/${id}.json`, 'PUT', { product: productData });
19
+ }
20
+
21
+ async delete(id) {
22
+ return this.request(`/products/${id}.json`, 'DELETE');
23
+ }
24
+
25
+ async count(params = {}) {
26
+ return this.request('/products/count.json', 'GET', null, params);
27
+ }
28
+ }
29
+
30
+ module.exports = Product;
@@ -0,0 +1,11 @@
1
+ const BaseClient = require('../Clients/BaseClient');
2
+
3
+ class Site extends BaseClient {
4
+
5
+ async get() {
6
+ // Just call the request method directly
7
+ return this.request('/site.json', 'GET');
8
+ }
9
+ }
10
+
11
+ module.exports = Site;
@@ -0,0 +1,7 @@
1
+ const BaseClient = require('../Clients/BaseClient');
2
+
3
+ class Theme extends BaseClient {
4
+
5
+ }
6
+
7
+ module.exports = Theme;
@@ -0,0 +1,30 @@
1
+ const BaseClient = require('../Clients/BaseClient');
2
+
3
+ class Webhook extends BaseClient {
4
+
5
+ async all() {
6
+ return this.get('/webhooks.json');
7
+ }
8
+
9
+ async create(webhookData) {
10
+ return this.post('/webhooks.json', { webhook: webhookData });
11
+ }
12
+
13
+ async delete(id) {
14
+ return this.delete(`/webhooks/${id}.json`);
15
+ }
16
+
17
+ async get(id) {
18
+ return this.get(`/webhooks/${id}.json`);
19
+ }
20
+
21
+ async update(id, webhookData) {
22
+ return this.put(`/webhooks/${id}.json`, { webhook: webhookData });
23
+ }
24
+
25
+ async count() {
26
+ return this.get('/webhooks/count.json');
27
+ }
28
+ }
29
+
30
+ module.exports = Webhook;