l-min-components 1.7.1314 → 1.7.1315
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/package.json +3 -2
- package/src/utils/authRoutes.js +3 -0
- package/src/utils/axiosConfig.js +131 -0
- package/src/utils/db.js +13 -0
- package/src/utils/deleteCookies.js +59 -0
- package/src/utils/test/translation.test.js +2 -0
- package/src/utils/translation.js +68 -0
- package/src/utils/webm2wav.js +81 -0
package/package.json
CHANGED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import Axios from "axios";
|
|
2
|
+
import { toast } from "react-toastify";
|
|
3
|
+
|
|
4
|
+
const pendingRequests = new Map();
|
|
5
|
+
|
|
6
|
+
const axios = Axios.create({
|
|
7
|
+
baseURL:
|
|
8
|
+
window.location.hostname.includes("staging") ||
|
|
9
|
+
window.location.hostname.includes("localhost")
|
|
10
|
+
? "https://dev-117782726-api.learngual.com"
|
|
11
|
+
: "https://api.learngual.com",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
axios.interceptors.request.use(
|
|
15
|
+
async (config) => {
|
|
16
|
+
// preventing duplicate requests >> starts
|
|
17
|
+
const requestKey = config.url;
|
|
18
|
+
|
|
19
|
+
if (pendingRequests.has(requestKey)) {
|
|
20
|
+
console.log("preventing::", requestKey);
|
|
21
|
+
return Promise.reject(new Axios.Cancel("Duplicate request"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const source = Axios.CancelToken.source();
|
|
25
|
+
pendingRequests.set(requestKey, source);
|
|
26
|
+
config.cancelToken = source.token;
|
|
27
|
+
// preventing duplicate requests ends >>
|
|
28
|
+
|
|
29
|
+
const cookies = document.cookie;
|
|
30
|
+
const cookieArray = cookies.split(";");
|
|
31
|
+
|
|
32
|
+
let token;
|
|
33
|
+
let defaultAccountID;
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < cookieArray.length; i++) {
|
|
36
|
+
const cookie = cookieArray[i].trim();
|
|
37
|
+
|
|
38
|
+
if (cookie.startsWith("access=")) {
|
|
39
|
+
token = cookie.substring("access=".length, cookie.length);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < cookieArray.length; i++) {
|
|
45
|
+
const cookie = cookieArray[i].trim();
|
|
46
|
+
if (cookie.startsWith("defaultAccountID=")) {
|
|
47
|
+
defaultAccountID = cookie.substring(
|
|
48
|
+
"defaultAccountID=".length,
|
|
49
|
+
cookie.length
|
|
50
|
+
);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Add additional logic to check if the current request path matches any authRoutes
|
|
56
|
+
// const currentPath = config.url;
|
|
57
|
+
// const matchedRoute = authRoutes.find((route) =>
|
|
58
|
+
// currentPath.includes(route)
|
|
59
|
+
// );
|
|
60
|
+
if (token) {
|
|
61
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
62
|
+
const date = new Date();
|
|
63
|
+
const params = {
|
|
64
|
+
// ...config.params,
|
|
65
|
+
// // _tz: date.getTimezoneOffset() / 60,
|
|
66
|
+
// _account: defaultAccountID,
|
|
67
|
+
// //
|
|
68
|
+
|
|
69
|
+
_account: defaultAccountID,
|
|
70
|
+
...config.params,
|
|
71
|
+
};
|
|
72
|
+
config.params = params;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return config;
|
|
76
|
+
},
|
|
77
|
+
(error) => {
|
|
78
|
+
return Promise.reject(error);
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// axios.interceptors.response.use(
|
|
83
|
+
// function (response) {
|
|
84
|
+
// // Any status code that lie within the range of 2xx cause this function to trigger
|
|
85
|
+
// // Do something with response data
|
|
86
|
+
// if (response.status === 403) {
|
|
87
|
+
// // Handle unauthorized access
|
|
88
|
+
// toast.error("Unauthorized");
|
|
89
|
+
// //console.log(response.status, "+++++++REPONSE ====");
|
|
90
|
+
// }
|
|
91
|
+
// return response;
|
|
92
|
+
// },
|
|
93
|
+
// function (error) {
|
|
94
|
+
// // Any status codes that fall outside the range of 2xx cause this function to trigger
|
|
95
|
+
// // Do something with response error
|
|
96
|
+
// return Promise.reject(error);
|
|
97
|
+
// }
|
|
98
|
+
// );
|
|
99
|
+
|
|
100
|
+
axios.interceptors.response.use(
|
|
101
|
+
(response) => {
|
|
102
|
+
pendingRequests.delete(response.config.url);
|
|
103
|
+
console.log("deleting", response.config.url);
|
|
104
|
+
if (
|
|
105
|
+
response.status === 207 &&
|
|
106
|
+
!window.location.host.includes("staging") &&
|
|
107
|
+
!window.location.host.includes("local")
|
|
108
|
+
) {
|
|
109
|
+
window.location.href = "/auth/account-type";
|
|
110
|
+
}
|
|
111
|
+
return response;
|
|
112
|
+
},
|
|
113
|
+
(err) => {
|
|
114
|
+
if (err) {
|
|
115
|
+
if (err.config) pendingRequests.delete(err.config.url);
|
|
116
|
+
|
|
117
|
+
// If it's not a cancellation error, handle it as usual
|
|
118
|
+
if (!Axios.isCancel(err)) {
|
|
119
|
+
if (err.response?.status === 403) {
|
|
120
|
+
toast.error(err?.response.data?.detail);
|
|
121
|
+
}
|
|
122
|
+
return err;
|
|
123
|
+
} else {
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
// return err;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
export default axios;
|
package/src/utils/db.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Dexie from "dexie";
|
|
2
|
+
import locale from "iso-639-1";
|
|
3
|
+
export const db = new Dexie("translations");
|
|
4
|
+
|
|
5
|
+
const languages = locale.getAllCodes();
|
|
6
|
+
db.version(2).stores({
|
|
7
|
+
kwarg: "&key, value, screenId",
|
|
8
|
+
...languages.reduce(
|
|
9
|
+
(x, y) => ({ ...x, [y]: "&key, value, screenId, updatedAt" }),
|
|
10
|
+
{}
|
|
11
|
+
),
|
|
12
|
+
words: "&text, screenId, updatedAt",
|
|
13
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import Cookies from "js-cookie";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Delete a single cookie for specific domain and path configuration
|
|
5
|
+
* @param {string} name - Cookie name to delete
|
|
6
|
+
* @param {Object} options - Cookie options (domain, expires, path)
|
|
7
|
+
*/
|
|
8
|
+
const deleteSingleCookie = (name, options) => {
|
|
9
|
+
Cookies.remove(name, options);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Delete cookies on domain and all subdomains
|
|
14
|
+
* @param {string|string[]} names - Cookie name or array of cookie names to delete
|
|
15
|
+
*/
|
|
16
|
+
const deleteCookies = (names) => {
|
|
17
|
+
// Convert single name to array for consistent handling
|
|
18
|
+
const cookieNames = Array.isArray(names) ? names : [names];
|
|
19
|
+
|
|
20
|
+
const date = new Date();
|
|
21
|
+
date.setHours(date.getHours() - 1);
|
|
22
|
+
const { host } = window.location;
|
|
23
|
+
const env = host.includes("localhost")
|
|
24
|
+
? "local"
|
|
25
|
+
: host.includes("test") || host.includes("staging")
|
|
26
|
+
? "staging"
|
|
27
|
+
: "production";
|
|
28
|
+
|
|
29
|
+
// Process each cookie name
|
|
30
|
+
cookieNames.forEach(name => {
|
|
31
|
+
if (env === "local") {
|
|
32
|
+
// For local environment
|
|
33
|
+
const localDomains = [".localhost", "localhost"];
|
|
34
|
+
localDomains.forEach(domain => {
|
|
35
|
+
deleteSingleCookie(name, {
|
|
36
|
+
domain,
|
|
37
|
+
expires: date,
|
|
38
|
+
path: "/",
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
// Also delete without domain specification for local testing
|
|
42
|
+
deleteSingleCookie(name, {
|
|
43
|
+
expires: date,
|
|
44
|
+
path: "/",
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
// For staging and production environments
|
|
48
|
+
const domain = ".learngual.com";
|
|
49
|
+
// Delete for main domain and all potential subdomains
|
|
50
|
+
deleteSingleCookie(name, {
|
|
51
|
+
domain,
|
|
52
|
+
expires: date,
|
|
53
|
+
path: "/",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default deleteCookies;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { db } from "./db";
|
|
2
|
+
import axios from "./axiosConfig";
|
|
3
|
+
|
|
4
|
+
async function loadTranslations(language = "ko") {
|
|
5
|
+
await new Promise((resolve) => {
|
|
6
|
+
//check if loading else resolve
|
|
7
|
+
if (document.readyState === "complete") {
|
|
8
|
+
return resolve();
|
|
9
|
+
}
|
|
10
|
+
window.addEventListener("load", resolve);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const screenId = btoa(window.location.href);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {{text: string; screenId: string; updatedAt: string}} WORDS
|
|
17
|
+
* @type {WORDS[]}
|
|
18
|
+
*/
|
|
19
|
+
const words = await db.words.where({ screenId }).toArray();
|
|
20
|
+
const texts = words.map((x) => x.text); // list of texts
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {{ key: string; value: string; screenId: string }} Kwarg
|
|
24
|
+
* @type {Kwarg[]} kwargList
|
|
25
|
+
*/
|
|
26
|
+
const kwargList = await db.kwarg.where({ screenId }).toArray();
|
|
27
|
+
const kwargs = kwargList.reduce((x, t) => ({ ...x, [t.key]: t.value }), {});
|
|
28
|
+
/**
|
|
29
|
+
* @type {{ data: {has_translated: boolean; result: string[]} }}
|
|
30
|
+
*/
|
|
31
|
+
const res = await axios({
|
|
32
|
+
url: "/iam/v1/utils/translate/",
|
|
33
|
+
method: "POST",
|
|
34
|
+
data: {
|
|
35
|
+
language,
|
|
36
|
+
sentences: texts,
|
|
37
|
+
kwargs,
|
|
38
|
+
},
|
|
39
|
+
params: {
|
|
40
|
+
_account: "",
|
|
41
|
+
},
|
|
42
|
+
authRequired: false,
|
|
43
|
+
});
|
|
44
|
+
// build an object using texts as key and results from request as value
|
|
45
|
+
if (!res.data) return {}; // somehow data was not found
|
|
46
|
+
const translations = texts.reduce(
|
|
47
|
+
(x, y, z) => ({ ...x, [y]: res.data.result[z] }),
|
|
48
|
+
{}
|
|
49
|
+
);
|
|
50
|
+
db[language].bulkPut(
|
|
51
|
+
Object.keys(translations).map((x, i) => ({
|
|
52
|
+
key: texts[i],
|
|
53
|
+
value: translations[x],
|
|
54
|
+
screenId,
|
|
55
|
+
updatedAt: new Date().toDateString(),
|
|
56
|
+
}))
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return translations;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function extractBracedText(text) {
|
|
63
|
+
const pattern = /\{([^}]+)\}/g; // Match all occurrences of text within {}
|
|
64
|
+
const matches = text.match(pattern);
|
|
65
|
+
return matches ? matches.map((match) => match.slice(1, -1)) : []; // Remove braces
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default loadTranslations;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
function writeString(view, offset, string) {
|
|
2
|
+
for (let i = 0; i < string.length; i++) {
|
|
3
|
+
view.setUint8(offset + i, string.charCodeAt(i));
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function audioBufferToWav(audioBuffer) {
|
|
8
|
+
const numChannels = audioBuffer.numberOfChannels;
|
|
9
|
+
const sampleRate = audioBuffer.sampleRate;
|
|
10
|
+
const format = 1;
|
|
11
|
+
const bitDepth = 16;
|
|
12
|
+
|
|
13
|
+
const bytesPerSample = bitDepth / 8;
|
|
14
|
+
const blockAlign = numChannels * bytesPerSample;
|
|
15
|
+
const byteRate = sampleRate * blockAlign;
|
|
16
|
+
|
|
17
|
+
const pcmChannels = [];
|
|
18
|
+
for (let i = 0; i < numChannels; i++) {
|
|
19
|
+
pcmChannels.push(audioBuffer.getChannelData(i));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const pcmDataLength = pcmChannels[0].length;
|
|
23
|
+
const dataSize = pcmDataLength * numChannels * bytesPerSample;
|
|
24
|
+
|
|
25
|
+
const interleavedPcm = new Int16Array(pcmDataLength * numChannels);
|
|
26
|
+
let outputOffset = 0;
|
|
27
|
+
for (let i = 0; i < pcmDataLength; i++) {
|
|
28
|
+
for (let channel = 0; channel < numChannels; channel++) {
|
|
29
|
+
let sample = pcmChannels[channel][i];
|
|
30
|
+
sample = Math.max(-1, Math.min(1, sample));
|
|
31
|
+
interleavedPcm[outputOffset++] =
|
|
32
|
+
sample < 0 ? sample * 0x8000 : sample * 0x7fff;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const buffer = new ArrayBuffer(44 + dataSize);
|
|
37
|
+
const view = new DataView(buffer);
|
|
38
|
+
|
|
39
|
+
writeString(view, 0, "RIFF");
|
|
40
|
+
view.setUint32(4, 36 + dataSize, true);
|
|
41
|
+
writeString(view, 8, "WAVE");
|
|
42
|
+
|
|
43
|
+
writeString(view, 12, "fmt ");
|
|
44
|
+
view.setUint32(16, 16, true);
|
|
45
|
+
view.setUint16(20, format, true);
|
|
46
|
+
view.setUint16(22, numChannels, true);
|
|
47
|
+
view.setUint32(24, sampleRate, true);
|
|
48
|
+
view.setUint32(28, byteRate, true);
|
|
49
|
+
view.setUint16(32, blockAlign, true);
|
|
50
|
+
view.setUint16(34, bitDepth, true);
|
|
51
|
+
|
|
52
|
+
writeString(view, 36, "data");
|
|
53
|
+
view.setUint32(40, dataSize, true);
|
|
54
|
+
|
|
55
|
+
new Int16Array(buffer, 44).set(interleavedPcm);
|
|
56
|
+
|
|
57
|
+
return new Blob([view], { type: "audio/wav" });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default async function convertBlobToWav(blob) {
|
|
61
|
+
if (!blob || blob.size === 0) {
|
|
62
|
+
throw new Error("Input blob is empty or null. Cannot convert to WAV.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let audioContext;
|
|
66
|
+
try {
|
|
67
|
+
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
68
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
69
|
+
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
|
|
70
|
+
const wavBlob = audioBufferToWav(audioBuffer);
|
|
71
|
+
|
|
72
|
+
return wavBlob;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error("Error during blob to WAV conversion:", error);
|
|
75
|
+
throw new Error(`Failed to convert blob to WAV: ${error.message}`);
|
|
76
|
+
} finally {
|
|
77
|
+
if (audioContext && audioContext.state !== "closed") {
|
|
78
|
+
await audioContext.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|