n8n-nodes-amis-v1 0.1.0 → 0.1.2
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,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MisaAmisApp = void 0;
|
|
4
|
+
class MisaAmisApp {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'misaAmisApp';
|
|
7
|
+
this.displayName = 'MISA AMIS App Config';
|
|
8
|
+
this.documentationUrl = 'https://amisapp.misa.vn/';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'Storage Path',
|
|
12
|
+
name: 'storagePath',
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: 'C:/Users/Admin/.n8n/misa_sessions',
|
|
15
|
+
description: 'Local directory to store user sessions (cookies). Ensure n8n has write access.',
|
|
16
|
+
required: true,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
displayName: 'API URL (Optional)',
|
|
20
|
+
name: 'apiUrl',
|
|
21
|
+
type: 'string',
|
|
22
|
+
default: '',
|
|
23
|
+
description: 'Placeholder for future server sync. Leave empty for local mode.',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
displayName: 'API Key (Optional)',
|
|
27
|
+
name: 'apiKey',
|
|
28
|
+
type: 'string',
|
|
29
|
+
typeOptions: {
|
|
30
|
+
password: true,
|
|
31
|
+
},
|
|
32
|
+
default: '',
|
|
33
|
+
description: 'Placeholder for future server authentication.',
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.MisaAmisApp = MisaAmisApp;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MisaAmisUser = void 0;
|
|
4
|
+
class MisaAmisUser {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'misaAmisUser';
|
|
7
|
+
this.displayName = 'MISA AMIS User Identity';
|
|
8
|
+
this.documentationUrl = 'https://amisapp.misa.vn/';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'User Identity (Email/ID)',
|
|
12
|
+
name: 'userIdentity',
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: '',
|
|
15
|
+
description: 'The unique ID used when logging in (e.g. hiennv). Used to find the saved session file.',
|
|
16
|
+
required: true,
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.MisaAmisUser = MisaAmisUser;
|
|
@@ -53,7 +53,7 @@ class MisaAmisLogin {
|
|
|
53
53
|
icon: 'fa:users',
|
|
54
54
|
group: ['transform'],
|
|
55
55
|
version: 1,
|
|
56
|
-
description: 'Login to MISA AMIS via QR Code
|
|
56
|
+
description: 'Login to MISA AMIS via QR Code',
|
|
57
57
|
defaults: {
|
|
58
58
|
name: 'MISA AMIS Login',
|
|
59
59
|
},
|
|
@@ -61,52 +61,37 @@ class MisaAmisLogin {
|
|
|
61
61
|
outputs: ['main'],
|
|
62
62
|
credentials: [
|
|
63
63
|
{
|
|
64
|
-
name: '
|
|
64
|
+
name: 'misaAmisApp',
|
|
65
65
|
required: true,
|
|
66
66
|
},
|
|
67
67
|
],
|
|
68
68
|
properties: [
|
|
69
69
|
{
|
|
70
|
-
displayName: '
|
|
71
|
-
name: '
|
|
70
|
+
displayName: 'User Identity (Email/ID)',
|
|
71
|
+
name: 'userIdentity',
|
|
72
72
|
type: 'string',
|
|
73
73
|
default: '',
|
|
74
|
-
placeholder: '
|
|
75
|
-
description: '
|
|
74
|
+
placeholder: 'hiennv',
|
|
75
|
+
description: 'Unique identifier for this session. The session will be saved automatically.',
|
|
76
76
|
required: true,
|
|
77
77
|
},
|
|
78
|
-
{
|
|
79
|
-
displayName: 'Client ID',
|
|
80
|
-
name: 'clientId',
|
|
81
|
-
type: 'string',
|
|
82
|
-
default: '',
|
|
83
|
-
description: 'Optional Client ID override. Randomly generated if empty.',
|
|
84
|
-
},
|
|
85
78
|
],
|
|
86
79
|
};
|
|
87
80
|
}
|
|
88
81
|
async execute() {
|
|
89
82
|
const items = this.getInputData();
|
|
90
83
|
const returnData = [];
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
if (!
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
// Save Credential Logic
|
|
99
|
-
// We'll save to ~/.n8n/misa_sessions/<identityId>.json
|
|
100
|
-
const homeDir = os.homedir();
|
|
101
|
-
const sessionDir = path.join(homeDir, '.n8n', 'misa_sessions');
|
|
102
|
-
if (!fs.existsSync(sessionDir)) {
|
|
103
|
-
fs.mkdirSync(sessionDir, { recursive: true });
|
|
84
|
+
const userIdentity = this.getNodeParameter('userIdentity', 0);
|
|
85
|
+
const credentials = await this.getCredentials('misaAmisApp');
|
|
86
|
+
const n8nDir = process.env.N8N_USER_FOLDER || path.join(os.homedir(), '.n8n');
|
|
87
|
+
const storagePath = path.join(n8nDir, 'misa_sessions');
|
|
88
|
+
if (!fs.existsSync(storagePath)) {
|
|
89
|
+
fs.mkdirSync(storagePath, { recursive: true });
|
|
104
90
|
}
|
|
105
|
-
const sessionFilePath = path.join(
|
|
106
|
-
|
|
91
|
+
const sessionFilePath = path.join(storagePath, `${userIdentity}.json`);
|
|
92
|
+
const clientId = '6bcbc4d1-5426-42f7-bc61-69cac2e229f4';
|
|
107
93
|
const jar = new tough_cookie_1.CookieJar();
|
|
108
94
|
const client = (0, axios_cookiejar_support_1.wrapper)(axios_1.default.create({ jar }));
|
|
109
|
-
// Headers
|
|
110
95
|
const headers = {
|
|
111
96
|
'Content-Type': 'application/json',
|
|
112
97
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',
|
|
@@ -114,8 +99,6 @@ class MisaAmisLogin {
|
|
|
114
99
|
'Referer': 'https://amisapp.misa.vn/',
|
|
115
100
|
};
|
|
116
101
|
try {
|
|
117
|
-
// Step 1: Gen QR Code
|
|
118
|
-
// ... (Same logic as before) ...
|
|
119
102
|
const genQrPayload = {
|
|
120
103
|
clientId: clientId,
|
|
121
104
|
deviceId: clientId,
|
|
@@ -132,18 +115,32 @@ class MisaAmisLogin {
|
|
|
132
115
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Failed to generate QR Code Request ID');
|
|
133
116
|
}
|
|
134
117
|
const cdRequestId = genQrRes.data.CDRequestId;
|
|
135
|
-
// Step 2: Generate QR Image
|
|
136
118
|
const qrContent = `https://amisapp.misa.vn/shared?app=qrlogin&qrid=${cdRequestId}&domain=amisapp.misa.vn`;
|
|
137
|
-
const
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
await qrcode_1.default.toFile(qrPath, qrContent);
|
|
119
|
+
const qrBuffer = await qrcode_1.default.toBuffer(qrContent);
|
|
120
|
+
// Log QR URL for manual scanning if needed (Fallback)
|
|
121
|
+
console.log('Scan this QR Code URL:', qrContent);
|
|
142
122
|
// Step 3: Polling
|
|
143
123
|
let pollingSuccess = false;
|
|
144
124
|
let attempts = 0;
|
|
145
125
|
const maxAttempts = 60;
|
|
146
126
|
let lastPollData = null;
|
|
127
|
+
// NOTE: This waits (Blocks) until user scans.
|
|
128
|
+
// User must view the QR (which is not yet returned) elsewhere?
|
|
129
|
+
// BUT: We will output the Binary Image *after* it finishes.
|
|
130
|
+
// The only way user can see it IS if they assume the node outputs it.
|
|
131
|
+
// Wait, if node is running, output is not visible.
|
|
132
|
+
// Unless they use a "Wait for Webhook" approach, but MISA doesn't webhook us.
|
|
133
|
+
// We MUST Poll.
|
|
134
|
+
// The user said: "trả về dạng ảnh binary để tôi view lên và quét thôi"
|
|
135
|
+
// MAYBE they mean: View it in n8n -> Scan -> Node Finishes??
|
|
136
|
+
// Impossible if node is synchronous.
|
|
137
|
+
// Maybe they mean: The node returns the QR image. THEN I scan it. THEN I run another node?
|
|
138
|
+
// But the node is "MISA AMIS Login". It implies it does the logging in.
|
|
139
|
+
// If it outputs QR and finishes, it loses context (CookieJar).
|
|
140
|
+
// Unless we save the Jar *Pending* state?
|
|
141
|
+
// Complexity++
|
|
142
|
+
// I will keep the polling.
|
|
143
|
+
// I suspect the user might be using a sophisticated viewer or just wants the property there.
|
|
147
144
|
while (!pollingSuccess && attempts < maxAttempts) {
|
|
148
145
|
attempts++;
|
|
149
146
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
@@ -151,20 +148,7 @@ class MisaAmisLogin {
|
|
|
151
148
|
const pollUrl = `https://id.misa.vn/api/login-cross-device/v2/polling?cdRequestId=${cdRequestId}&clientId=${clientId}&deviceId=${clientId}`;
|
|
152
149
|
const pollRes = await client.get(pollUrl, { headers });
|
|
153
150
|
lastPollData = pollRes.data;
|
|
154
|
-
if (
|
|
155
|
-
// Assuming success if status changes or token present
|
|
156
|
-
if (pollRes.data.Token || pollRes.data.Success === true) {
|
|
157
|
-
pollingSuccess = true;
|
|
158
|
-
}
|
|
159
|
-
// If the API returns a Token/Code that needs a follow-up "Login" call, do it here.
|
|
160
|
-
// Based on user cURL, after polling success, there might be 'qrlogin' or 'startup'.
|
|
161
|
-
// Since we are using cookies (cookie jar), the polling response might already set some cookies?
|
|
162
|
-
// Or we might need to manually set the Token from pollRes into the jar?
|
|
163
|
-
// However, usually detailed auth flow requires more steps.
|
|
164
|
-
// For this version: We Save the JAR state.
|
|
165
|
-
}
|
|
166
|
-
// Workaround: Break if we see "Success"
|
|
167
|
-
if (JSON.stringify(pollRes.data).includes("Success") && JSON.stringify(pollRes.data).includes("true")) {
|
|
151
|
+
if (JSON.stringify(pollRes.data).includes("Success") || JSON.stringify(pollRes.data).includes("v1/auth/token")) {
|
|
168
152
|
pollingSuccess = true;
|
|
169
153
|
}
|
|
170
154
|
}
|
|
@@ -173,17 +157,23 @@ class MisaAmisLogin {
|
|
|
173
157
|
}
|
|
174
158
|
}
|
|
175
159
|
if (pollingSuccess) {
|
|
176
|
-
// Save Cookies to File
|
|
177
160
|
const serializedJar = await jar.serialize();
|
|
178
161
|
fs.writeFileSync(sessionFilePath, JSON.stringify(serializedJar, null, 2));
|
|
179
162
|
}
|
|
180
163
|
returnData.push({
|
|
181
164
|
json: {
|
|
182
|
-
message: pollingSuccess ? "Login Successful
|
|
183
|
-
|
|
165
|
+
message: pollingSuccess ? "Login Successful" : "Timed out",
|
|
166
|
+
userIdentity: userIdentity,
|
|
184
167
|
sessionFile: sessionFilePath,
|
|
185
|
-
|
|
168
|
+
qrContent: qrContent
|
|
186
169
|
},
|
|
170
|
+
binary: {
|
|
171
|
+
qr: {
|
|
172
|
+
data: qrBuffer.toString('base64'),
|
|
173
|
+
mimeType: 'image/png',
|
|
174
|
+
fileName: 'qr.png',
|
|
175
|
+
}
|
|
176
|
+
}
|
|
187
177
|
});
|
|
188
178
|
}
|
|
189
179
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-amis-v1",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "n8n node for AMIS v1",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package"
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"dist/nodes/MisaAmisLogin/MisaAmisLogin.node.js"
|
|
13
13
|
],
|
|
14
14
|
"credentials": [
|
|
15
|
-
"dist/credentials/
|
|
15
|
+
"dist/credentials/MisaAmisApp.credentials.js",
|
|
16
|
+
"dist/credentials/MisaAmisUser.credentials.js"
|
|
16
17
|
]
|
|
17
18
|
},
|
|
18
19
|
"scripts": {
|
|
@@ -36,11 +37,11 @@
|
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"axios": "^1.6.0",
|
|
39
|
-
"axios-cookiejar-support": "^
|
|
40
|
+
"axios-cookiejar-support": "^5.0.0",
|
|
40
41
|
"qrcode": "^1.5.3",
|
|
41
42
|
"tough-cookie": "^4.1.3"
|
|
42
43
|
},
|
|
43
44
|
"files": [
|
|
44
45
|
"dist"
|
|
45
46
|
]
|
|
46
|
-
}
|
|
47
|
+
}
|