signal-styler 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/utils.js ADDED
@@ -0,0 +1,212 @@
1
+ const asar = require("@electron/asar");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const os = require("os");
5
+
6
+ const NEW_ASAR_PATH = path.join(os.homedir(), ".cache", "signal-styled.asar");
7
+
8
+ const BACKUP_ASAR_PATH = path.join(
9
+ os.homedir(),
10
+ ".cache",
11
+ "signal-original.asar"
12
+ );
13
+
14
+ const MODDED_MANIFEST_HEADER = `/* SIGNAL-STYLER */ @import "custom.css"; /* SIGNAL-STYLER */`;
15
+
16
+ class Utils {
17
+ constructor() {
18
+ this.asarPath = this.assumeAsarPath();
19
+ }
20
+
21
+ /**
22
+ * Validates if the given asar path is valid.
23
+ * @return {boolean} true if the asar path is valid, false otherwise.
24
+ *
25
+ * A valid asar path is one that is a file and ends with ".asar".
26
+ */
27
+ validateAsarPath() {
28
+ if (!this.asarPath) return false;
29
+
30
+ return (
31
+ fs.existsSync(this.asarPath) &&
32
+ this.asarPath.endsWith(".asar") &&
33
+ fs.statSync(this.asarPath).isFile()
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Checks if the asar file has root-only permissions.
39
+ *
40
+ * If the file has root-only permissions, it means that the user needs to run
41
+ * signal-styler with sudo.
42
+ *
43
+ * @return {boolean} true if the asar file has root-only permissions, false
44
+ * otherwise.
45
+ */
46
+ checkNeedsSudo() {
47
+ try {
48
+ const stats = fs.statSync(this.asarPath);
49
+ return stats.uid === 0;
50
+ } catch (err) {
51
+ console.error(`Error checking file ownership: ${err.message}`);
52
+ return true;
53
+ }
54
+ }
55
+
56
+ assumeAsarPath() {
57
+ const systemFlatpak =
58
+ "/var/lib/flatpak/app/org.signal.Signal/current/active/files/Signal/resources/app.asar";
59
+ const userFlatpak = path.join(
60
+ os.homedir(),
61
+ ".var",
62
+ "app",
63
+ "org.signal.Signal",
64
+ "current",
65
+ "active",
66
+ "files",
67
+ "Signal",
68
+ "resources",
69
+ "app.asar"
70
+ );
71
+
72
+ const windows =
73
+ process.platform === "win32"
74
+ ? path.join(
75
+ process.env.LOCALAPPDATA,
76
+ "Programs",
77
+ "Signal",
78
+ "resources",
79
+ "app.asar"
80
+ )
81
+ : null;
82
+
83
+ const darwin =
84
+ process.platform === "darwin"
85
+ ? "/Applications/Signal.app/Contents/Resources/app.asar"
86
+ : null;
87
+
88
+ if (fs.existsSync(systemFlatpak)) {
89
+ return systemFlatpak;
90
+ } else if (fs.existsSync(userFlatpak)) {
91
+ return userFlatpak;
92
+ } else if (windows && fs.existsSync(windows)) {
93
+ return windows;
94
+ } else if (darwin && fs.existsSync(darwin)) {
95
+ return darwin;
96
+ } else {
97
+ return null;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Extracts the manifest.css file from the given asar.
103
+ * @return {string} the text content of the manifest.css file.
104
+ */
105
+ getManifest() {
106
+ return asar.extractFile(this.asarPath, "stylesheets/manifest.css");
107
+ }
108
+
109
+ /**
110
+ * Writes the given manifest to the correct location inside the `patchDir`
111
+ * after prepending the `MODDED_MANIFEST_HEADER`.
112
+ * @param {string} manifest - The text content of the manifest.css file.
113
+ */
114
+ patchManifest(manifest) {
115
+ // write new manifest to patchDir
116
+
117
+ fs.writeFileSync(
118
+ path.join(this.patchDir, "stylesheets/manifest.css"),
119
+ MODDED_MANIFEST_HEADER + "\n" + manifest.toString()
120
+ );
121
+ }
122
+
123
+ /**
124
+ * Copies the given CSS file to the correct location inside the `patchDir`.
125
+ * @param {string} cssPath - Path to the custom stylesheet CSS file.
126
+ */
127
+ setStylesheet(cssPath) {
128
+ fs.copyFileSync(
129
+ cssPath,
130
+ path.join(this.patchDir, "stylesheets/custom.css")
131
+ );
132
+ }
133
+
134
+ /**
135
+ * Creates two temporary directories, `patchDir` and `buildDir`, and also
136
+ * creates a "stylesheets" directory inside `patchDir`.
137
+ *
138
+ * The directories are created in the system's temporary directory and are
139
+ * named "signal-styler-patch-{random}" and "signal-styler-build-{random}".
140
+ *
141
+ * The `patchDir` is used to store the modified manifest.css file, while the
142
+ * `buildDir` is used to store the full modified asar file before it is built
143
+ * into a new asar file.
144
+ */
145
+ createTempDirs() {
146
+ this.patchDir = fs.mkdtempSync(
147
+ path.join(os.tmpdir(), "signal-styler-patch-")
148
+ );
149
+ this.buildDir = fs.mkdtempSync(
150
+ path.join(os.tmpdir(), "signal-styler-build-")
151
+ );
152
+
153
+ fs.mkdirSync(path.join(this.patchDir, "stylesheets"));
154
+ }
155
+
156
+ /**
157
+ * Returns whether the given manifest string has been modified by
158
+ * signal-styler. The check is done by looking for the presence of the
159
+ * MODDED_MANIFEST_HEADER string.
160
+ * @param {string} manifest - The text content of the manifest.css file.
161
+ * @returns {boolean} - true if signal-styler has modified this manifest, false
162
+ * otherwise.
163
+ */
164
+ isManifestModified(manifest) {
165
+ return manifest.toString().startsWith(MODDED_MANIFEST_HEADER);
166
+ }
167
+
168
+ /**
169
+ * Builds a new Signal Desktop asar file by first unpacking the full asar to
170
+ * the `buildDir`, then copying the content of the `patchDir` over it, then
171
+ * backing up the original asar by copying it to `BACKUP_ASAR_PATH`, and
172
+ * finally building a new asar with the modified content.
173
+ *
174
+ * @returns {Promise<void>} - a Promise that resolves when the build is
175
+ * complete.
176
+ */
177
+ async build() {
178
+ // unpack full asar to buildDir
179
+ asar.extractAll(this.asarPath, this.buildDir);
180
+
181
+ // copy content of patchDir to buildDir
182
+ fs.cpSync(this.patchDir, this.buildDir, { recursive: true });
183
+
184
+ // backup original asar
185
+ fs.copyFileSync(this.asarPath, BACKUP_ASAR_PATH);
186
+
187
+ // build asar
188
+ await asar.createPackage(this.buildDir, NEW_ASAR_PATH);
189
+ }
190
+
191
+ /**
192
+ * Installs the new asar file that was built by copying it to the location of
193
+ * the original asar file.
194
+ */
195
+ install() {
196
+ // install new asar
197
+ fs.copyFileSync(NEW_ASAR_PATH, this.asarPath);
198
+ }
199
+
200
+ /**
201
+ * Removes the temporary directories and files created during the process.
202
+ */
203
+ cleanup() {
204
+ // clean up
205
+
206
+ fs.rmSync(this.patchDir, { recursive: true });
207
+ fs.rmSync(this.buildDir, { recursive: true });
208
+ fs.unlinkSync(NEW_ASAR_PATH);
209
+ }
210
+ }
211
+
212
+ module.exports = new Utils();