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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/index.js +232 -0
  4. 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
+ [![codecov](https://codecov.io/gh/freaktechnik/thunderbird-account-qr-code/graph/badge.svg?token=HGWPlqqIdA)](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
+ }