@rebuy/rebuy 1.0.0-rc.3
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 +1 -0
- package/api.js +59 -0
- package/client.js +104 -0
- package/cookie.js +153 -0
- package/geolocation.js +57 -0
- package/identity.js +66 -0
- package/package.json +36 -0
- package/session.js +46 -0
- package/utilities.js +341 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Rebuy
|
package/api.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { serialize } from './utilities.js';
|
|
2
|
+
|
|
3
|
+
const config = {
|
|
4
|
+
key: null,
|
|
5
|
+
domain: 'https://rebuyengine.com',
|
|
6
|
+
cdnDomain: 'https://cdn.rebuyengine.com',
|
|
7
|
+
eventDomain: 'https://rebuyengine.com',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const makeCall = async (method, path, data, origin) => {
|
|
11
|
+
const url = `${origin}${path}`;
|
|
12
|
+
const requestUrl = new URL(url);
|
|
13
|
+
|
|
14
|
+
const requestData = {
|
|
15
|
+
key: config.key,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
if (typeof data == 'object' && data != null) {
|
|
19
|
+
Object.assign(requestData, data);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const requestObject = {
|
|
23
|
+
method,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (method == 'GET') {
|
|
27
|
+
requestUrl.search = serialize(requestData);
|
|
28
|
+
} else if (method == 'POST') {
|
|
29
|
+
requestObject.headers = {
|
|
30
|
+
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
31
|
+
};
|
|
32
|
+
requestObject.body = new URLSearchParams(requestData);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const request = await fetch(requestUrl, requestObject);
|
|
36
|
+
return await request.json();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default class Api {
|
|
40
|
+
constructor(options) {
|
|
41
|
+
if (typeof options == 'string') {
|
|
42
|
+
config.key = options;
|
|
43
|
+
} else if (typeof options == 'object' && options != null) {
|
|
44
|
+
Object.assign(config, options);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async callEvent(method, path, data) {
|
|
49
|
+
return await makeCall(method, path, data, config.eventDomain);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async callCdn(method, path, data) {
|
|
53
|
+
return await makeCall(method, path, data, config.cdnDomain);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async callApi(method, path, data) {
|
|
57
|
+
return await makeCall(method, path, data, config.domain);
|
|
58
|
+
}
|
|
59
|
+
}
|
package/client.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { convertProductToStorefrontFormat } from './utilities.js';
|
|
2
|
+
import Identity from './identity.js';
|
|
3
|
+
import Api from './api.js';
|
|
4
|
+
|
|
5
|
+
const config = {
|
|
6
|
+
key: null,
|
|
7
|
+
defaultParameters: null,
|
|
8
|
+
contextParameters: null,
|
|
9
|
+
api: null,
|
|
10
|
+
identity: null,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const trackEvent = async (eventData) => {
|
|
14
|
+
if (config.identity.visitorId()) {
|
|
15
|
+
eventData.uuid = config.identity.visitorId();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return await config.api.callEvent('POST', '/api/v1/analytics/event', eventData);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const makeCall = async (endpoint, params, format) => {
|
|
22
|
+
const query = {};
|
|
23
|
+
|
|
24
|
+
if (config.defaultParameters != null) {
|
|
25
|
+
Object.assign(query, config.defaultParameters);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (config.contextParameters != null) {
|
|
29
|
+
Object.assign(query, config.contextParameters);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof params == 'object' && params != null) {
|
|
33
|
+
Object.assign(query, params);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (config.identity.visitorId()) {
|
|
37
|
+
query.uuid = config.identity.visitorId();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const response = await config.api.callApi('GET', endpoint, query);
|
|
41
|
+
|
|
42
|
+
if (response.data && format == 'storefront') {
|
|
43
|
+
for (let i = 0; i < response.data.length; i++) {
|
|
44
|
+
response.data[i] = convertProductToStorefrontFormat(response.data[i]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return response;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default class RebuyClient {
|
|
52
|
+
constructor(key, defaultParameters) {
|
|
53
|
+
if (typeof key == 'string') {
|
|
54
|
+
config.key = key;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof defaultParameters == 'object' && defaultParameters != null) {
|
|
58
|
+
config.defaultParameters = defaultParameters;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
config.api = new Api(config.key);
|
|
62
|
+
config.identity = new Identity();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setDefaultParameters(defaultParameters) {
|
|
66
|
+
if (typeof defaultParameters == 'object' && defaultParameters != null) {
|
|
67
|
+
config.defaultParameters = defaultParameters;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
setContextParameters(contextParameters) {
|
|
72
|
+
if (typeof contextParameters == 'object' && contextParameters != null) {
|
|
73
|
+
config.contextParameters = contextParameters;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async getData(endpoint, params) {
|
|
78
|
+
return await makeCall(endpoint, params);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async getStorefrontData(endpoint, params) {
|
|
82
|
+
return await makeCall(endpoint, params, 'storefront');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async trackProductViewed(data) {
|
|
86
|
+
const requiredKeys = ['shopify_product_id', 'shopify_product_handle'];
|
|
87
|
+
|
|
88
|
+
const defaultData = {
|
|
89
|
+
subject: 'user',
|
|
90
|
+
verb: 'viewed',
|
|
91
|
+
noun: 'product',
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (typeof data != 'undefined' && data != null) {
|
|
95
|
+
const dataKeys = Object.keys(data);
|
|
96
|
+
if (dataKeys.some((key) => requiredKeys.includes(key))) {
|
|
97
|
+
const payload = Object.assign(data, defaultData);
|
|
98
|
+
return await trackEvent(payload);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
package/cookie.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { dataToString, stringToData, isBase64Encoded } from './utilities.js';
|
|
2
|
+
|
|
3
|
+
export function get(name) {
|
|
4
|
+
if (typeof document == 'undefined') {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Get cookie
|
|
9
|
+
const cookie = document.cookie.match('(^|;) ?' + decodeURIComponent(name) + '=([^;]*)(;|$)');
|
|
10
|
+
|
|
11
|
+
let value = null;
|
|
12
|
+
|
|
13
|
+
if (cookie != null) {
|
|
14
|
+
// Get data
|
|
15
|
+
const data = decodeURIComponent(cookie[2]);
|
|
16
|
+
|
|
17
|
+
// Auto decode
|
|
18
|
+
const decode = isBase64Encoded(data) ? true : false;
|
|
19
|
+
|
|
20
|
+
// Convert to data object
|
|
21
|
+
value = stringToData(data, decode);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getAll() {
|
|
28
|
+
const cookies = {};
|
|
29
|
+
|
|
30
|
+
if (document && document.cookie && document.cookie != '') {
|
|
31
|
+
const split = document.cookie.split(';');
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < split.length; i++) {
|
|
34
|
+
const pairs = split[i].split('=');
|
|
35
|
+
|
|
36
|
+
pairs[0] = pairs[0].replace(/^ /, '');
|
|
37
|
+
|
|
38
|
+
const key = decodeURIComponent(pairs[0]);
|
|
39
|
+
const value = decodeURIComponent(pairs[1]);
|
|
40
|
+
|
|
41
|
+
// Auto decode
|
|
42
|
+
const decode = isBase64Encoded(value) ? true : false;
|
|
43
|
+
|
|
44
|
+
cookies[key] = stringToData(value, decode);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return cookies;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function set(name, value, config) {
|
|
52
|
+
if (typeof document == 'undefined') {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const attributes = ['path', 'domain', 'maxAge', 'expires', 'secure', 'sameSite'];
|
|
57
|
+
const convenienceTimes = ['seconds', 'minutes', 'hours', 'days', 'weeks', 'months', 'years'];
|
|
58
|
+
|
|
59
|
+
let cookieAttributes = {
|
|
60
|
+
path: '/',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (typeof config != 'undefined' && Number.isInteger(config)) {
|
|
64
|
+
cookieAttributes['max-age'] = config;
|
|
65
|
+
} else if (typeof config === 'object' && config !== null) {
|
|
66
|
+
for (let key in config) {
|
|
67
|
+
if (attributes.includes(key)) {
|
|
68
|
+
if (key == 'maxAge') {
|
|
69
|
+
cookieAttributes['max-age'] = config[key];
|
|
70
|
+
} else if (key == 'sameSite') {
|
|
71
|
+
cookieAttributes['samesite'] = config[key];
|
|
72
|
+
} else if (key == 'expires') {
|
|
73
|
+
cookieAttributes[key] = new Date(config[key]).toGMTString();
|
|
74
|
+
} else {
|
|
75
|
+
cookieAttributes[key] = config[key];
|
|
76
|
+
}
|
|
77
|
+
} else if (convenienceTimes.includes(key)) {
|
|
78
|
+
let duration = config[key];
|
|
79
|
+
|
|
80
|
+
if (key == 'seconds') {
|
|
81
|
+
duration = duration * 1;
|
|
82
|
+
} else if (key == 'minutes') {
|
|
83
|
+
duration = duration * 60;
|
|
84
|
+
} else if (key == 'hours') {
|
|
85
|
+
duration = duration * 60 * 60;
|
|
86
|
+
} else if (key == 'days') {
|
|
87
|
+
duration = duration * 60 * 60 * 24;
|
|
88
|
+
} else if (key == 'weeks') {
|
|
89
|
+
duration = duration * 60 * 60 * 24 * 7;
|
|
90
|
+
} else if (key == 'months') {
|
|
91
|
+
duration = duration * 60 * 60 * 24 * 30;
|
|
92
|
+
} else if (key == 'years') {
|
|
93
|
+
duration = duration * 60 * 60 * 24 * 365;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
cookieAttributes['max-age'] = duration;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Convert data to string
|
|
102
|
+
value = dataToString(value, config.encode);
|
|
103
|
+
|
|
104
|
+
// Define cookie
|
|
105
|
+
let cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value);
|
|
106
|
+
|
|
107
|
+
// Add optional cookie attributes
|
|
108
|
+
for (let key in cookieAttributes) {
|
|
109
|
+
cookie += ';' + key + '=' + cookieAttributes[key];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Write cookie
|
|
113
|
+
document.cookie = cookie;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function find(name) {
|
|
117
|
+
const matches = [];
|
|
118
|
+
const cookies = getAll();
|
|
119
|
+
|
|
120
|
+
for (let key in cookies) {
|
|
121
|
+
if (key.includes(name)) {
|
|
122
|
+
matches.push({
|
|
123
|
+
name: key,
|
|
124
|
+
value: cookies[key],
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return matches;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function destroy(name) {
|
|
133
|
+
set(name, '', { seconds: 0 });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function enabled() {
|
|
137
|
+
const test = {
|
|
138
|
+
key: '__cookie_test',
|
|
139
|
+
value: 1,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
set(test.key, test.value);
|
|
143
|
+
|
|
144
|
+
const enabled = get(test.key) == test.value ? true : false;
|
|
145
|
+
|
|
146
|
+
if (enabled) {
|
|
147
|
+
destroy(test.key);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return enabled;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export default { get, set, getAll, find, destroy, enabled };
|
package/geolocation.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import Cookie from './cookie.js';
|
|
2
|
+
import Api from './api.js';
|
|
3
|
+
|
|
4
|
+
const config = {
|
|
5
|
+
key: null,
|
|
6
|
+
geolocation: null,
|
|
7
|
+
geolocationCookie: '_rebuyGeolocation',
|
|
8
|
+
geolocationDuration: {
|
|
9
|
+
minutes: 30,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const getGeolocation = async () => {
|
|
14
|
+
const api = new Api(config.key);
|
|
15
|
+
const response = await api.callApi('GET', '/api/v1/customers/geolocation');
|
|
16
|
+
|
|
17
|
+
if (response.data) {
|
|
18
|
+
// Update config with geolocation
|
|
19
|
+
config.geolocation = response.data;
|
|
20
|
+
|
|
21
|
+
// Write cookie with geolocation
|
|
22
|
+
const cookieOptions = {
|
|
23
|
+
secure: true,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Merge cookie options with geolocation config
|
|
27
|
+
Object.assign(cookieOptions, config.geolocationDuration);
|
|
28
|
+
|
|
29
|
+
// Write cookie with geolocation
|
|
30
|
+
Cookie.set(config.geolocationCookie, config.geolocation, cookieOptions);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return config.geolocation;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default class Geolocation {
|
|
37
|
+
constructor(key) {
|
|
38
|
+
if (typeof key == 'string') {
|
|
39
|
+
config.key = key;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
config.geolocation = Cookie.get(config.geolocationCookie);
|
|
43
|
+
|
|
44
|
+
// Create a new geolocation (if needed)
|
|
45
|
+
if (config.geolocation === null) {
|
|
46
|
+
getGeolocation();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async geolocation() {
|
|
51
|
+
if (config.geolocation == null) {
|
|
52
|
+
await getGeolocation();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return config.geolocation;
|
|
56
|
+
}
|
|
57
|
+
}
|
package/identity.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { uuid } from './utilities.js';
|
|
2
|
+
import Session from './session.js';
|
|
3
|
+
import Cookie from './cookie.js';
|
|
4
|
+
import Geolocation from './geolocation.js';
|
|
5
|
+
|
|
6
|
+
const config = {
|
|
7
|
+
key: null,
|
|
8
|
+
visitorId: null,
|
|
9
|
+
visitorIdCookie: '_rebuyVisitorId',
|
|
10
|
+
visitorDuration: {
|
|
11
|
+
years: 1,
|
|
12
|
+
},
|
|
13
|
+
session: null,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default class Identity {
|
|
17
|
+
constructor(key) {
|
|
18
|
+
if (typeof key == 'string') {
|
|
19
|
+
config.key = key;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
config.visitorId = Cookie.get(config.visitorIdCookie);
|
|
23
|
+
|
|
24
|
+
// Create a new identifier (if needed)
|
|
25
|
+
if (config.visitorId === null) {
|
|
26
|
+
config.visitorId = uuid();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Write cookie with visitor ID
|
|
30
|
+
const cookieOptions = {
|
|
31
|
+
secure: true,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Merge cookie options with visitor config
|
|
35
|
+
Object.assign(cookieOptions, config.visitorDuration);
|
|
36
|
+
|
|
37
|
+
// Write cookie with session ID
|
|
38
|
+
Cookie.set(config.visitorIdCookie, config.visitorId, cookieOptions);
|
|
39
|
+
|
|
40
|
+
// Create visitor session
|
|
41
|
+
config.session = new Session();
|
|
42
|
+
|
|
43
|
+
// Create visitor geolocation
|
|
44
|
+
config.geolocation = new Geolocation(config.key);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
visitorId() {
|
|
48
|
+
return config.visitorId;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
sessionId() {
|
|
52
|
+
return config.session.sessionId();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
sessionStart() {
|
|
56
|
+
return config.session.sessionStart();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
sessionDuration() {
|
|
60
|
+
return config.session.sessionDuration();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async geolocation() {
|
|
64
|
+
return await config.geolocation.geolocation();
|
|
65
|
+
}
|
|
66
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rebuy/rebuy",
|
|
3
|
+
"description": "This is the default library for Rebuy",
|
|
4
|
+
"version": "1.0.0-rc.3",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Rebuy, Inc.",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"**/*.js"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"lint": "eslint .",
|
|
16
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+ssh://git@bitbucket.org/rebuyengine/npm-rebuy.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://bitbucket.org/rebuyengine/npm-rebuy/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://bitbucket.org/rebuyengine/npm-rebuy#readme",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@semantic-release/changelog": "^6.0.1",
|
|
28
|
+
"@semantic-release/commit-analyzer": "^9.0.2",
|
|
29
|
+
"@semantic-release/git": "^10.0.1",
|
|
30
|
+
"@semantic-release/npm": "^9.0.1",
|
|
31
|
+
"@semantic-release/release-notes-generator": "^10.0.3",
|
|
32
|
+
"conventional-changelog-eslint": "^3.0.9",
|
|
33
|
+
"eslint": "^8.17.0",
|
|
34
|
+
"semantic-release": "^19.0.3"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/session.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { sessionId } from './utilities.js';
|
|
2
|
+
import Cookie from './cookie.js';
|
|
3
|
+
|
|
4
|
+
const config = {
|
|
5
|
+
now: null,
|
|
6
|
+
sessionId: null,
|
|
7
|
+
sessionIdCookie: '_rebuySessionId',
|
|
8
|
+
sessionDuration: {
|
|
9
|
+
minutes: 30,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default class Session {
|
|
14
|
+
constructor() {
|
|
15
|
+
config.now = new Date().getTime();
|
|
16
|
+
config.sessionId = Cookie.get(config.sessionIdCookie);
|
|
17
|
+
|
|
18
|
+
// Create a new session (if needed)
|
|
19
|
+
if (config.sessionId === null) {
|
|
20
|
+
config.sessionId = sessionId();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Write cookie with session ID
|
|
24
|
+
const cookieOptions = {
|
|
25
|
+
secure: true,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Merge cookie options with session config
|
|
29
|
+
Object.assign(cookieOptions, config.sessionDuration);
|
|
30
|
+
|
|
31
|
+
// Write cookie with session ID
|
|
32
|
+
Cookie.set(config.sessionIdCookie, config.sessionId, cookieOptions);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
sessionId() {
|
|
36
|
+
return config.sessionId;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
sessionStart() {
|
|
40
|
+
return Number(config.sessionId.split('.')[1]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
sessionDuration() {
|
|
44
|
+
return parseInt((config.now - this.sessionStart()) / 1000 / 60);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/utilities.js
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
export function isBase64Encoded(str) {
|
|
2
|
+
const base64Regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;
|
|
3
|
+
return base64Regex.test(str) ? true : false;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function stringToData(data, decode) {
|
|
7
|
+
let response = data;
|
|
8
|
+
|
|
9
|
+
// Base64 decode
|
|
10
|
+
if (decode == true) {
|
|
11
|
+
response = atob(response);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
response = JSON.parse(response);
|
|
16
|
+
} catch (e) {
|
|
17
|
+
// failure to parse cookie
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return response;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function dataToString(data, encode) {
|
|
24
|
+
let response = data;
|
|
25
|
+
|
|
26
|
+
if (typeof response != 'string') {
|
|
27
|
+
response = JSON.stringify(response);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Base64 encode
|
|
31
|
+
if (encode == true) {
|
|
32
|
+
response = btoa(response);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return response;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function stripHtml(str) {
|
|
39
|
+
return str.replace(/<(.|\n)*?>/g, '');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getIdFromGraphUrl(graphId, objectType) {
|
|
43
|
+
let id = null;
|
|
44
|
+
|
|
45
|
+
const regex = new RegExp(`gid://shopify/${objectType}/(.*)`);
|
|
46
|
+
const matches = graphId.match(regex);
|
|
47
|
+
|
|
48
|
+
if (matches != null) {
|
|
49
|
+
id = matches[1];
|
|
50
|
+
|
|
51
|
+
if (!isNaN(id)) {
|
|
52
|
+
id = Number(id);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return id;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function variantAvailable(variant) {
|
|
60
|
+
return !(variant.inventory_management && variant.inventory_policy == 'deny' && variant.inventory_quantity <= 0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function firstAvailableVariant(product) {
|
|
64
|
+
// Select first variant
|
|
65
|
+
let selectedVariant = product.variants[0];
|
|
66
|
+
|
|
67
|
+
// Upgrade to first available
|
|
68
|
+
for (let i = 0; i < product.variants.length; i++) {
|
|
69
|
+
if (variantAvailable(product.variants[i])) {
|
|
70
|
+
selectedVariant = product.variants[i];
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return selectedVariant;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function uuid() {
|
|
79
|
+
let d = new Date().getTime();
|
|
80
|
+
let d2 = (performance && performance.now && performance.now() * 1000) || 0;
|
|
81
|
+
|
|
82
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
83
|
+
let r = Math.random() * 16;
|
|
84
|
+
if (d > 0) {
|
|
85
|
+
r = (d + r) % 16 | 0;
|
|
86
|
+
d = Math.floor(d / 16);
|
|
87
|
+
} else {
|
|
88
|
+
r = (d2 + r) % 16 | 0;
|
|
89
|
+
d2 = Math.floor(d2 / 16);
|
|
90
|
+
}
|
|
91
|
+
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function sessionId() {
|
|
96
|
+
const config = {
|
|
97
|
+
chars: '0123456789',
|
|
98
|
+
length: 12,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
let sessionId = '';
|
|
102
|
+
let sessionStart = new Date().getTime();
|
|
103
|
+
|
|
104
|
+
for (let i = config.length; i > 0; i--) {
|
|
105
|
+
sessionId += config.chars[Math.floor(Math.random() * config.chars.length)];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return `REBUY.${sessionStart}.${sessionId}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function convertProductToStorefrontFormat(product) {
|
|
112
|
+
let defaultVariant = firstAvailableVariant(product);
|
|
113
|
+
let minVariantPrice = variantMinMaxPriceObject(product, 'price', 'min');
|
|
114
|
+
let maxVariantPrice = variantMinMaxPriceObject(product, 'price', 'max');
|
|
115
|
+
let minVariantCompareAtPrice = variantMinMaxPriceObject(product, 'compare_at_price', 'min');
|
|
116
|
+
let maxVariantCompareAtPrice = variantMinMaxPriceObject(product, 'compare_at_price', 'max');
|
|
117
|
+
let selectedOptions = selectedVariantOptions(product, defaultVariant);
|
|
118
|
+
|
|
119
|
+
let variants = product.variants.map((variant) => convertVariantToStorefrontFormat(product, variant));
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
id: `gid://shopify/Product/${product.id}`,
|
|
123
|
+
title: product.title,
|
|
124
|
+
handle: product.handle,
|
|
125
|
+
description: stripHtml(product.body_html),
|
|
126
|
+
descriptionHtml: product.body_html,
|
|
127
|
+
vendor: product.vendor,
|
|
128
|
+
featuredImage: productImageObject(product),
|
|
129
|
+
options: [
|
|
130
|
+
...product.options.map((option) => {
|
|
131
|
+
return { name: option.name, values: option.values };
|
|
132
|
+
}),
|
|
133
|
+
],
|
|
134
|
+
selectedOptions,
|
|
135
|
+
priceRange: {
|
|
136
|
+
minVariantPrice,
|
|
137
|
+
maxVariantPrice,
|
|
138
|
+
},
|
|
139
|
+
compareAtPriceRange: {
|
|
140
|
+
minVariantCompareAtPrice,
|
|
141
|
+
maxVariantCompareAtPrice,
|
|
142
|
+
},
|
|
143
|
+
variants: convertToNodes(variants),
|
|
144
|
+
images: null,
|
|
145
|
+
media: [],
|
|
146
|
+
metafields: [],
|
|
147
|
+
collections: null,
|
|
148
|
+
selectedSellingPlan: null,
|
|
149
|
+
selectedSellingPlanAllocation: null,
|
|
150
|
+
sellingPlanGroups: [],
|
|
151
|
+
seo: { title: null, description: null },
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function convertToNodes(arr) {
|
|
156
|
+
return {
|
|
157
|
+
edges: [
|
|
158
|
+
...arr.map((node) => {
|
|
159
|
+
return { node };
|
|
160
|
+
}),
|
|
161
|
+
],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function convertVariantToStorefrontFormat(product, variant) {
|
|
166
|
+
let selectedOptions = selectedVariantOptions(product, variant);
|
|
167
|
+
let image = productImageObject(product, variant.image_id);
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
id: `gid://shopify/ProductVariant/${variant.id}`,
|
|
171
|
+
availableForSale: variantAvailable(variant),
|
|
172
|
+
priceV2: variantPriceObject(variant, 'price'),
|
|
173
|
+
compareAtPriceV2: variantPriceObject(variant, 'compare_at_price'),
|
|
174
|
+
image: image,
|
|
175
|
+
selectedOptions: [selectedOptions],
|
|
176
|
+
sku: variant.sku,
|
|
177
|
+
title: variant.title,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function variantMinMaxPriceObject(product, key = 'price', operator = 'min') {
|
|
182
|
+
let priceObject = variantPriceObject(product.variants[0], key);
|
|
183
|
+
|
|
184
|
+
for (let i = 0; i < product.variants.length; i++) {
|
|
185
|
+
const variantPrice = variantPriceObject(product.variants[i], key);
|
|
186
|
+
|
|
187
|
+
if (variantPrice != null) {
|
|
188
|
+
const variantPriceAmount = Number(variantPrice.amount);
|
|
189
|
+
const currentPriceAmount = priceObject != null ? Number(priceObject.amount) : null;
|
|
190
|
+
|
|
191
|
+
if (
|
|
192
|
+
currentPriceAmount == null ||
|
|
193
|
+
(operator == 'min' && variantPriceAmount < currentPriceAmount) ||
|
|
194
|
+
(operator == 'max' && variantPriceAmount > currentPriceAmount)
|
|
195
|
+
) {
|
|
196
|
+
priceObject = variantPrice;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return priceObject;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function variantPriceObject(variant, key = 'price') {
|
|
205
|
+
if (variant[key] != null) {
|
|
206
|
+
return {
|
|
207
|
+
amount: variant[key],
|
|
208
|
+
currencyCode: 'USD',
|
|
209
|
+
};
|
|
210
|
+
} else {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function selectedVariantOptions(product, variant) {
|
|
216
|
+
let selectedOptions = {};
|
|
217
|
+
|
|
218
|
+
if (variant.option1 != null) {
|
|
219
|
+
selectedOptions[product.options[0].name] = variant.option1;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (variant.option2 != null) {
|
|
223
|
+
selectedOptions[product.options[1].name] = variant.option2;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (variant.option3 != null) {
|
|
227
|
+
selectedOptions[product.options[2].name] = variant.option3;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return selectedOptions;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function productImageObject(product, id) {
|
|
234
|
+
let image = null;
|
|
235
|
+
|
|
236
|
+
if (product.image) {
|
|
237
|
+
image = {
|
|
238
|
+
id: `gid://shopify/ProductImage/${product.image.id}`,
|
|
239
|
+
url: product.image.src,
|
|
240
|
+
altText: product.image.alt,
|
|
241
|
+
width: product.image.width,
|
|
242
|
+
height: product.image.height,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (product.images && product.images.length > 0 && id != null) {
|
|
247
|
+
const matchingImage = product.images.find((i) => i.id == id);
|
|
248
|
+
|
|
249
|
+
if (matchingImage) {
|
|
250
|
+
image = {
|
|
251
|
+
id: `gid://shopify/ProductImage/${matchingImage.id}`,
|
|
252
|
+
url: matchingImage.src,
|
|
253
|
+
altText: matchingImage.alt,
|
|
254
|
+
width: matchingImage.width,
|
|
255
|
+
height: matchingImage.height,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return image;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function queryStringToObject(str) {
|
|
264
|
+
const params = new URLSearchParams(str);
|
|
265
|
+
return Object.fromEntries(params.entries());
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function utmObjectFromString(str) {
|
|
269
|
+
const utmKeys = ['utm_campaign', 'utm_medium', 'utm_source', 'utm_term', 'utm_content'];
|
|
270
|
+
|
|
271
|
+
const matches = {};
|
|
272
|
+
|
|
273
|
+
const url = new URL(str);
|
|
274
|
+
const queryObject = queryStringToObject(url.search);
|
|
275
|
+
|
|
276
|
+
for (const [key, value] of Object.entries(queryObject)) {
|
|
277
|
+
if (utmKeys.includes(key)) {
|
|
278
|
+
matches[key] = value;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return Object.keys(matches).length > 0 ? matches : null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function amountToCents(amount) {
|
|
286
|
+
if (isNaN(amount)) {
|
|
287
|
+
amount = 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (typeof amount != 'string') {
|
|
291
|
+
amount = amount.toString();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (amount.indexOf('.') != -1) {
|
|
295
|
+
amount = parseFloat(amount).toFixed(2) * 100;
|
|
296
|
+
} else {
|
|
297
|
+
amount = parseInt(amount);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return parseInt(amount);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function serialize(obj) {
|
|
304
|
+
const serialized = [];
|
|
305
|
+
|
|
306
|
+
const add = (key, value) => {
|
|
307
|
+
value = typeof value === 'function' ? value() : value;
|
|
308
|
+
value = value === null ? '' : value === undefined ? '' : value;
|
|
309
|
+
serialized[serialized.length] = encodeURIComponent(key) + '=' + encodeURIComponent(value);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const buildParameters = (prefix, obj) => {
|
|
313
|
+
let i, len, key;
|
|
314
|
+
|
|
315
|
+
if (prefix) {
|
|
316
|
+
if (Array.isArray(obj)) {
|
|
317
|
+
for (i = 0, len = obj.length; i < len; i++) {
|
|
318
|
+
buildParameters(prefix + '[' + (typeof obj[i] === 'object' && obj[i] ? i : '') + ']', obj[i]);
|
|
319
|
+
}
|
|
320
|
+
} else if (Object.prototype.toString.call(obj) === '[object Object]') {
|
|
321
|
+
for (key in obj) {
|
|
322
|
+
buildParameters(prefix + '[' + key + ']', obj[key]);
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
add(prefix, obj);
|
|
326
|
+
}
|
|
327
|
+
} else if (Array.isArray(obj)) {
|
|
328
|
+
for (i = 0, len = obj.length; i < len; i++) {
|
|
329
|
+
add(obj[i].name, obj[i].value);
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
for (key in obj) {
|
|
333
|
+
buildParameters(key, obj[key]);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return serialized;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
return buildParameters('', obj).join('&');
|
|
341
|
+
}
|