thunderbird-account-qr-code 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.
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/index.js +232 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Martin Giger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Thunderbird account QR code
|
|
2
|
+
[](https://codecov.io/gh/freaktechnik/thunderbird-account-qr-code)
|
|
3
|
+
|
|
4
|
+
Thunderbird for Android allows importing accounts through a QR code. This allows easy transmission of account configuration details. The goal of this library is to help with generating such QR codes by formatting the information as required by the [format for Thunderbird for Android](https://github.com/thunderbird/thunderbird-android/blob/8c2c036d36344f56128fcad77a5f355336ad67cc/feature/migration/qrcode/qr-code-format.md).
|
|
5
|
+
|
|
6
|
+
It does, however, not include any actual QR generation capabilities, you can choose almost any library that can encode an arbitrary string as data. Note that you might have to appropriately encode the string first, so UTF-8 code points are properly transmitted.
|
|
7
|
+
|
|
8
|
+
This implementation is independent from the [one in Thunderbird desktop](https://searchfox.org/comm-central/rev/bb6559a0db8b168f98d60ec91ec5f22f041f2b14/mail/modules/QRExport.sys.mjs) and might behave differently in some cases.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
The library is published on npm, but it is also self-contained so you can also include `index.js` in your project directly. Make sure to preserve licensing information.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
To format the data, use the method `encodeAccounts` and the constants provided by the module. There is fairly thorough JS Doc explaining the usage.
|
|
17
|
+
|
|
18
|
+
### Example
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
import {
|
|
22
|
+
encodeAccounts,
|
|
23
|
+
INCOMING_PROTOCOL,
|
|
24
|
+
CONNECTION_SECURITY,
|
|
25
|
+
AUTHENTICATION_TYPE,
|
|
26
|
+
} from "thunderbird-account-qr-code";
|
|
27
|
+
const qrInput = encodeAccounts([ {
|
|
28
|
+
incomingProtocol: INCOMING_PROTOCOL.IMAP,
|
|
29
|
+
incomingHostname: "imap.example.com",
|
|
30
|
+
incomingPort: 993,
|
|
31
|
+
incomingConnectionSecurity: CONNECTION_SECURITY.Tls,
|
|
32
|
+
incomingAuthenticationType: AUTHENTICATION_TYPE.PasswordCleartext,
|
|
33
|
+
incomingUsername: "email@example.com",
|
|
34
|
+
outgoingHostname: "smtp.example.com",
|
|
35
|
+
outgoingPort: 465,
|
|
36
|
+
outgoingConnectionSecurity: CONNECTION_SECURITY.Tls,
|
|
37
|
+
outgoingAuthenticationType: AUTHENTICATION_TYPE.PasswordCleartext,
|
|
38
|
+
outgoingUsername: "email@example.com",
|
|
39
|
+
identityEmailAddress: "email@example.com",
|
|
40
|
+
identityDisplayName: "Example account config",
|
|
41
|
+
} ]);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Example usage
|
|
45
|
+
|
|
46
|
+
You can find the example provided in the `exmample` directory hosted at https://lab.humanoids.be/thunderbird-account-qr-code/example.
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
This library is licensed under the [MIT License](./LICENSE).
|
package/index.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This type encodes a subset of the allowed data for the QR code. If you need a
|
|
3
|
+
* more complex structure that is supported by the format, this library is not
|
|
4
|
+
* for you. All properties are required unless otherwise noted.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {object} AccountInfo
|
|
7
|
+
* @property {IncomingProtocolType} incomingProtocol - The incoming server
|
|
8
|
+
* protocol.
|
|
9
|
+
* @property {string} incomingHostname - The host name of the incoming server.
|
|
10
|
+
* @property {number} incomingPort - The port of the incoming server.
|
|
11
|
+
* @property {ConnectionSecurity} incomingConnectionSecurity - The socket
|
|
12
|
+
* security method of the incoming server.
|
|
13
|
+
* @property {AuthenticationType} incomingAuthenticationType - The
|
|
14
|
+
* authentication method of the incoming server.
|
|
15
|
+
* @property {string} incomingUsername - Username to use on the incoming server.
|
|
16
|
+
* @property {?string} incomingAccountName - Account name for identifying the
|
|
17
|
+
* incoming server configuration. If fully omitted, the password must also be
|
|
18
|
+
* omitted and the email address will be used as account name. A null or empty
|
|
19
|
+
* string as value still allows providing a password.
|
|
20
|
+
* @property {?string} incomingPassword - If provided, the password for the
|
|
21
|
+
* incoming server. If omitted, the client will ask for the password after
|
|
22
|
+
* import.
|
|
23
|
+
* @property {?"smtp"} outgoingProtocol - The outgoing protocol type. Can be
|
|
24
|
+
* omitted, since it will always be SMTP.
|
|
25
|
+
* @property {string} outgoingHostname - The host name of the outgoing server.
|
|
26
|
+
* @property {number} outgoingPort - The port of the outgoing server.
|
|
27
|
+
* @property {ConnectionSecurity} outgoingConnectionSecurity - The socket
|
|
28
|
+
* connection security of the outgoing server.
|
|
29
|
+
* @property {AuthenticationType} outgoingAuthenticationType - The
|
|
30
|
+
* authentication method of the outgoing server.
|
|
31
|
+
* @property {string} outgoingUsername - The username for the outgoing server.
|
|
32
|
+
* @property {?string} outgoingPassword - The password for the outgoing server.
|
|
33
|
+
* If not provided, the client will ask for a password after import.
|
|
34
|
+
* @property {string} identityEmailAddress - The email address to use for
|
|
35
|
+
* outgoing messages.
|
|
36
|
+
* @property {string} identityDisplayName - The name to use in outgoing
|
|
37
|
+
* messages.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const FORMAT_VERSION = 1,
|
|
41
|
+
OUTGOING_PROTOCOL_SMTP = 0,
|
|
42
|
+
DEFAULT_SEQUENCE_POSITION = 1,
|
|
43
|
+
DEFAULT_SEQUENCE_TOTAL = 1;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {number} IncomingProtocolType
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @enum {IncomingProtocolType}
|
|
51
|
+
*/
|
|
52
|
+
export const INCOMING_PROTOCOL = {
|
|
53
|
+
IMAP: 0,
|
|
54
|
+
POP3: 1,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @typedef {number} ConnectionSecurity
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @enum {ConnectionSecurity}
|
|
63
|
+
*/
|
|
64
|
+
export const CONNECTION_SECURITY = {
|
|
65
|
+
Plain: 0,
|
|
66
|
+
AlwaysStartTls: 2,
|
|
67
|
+
Tls: 3,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @typedef {number} AuthenticationType
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @enum {AuthenticationType}
|
|
76
|
+
*/
|
|
77
|
+
export const AUTHENTICATION_TYPE = {
|
|
78
|
+
None: 0,
|
|
79
|
+
PasswordCleartext: 1,
|
|
80
|
+
PasswordEncrypted: 2,
|
|
81
|
+
Gssapi: 3,
|
|
82
|
+
Ntlm: 4,
|
|
83
|
+
TlsCertificate: 5,
|
|
84
|
+
OAuth2: 6,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Generate the miscellaneous data element.
|
|
89
|
+
*
|
|
90
|
+
* @param {number} sequenceNumber - 1-based index of the QR code.
|
|
91
|
+
* @param {number} sequenceEnd - Total number of QR codes.
|
|
92
|
+
* @returns {number[]}
|
|
93
|
+
*/
|
|
94
|
+
function getMiscellaneousData(sequenceNumber, sequenceEnd) {
|
|
95
|
+
return [
|
|
96
|
+
sequenceNumber,
|
|
97
|
+
sequenceEnd,
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Optionally add a set of items to an array. Useful to spread into an array
|
|
103
|
+
* literal.
|
|
104
|
+
*
|
|
105
|
+
* @param {boolean} condition - A value to check if items should be included.
|
|
106
|
+
* @param {any[]} items - The items to include.
|
|
107
|
+
* @returns {any[]} An empty array if the condition is false, else the items.
|
|
108
|
+
*/
|
|
109
|
+
function optionalArrayItems(condition, items) {
|
|
110
|
+
if(!condition) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
return items;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isEmptyValue(value) {
|
|
117
|
+
return !value && (value === null || value === "");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Build the IncomingServer element.
|
|
122
|
+
*
|
|
123
|
+
* @param {AccountInfo} accountInfo - The account information.
|
|
124
|
+
* @returns {any[]}
|
|
125
|
+
*/
|
|
126
|
+
function getIncomingServer(accountInfo) {
|
|
127
|
+
return [
|
|
128
|
+
accountInfo.incomingProtocol,
|
|
129
|
+
accountInfo.incomingHostname,
|
|
130
|
+
accountInfo.incomingPort,
|
|
131
|
+
accountInfo.incomingConnectionSecurity,
|
|
132
|
+
accountInfo.incomingAuthenticationType,
|
|
133
|
+
accountInfo.incomingUsername,
|
|
134
|
+
...optionalArrayItems(accountInfo.incomingAccountName || isEmptyValue(accountInfo.incomingAccountName), [
|
|
135
|
+
accountInfo.incomingAccountName,
|
|
136
|
+
...optionalArrayItems(accountInfo.incomingPassword, [ accountInfo.incomingPassword ]),
|
|
137
|
+
]),
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Build an OutgoingServer element.
|
|
143
|
+
*
|
|
144
|
+
* @param {AccountInfo} accountInfo - The account information.
|
|
145
|
+
* @returns {any[]}
|
|
146
|
+
*/
|
|
147
|
+
function getOutgoingServer(accountInfo) {
|
|
148
|
+
return [
|
|
149
|
+
OUTGOING_PROTOCOL_SMTP,
|
|
150
|
+
accountInfo.outgoingHostname,
|
|
151
|
+
accountInfo.outgoingPort,
|
|
152
|
+
accountInfo.outgoingConnectionSecurity,
|
|
153
|
+
accountInfo.outgoingAuthenticationType,
|
|
154
|
+
accountInfo.outgoingUsername,
|
|
155
|
+
...optionalArrayItems(accountInfo.outgoingPassword, [ accountInfo.outgoingPassword ]),
|
|
156
|
+
];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Build an Identity element.
|
|
161
|
+
*
|
|
162
|
+
* @param {AccountInfo} accountInfo - The account information.
|
|
163
|
+
* @returns {string[]}
|
|
164
|
+
*/
|
|
165
|
+
function getIdentity(accountInfo) {
|
|
166
|
+
return [
|
|
167
|
+
accountInfo.identityEmailAddress,
|
|
168
|
+
accountInfo.identityDisplayName,
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Build an OutgoingServerGroups element.
|
|
174
|
+
*
|
|
175
|
+
* @param {AccountInfo} accountInfo - The account information.
|
|
176
|
+
* @returns {any[]}
|
|
177
|
+
*/
|
|
178
|
+
function getOutgoingServerGroups(accountInfo) {
|
|
179
|
+
return [ [
|
|
180
|
+
getOutgoingServer(accountInfo),
|
|
181
|
+
getIdentity(accountInfo),
|
|
182
|
+
] ];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get the two arrays that make up a single email account.
|
|
187
|
+
*
|
|
188
|
+
* @param {AccountInfo} accountInfo - The account information.
|
|
189
|
+
* @returns {any[]}
|
|
190
|
+
*/
|
|
191
|
+
function getAccountArrays(accountInfo) {
|
|
192
|
+
return [
|
|
193
|
+
getIncomingServer(accountInfo),
|
|
194
|
+
getOutgoingServerGroups(accountInfo),
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* eslint-disable no-secrets/no-secrets */
|
|
199
|
+
/**
|
|
200
|
+
* This follows the specification for version 1 of the
|
|
201
|
+
* {@link https://github.com/thunderbird/thunderbird-android/blob/8c2c036d36344f56128fcad77a5f355336ad67cc/feature/migration/qrcode/qr-code-format.md|Thunderbird for Andriod QR code format}.
|
|
202
|
+
*
|
|
203
|
+
* This generates the UTF-8 string that should then be re-encoded as binary data
|
|
204
|
+
* in the QR code. Some QR code libraries support this out of the box, other
|
|
205
|
+
* require explicit conversion to a byte string or more modern JS byte format
|
|
206
|
+
* like UInt8Array - which can easily be achieved with a TextEncoder.
|
|
207
|
+
*
|
|
208
|
+
* To allow managing the QR code data size, multiple accounts can be split over
|
|
209
|
+
* multiple QR codes. If you choose to do this, you must decide the split
|
|
210
|
+
* yourself and then provide the sequence information in the second and third
|
|
211
|
+
* function parameter.
|
|
212
|
+
*
|
|
213
|
+
* @param {AccountInfo[]} accountInfos
|
|
214
|
+
* @param {number} [sequenceNumber=1] - The (1-based) position of this QR code
|
|
215
|
+
* in a sequence of QR codes. When only transmitting a single account this can
|
|
216
|
+
* be safely omitted.
|
|
217
|
+
* @param {number} [sequenceTotal=1] - The total QR codes in the sequence. When
|
|
218
|
+
* only transmitting a single account this can be safely omitted.
|
|
219
|
+
* @returns {string}
|
|
220
|
+
*/
|
|
221
|
+
/* eslint-enable no-secrets/no-secrets */
|
|
222
|
+
export function encodeAccounts(
|
|
223
|
+
accountInfos,
|
|
224
|
+
sequenceNumber = DEFAULT_SEQUENCE_POSITION,
|
|
225
|
+
sequenceTotal = DEFAULT_SEQUENCE_TOTAL,
|
|
226
|
+
) {
|
|
227
|
+
return JSON.stringify([
|
|
228
|
+
FORMAT_VERSION,
|
|
229
|
+
getMiscellaneousData(sequenceNumber, sequenceTotal),
|
|
230
|
+
...accountInfos.flatMap((accountInfo) => getAccountArrays(accountInfo)),
|
|
231
|
+
]);
|
|
232
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "thunderbird-account-qr-code",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Encode E-Mail account info in a QR code for import in Thunderbird for Android\"",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "c8 ava",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"report": "c8 report -r lcov"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"thunderbird",
|
|
14
|
+
"qr",
|
|
15
|
+
"email"
|
|
16
|
+
],
|
|
17
|
+
"files": [
|
|
18
|
+
"index.js",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"author": "Martin Giger (https://humanoids.be)",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@freaktechnik/eslint-config-browser": "^12.0.1",
|
|
26
|
+
"@freaktechnik/eslint-config-node": "^12.0.1",
|
|
27
|
+
"@freaktechnik/eslint-config-test": "^12.0.1",
|
|
28
|
+
"ava": "^6.4.1",
|
|
29
|
+
"c8": "^10.1.3",
|
|
30
|
+
"eslint": "^9.39.2",
|
|
31
|
+
"qr": "^0.5.3"
|
|
32
|
+
}
|
|
33
|
+
}
|