@efffrida/frida-tools 0.0.26 → 0.0.28

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 (38) hide show
  1. package/dist/FridaDevice.d.ts +9 -8
  2. package/dist/FridaDevice.d.ts.map +1 -1
  3. package/dist/FridaDevice.js.map +1 -1
  4. package/dist/FridaDeviceAcquisitionError.d.ts +3 -18
  5. package/dist/FridaDeviceAcquisitionError.d.ts.map +1 -1
  6. package/dist/FridaDeviceAcquisitionError.js +2 -13
  7. package/dist/FridaDeviceAcquisitionError.js.map +1 -1
  8. package/dist/FridaScript.d.ts +12 -13
  9. package/dist/FridaScript.d.ts.map +1 -1
  10. package/dist/FridaScript.js +10 -9
  11. package/dist/FridaScript.js.map +1 -1
  12. package/dist/FridaSession.d.ts +21 -7
  13. package/dist/FridaSession.d.ts.map +1 -1
  14. package/dist/FridaSession.js.map +1 -1
  15. package/dist/FridaSessionError.d.ts +1 -16
  16. package/dist/FridaSessionError.d.ts.map +1 -1
  17. package/dist/FridaSessionError.js +2 -13
  18. package/dist/FridaSessionError.js.map +1 -1
  19. package/dist/internal/compiler.d.ts +2 -0
  20. package/dist/internal/compiler.d.ts.map +1 -0
  21. package/dist/internal/compiler.js +82 -0
  22. package/dist/internal/compiler.js.map +1 -0
  23. package/dist/internal/device.js +33 -32
  24. package/dist/internal/device.js.map +1 -1
  25. package/dist/internal/script.js +56 -105
  26. package/dist/internal/script.js.map +1 -1
  27. package/dist/internal/session.js +90 -48
  28. package/dist/internal/session.js.map +1 -1
  29. package/package.json +11 -17
  30. package/src/FridaDevice.ts +11 -10
  31. package/src/FridaDeviceAcquisitionError.ts +4 -29
  32. package/src/FridaScript.ts +22 -23
  33. package/src/FridaSession.ts +25 -7
  34. package/src/FridaSessionError.ts +2 -24
  35. package/src/internal/compiler.ts +122 -0
  36. package/src/internal/device.ts +85 -67
  37. package/src/internal/script.ts +209 -292
  38. package/src/internal/session.ts +116 -52
@@ -0,0 +1,122 @@
1
+ import * as Cause from "effect/Cause";
2
+ import * as Context from "effect/Context";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Function from "effect/Function";
5
+ import * as Predicate from "effect/Predicate";
6
+
7
+ import * as Frida from "frida";
8
+
9
+ import * as FridaSessionError from "../FridaSessionError.ts";
10
+
11
+ /** @internal */
12
+ export const Compiler: Context.Reference<Frida.Compiler> = Context.Reference<Frida.Compiler>(
13
+ "@efffrida/frida-tools/compiler/default",
14
+ { defaultValue: () => new Frida.Compiler() }
15
+ );
16
+
17
+ /** @internal */
18
+ export const compile = Function.dual<
19
+ (
20
+ options?: Frida.CompilerOptions | undefined
21
+ ) => (path: string) => Effect.Effect<string, FridaSessionError.FridaSessionError, never>,
22
+ (
23
+ path: string,
24
+ options?: Frida.CompilerOptions | undefined
25
+ ) => Effect.Effect<string, FridaSessionError.FridaSessionError, never>
26
+ >(
27
+ (arguments_) => Predicate.isString(arguments_[0]),
28
+ (path: string, options?: Frida.CompilerOptions | undefined) =>
29
+ Effect.gen(function* () {
30
+ const compiler = yield* Compiler;
31
+
32
+ // https://github.com/frida/frida-compile/blob/e81ae27369466c69868fc6ee36c0f227bbfe340c/src/cli.ts#L173-L182
33
+ interface Diagnostic {
34
+ category: string;
35
+ code: number;
36
+ text: string;
37
+ file?: {
38
+ path: string;
39
+ line: number;
40
+ character: number;
41
+ };
42
+ }
43
+
44
+ const formatDiagnostic = (diagnostic: Diagnostic): FridaSessionError.FridaSessionError => {
45
+ const location = diagnostic.file
46
+ ? `${diagnostic.file.path}:${diagnostic.file.line}:${diagnostic.file.character}`
47
+ : undefined;
48
+ const message = `TS${diagnostic.code}: ${diagnostic.text}`;
49
+ const cause = location ? `${location} - ${message}` : message;
50
+ return new FridaSessionError.FridaSessionError({ cause, when: "compile" });
51
+ };
52
+
53
+ const compileErrors: Array<Diagnostic> = [];
54
+ const cancellable = new Frida.Cancellable();
55
+
56
+ const compilerBuildOptions = {
57
+ externals: options?.externals,
58
+ projectRoot: options?.projectRoot,
59
+ platform: options?.platform ?? Frida.JsPlatform.Gum,
60
+ typeCheck: options?.typeCheck ?? Frida.TypeCheckMode.Full,
61
+ sourceMaps: options?.sourceMaps ?? Frida.SourceMaps.Included,
62
+ compression: options?.compression ?? Frida.JsCompression.None,
63
+ bundleFormat: options?.bundleFormat ?? Frida.BundleFormat.Esm,
64
+ outputFormat: options?.outputFormat ?? Frida.OutputFormat.Unescaped,
65
+ };
66
+
67
+ return yield* Effect.callback<string, FridaSessionError.FridaSessionError, never>((resume) => {
68
+ const onOutput = (bundle: string) => {
69
+ disconnectAll();
70
+ resume(Effect.succeed(bundle));
71
+ };
72
+
73
+ const onDiagnostic = (diagnostic: Array<Diagnostic>) => {
74
+ for (const diag of diagnostic) {
75
+ if (diag.category === "error") {
76
+ compileErrors.push(diag);
77
+ }
78
+ }
79
+ };
80
+
81
+ const onFinished = () => {
82
+ disconnectAll();
83
+ if (compileErrors.length > 0) {
84
+ resume(
85
+ Effect.failCauseSync(() => {
86
+ const [first, ...rest] = compileErrors;
87
+ let cause = Cause.fail(formatDiagnostic(first));
88
+ for (const diag of rest) {
89
+ cause = Cause.combine(cause, Cause.fail(formatDiagnostic(diag)));
90
+ }
91
+ return cause;
92
+ })
93
+ );
94
+ }
95
+ };
96
+
97
+ compiler.output.connect(onOutput);
98
+ compiler.diagnostics.connect(onDiagnostic);
99
+ compiler.finished.connect(onFinished);
100
+ const disconnectAll = () => {
101
+ compiler.output.disconnect(onOutput);
102
+ compiler.diagnostics.disconnect(onDiagnostic);
103
+ compiler.finished.disconnect(onFinished);
104
+ };
105
+
106
+ compiler.build(path, compilerBuildOptions, cancellable).catch((cause) => {
107
+ disconnectAll();
108
+ resume(
109
+ new FridaSessionError.FridaSessionError({
110
+ when: "compile",
111
+ cause,
112
+ })
113
+ );
114
+ });
115
+
116
+ return Effect.sync(() => {
117
+ disconnectAll();
118
+ cancellable.cancel();
119
+ });
120
+ });
121
+ })
122
+ );
@@ -1,26 +1,23 @@
1
- import type * as CommandExecutor from "@effect/platform/CommandExecutor";
2
- import type * as ConfigError from "effect/ConfigError";
1
+ import type * as PlatformError from "effect/PlatformError";
3
2
  import type * as Scope from "effect/Scope";
4
3
 
5
- import * as Command from "@effect/platform/Command";
6
- import * as PlatformError from "@effect/platform/Error";
7
- import * as Chunk from "effect/Chunk";
8
4
  import * as Config from "effect/Config";
9
5
  import * as Context from "effect/Context";
10
6
  import * as Effect from "effect/Effect";
11
7
  import * as Layer from "effect/Layer";
12
- import * as ParseResult from "effect/ParseResult";
8
+ import * as Path from "effect/Path";
13
9
  import * as Predicate from "effect/Predicate";
14
10
  import * as Schema from "effect/Schema";
15
11
  import * as Stream from "effect/Stream";
16
12
  import * as String from "effect/String";
17
13
  import * as Tuple from "effect/Tuple";
18
- import * as Frida from "frida";
19
- import * as net from "node:net";
20
- import * as path from "node:path";
14
+ import * as ChildProcess from "effect/unstable/process/ChildProcess";
15
+ import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
21
16
 
22
17
  import type * as FridaDevice from "../FridaDevice.ts";
23
18
 
19
+ import * as Frida from "frida";
20
+
24
21
  import * as FridaDeviceAcquisitionError from "../FridaDeviceAcquisitionError.ts";
25
22
 
26
23
  /** @internal */
@@ -29,7 +26,7 @@ export const FridaDeviceTypeId: FridaDevice.FridaDeviceTypeId = Symbol.for(
29
26
  ) as FridaDevice.FridaDeviceTypeId;
30
27
 
31
28
  /** @internal */
32
- export const Tag = Context.GenericTag<FridaDevice.FridaDevice>("@efffrida/frida-tools/FridaDevice");
29
+ export const Tag = Context.Service<FridaDevice.FridaDevice>("@efffrida/frida-tools/FridaDevice");
33
30
 
34
31
  /** @internal */
35
32
  export const isFridaDevice = (u: unknown): u is FridaDevice.FridaDevice => Predicate.hasProperty(u, FridaDeviceTypeId);
@@ -142,14 +139,15 @@ export const acquireAndroidEmulatorDevice = Effect.fn("acquireAndroidEmulatorDev
142
139
  | undefined
143
140
  ): Effect.fn.Return<
144
141
  FridaDevice.FridaDevice,
145
- ParseResult.ParseError | PlatformError.PlatformError | FridaDeviceAcquisitionError.FridaDeviceAcquisitionError,
146
- CommandExecutor.CommandExecutor | Scope.Scope
142
+ PlatformError.PlatformError | FridaDeviceAcquisitionError.FridaDeviceAcquisitionError,
143
+ ChildProcessSpawner.ChildProcessSpawner | Scope.Scope
147
144
  > {
148
145
  const hidden = options?.hidden ?? false;
149
146
  const adbExecutable = options?.adbExecutable ?? "adb";
150
147
  const emulatorExecutable = options?.emulatorExecutable ?? "emulator";
151
148
  const fridaExecutable = options?.fridaExecutable ?? "/data/local/tmp/frida-server";
152
149
 
150
+ const net = yield* Effect.promise(() => import("node:net"));
153
151
  yield* Effect.annotateCurrentSpan({
154
152
  "emulator.name": name,
155
153
  "emulator.hidden": hidden,
@@ -159,7 +157,7 @@ export const acquireAndroidEmulatorDevice = Effect.fn("acquireAndroidEmulatorDev
159
157
  });
160
158
 
161
159
  const getTwoConsecutiveFreePorts = (startingAt: number = 2000) =>
162
- Effect.async<readonly [firstPort: number, secondPort: number], never, never>((resume) => {
160
+ Effect.callback<readonly [firstPort: number, secondPort: number], never, never>((resume) => {
163
161
  const socket1 = net.createConnection(startingAt);
164
162
  const socket2 = net.createConnection(startingAt + 1);
165
163
 
@@ -171,12 +169,11 @@ export const acquireAndroidEmulatorDevice = Effect.fn("acquireAndroidEmulatorDev
171
169
  };
172
170
 
173
171
  const onError = (error: NodeJS.ErrnoException) => {
174
- if (error.code !== "ECONNREFUSED") {
175
- resume(Effect.dieMessage("Failed to get two free consecutive ports"));
176
- }
172
+ cleanup();
177
173
 
178
- if (socket1.connecting === false && socket2.connecting === false) {
179
- cleanup();
174
+ if (error.code !== "ECONNREFUSED") {
175
+ resume(Effect.die("Failed to get two free consecutive ports"));
176
+ } else if (socket1.connecting === false && socket2.connecting === false) {
180
177
  resume(Effect.succeed(Tuple.make(startingAt, startingAt + 1)));
181
178
  }
182
179
  };
@@ -193,13 +190,13 @@ export const acquireAndroidEmulatorDevice = Effect.fn("acquireAndroidEmulatorDev
193
190
  return Effect.sync(() => cleanup());
194
191
  });
195
192
 
193
+ const childProcessSpawner = yield* ChildProcessSpawner.ChildProcessSpawner;
196
194
  const filterExitOk = (code: number) => (code === 0 ? Effect.void : Effect.fail(code));
197
195
 
198
196
  const [firstPort, _secondPort] = yield* getTwoConsecutiveFreePorts();
199
197
  const emulator = `emulator-${firstPort}`;
200
198
 
201
- const emulatorProcess = yield* Command.make(
202
- emulatorExecutable,
199
+ const emulatorProcess = yield* ChildProcess.make(emulatorExecutable, [
203
200
  `@${name}`,
204
201
  "-delay-adb",
205
202
  "-read-only",
@@ -207,12 +204,12 @@ export const acquireAndroidEmulatorDevice = Effect.fn("acquireAndroidEmulatorDev
207
204
  "-port",
208
205
  firstPort.toString(),
209
206
  ...(hidden ? ["-no-window"] : []),
210
- ...(options?.extraEmulatorArgs ?? [])
211
- ).pipe(Command.start);
207
+ ...(options?.extraEmulatorArgs ?? []),
208
+ ]);
212
209
 
213
210
  yield* Effect.addFinalizer(() =>
214
- Command.make(adbExecutable, "-s", emulator, "emu", "kill").pipe(
215
- Command.exitCode,
211
+ ChildProcess.make(adbExecutable, ["-s", emulator, "emu", "kill"]).pipe(
212
+ childProcessSpawner.exitCode,
216
213
  Effect.flatMap(filterExitOk),
217
214
  Effect.orDie
218
215
  )
@@ -223,49 +220,64 @@ export const acquireAndroidEmulatorDevice = Effect.fn("acquireAndroidEmulatorDev
223
220
  String.includes("Boot completed")
224
221
  );
225
222
 
226
- const decoder = new TextDecoder();
227
223
  yield* emulatorProcess.stderr.pipe(Stream.runHead, Effect.forkScoped);
228
224
  yield* emulatorProcess.stdout.pipe(
229
- Stream.mapChunks(Chunk.map((bytes) => decoder.decode(bytes))),
225
+ Stream.decodeText(),
230
226
  Stream.splitLines,
231
227
  Stream.takeUntil(isBootCompletedPredicate),
232
228
  Stream.runDrain
233
229
  );
234
230
 
235
- yield* Command.make(adbExecutable, `-s`, emulator, "wait-for-device").pipe(
236
- Command.exitCode,
231
+ yield* ChildProcess.make(adbExecutable, ["-s", emulator, "wait-for-device"]).pipe(
232
+ childProcessSpawner.exitCode,
237
233
  Effect.flatMap(filterExitOk),
238
- Effect.mapError(
234
+ Effect.catchIf(
235
+ Predicate.isNumber,
239
236
  (code) =>
240
237
  new FridaDeviceAcquisitionError.FridaDeviceAcquisitionError({
241
- attempts: 1,
242
- acquisitionMethod: "android-emulator",
243
238
  cause: `ADB wait-for-device failed with exit code ${code}`,
239
+ acquisitionMethod: "android-emulator",
240
+ attempts: 1,
244
241
  })
245
242
  )
246
243
  );
247
244
 
248
- yield* Command.make(adbExecutable, `-s`, emulator, "root").pipe(
249
- Command.exitCode,
245
+ yield* ChildProcess.make(adbExecutable, ["-s", emulator, "root"]).pipe(
246
+ childProcessSpawner.exitCode,
250
247
  Effect.flatMap(filterExitOk),
251
- Effect.mapError(
248
+ Effect.catchIf(
249
+ Predicate.isNumber,
252
250
  (code) =>
253
251
  new FridaDeviceAcquisitionError.FridaDeviceAcquisitionError({
254
- attempts: 1,
255
- acquisitionMethod: "android-emulator",
256
252
  cause: `ADB root failed with exit code ${code}`,
253
+ acquisitionMethod: "android-emulator",
254
+ attempts: 1,
257
255
  })
258
256
  )
259
257
  );
260
258
 
261
- yield* Command.make(adbExecutable, "-s", emulator, "shell", `"${fridaExecutable}"`).pipe(
262
- Command.start,
259
+ yield* ChildProcess.make(adbExecutable, ["-s", emulator, "shell", `"${fridaExecutable}"`]).pipe(
263
260
  Effect.andThen(Effect.sleep("3 seconds"))
264
261
  );
265
262
 
266
- const fridaPort = yield* Command.make(adbExecutable, "-s", emulator, "forward", "tcp:0", "tcp:27042").pipe(
267
- Command.string,
268
- Effect.flatMap(Schema.decode(Schema.NumberFromString))
263
+ const fridaPort = yield* ChildProcess.make(adbExecutable, [
264
+ "-s",
265
+ emulator,
266
+ "forward",
267
+ "tcp:0",
268
+ "tcp:27042",
269
+ ]).pipe(
270
+ childProcessSpawner.string,
271
+ Effect.flatMap(Schema.decodeEffect(Schema.NumberFromString)),
272
+ Effect.catchTag(
273
+ "SchemaError",
274
+ (cause) =>
275
+ new FridaDeviceAcquisitionError.FridaDeviceAcquisitionError({
276
+ acquisitionMethod: "android-emulator",
277
+ attempts: 1,
278
+ cause,
279
+ })
280
+ )
269
281
  );
270
282
 
271
283
  const { host: _host, ...remoteDevice } = yield* acquireRemoteDevice(`localhost:${fridaPort}`);
@@ -275,17 +287,17 @@ export const acquireAndroidEmulatorDevice = Effect.fn("acquireAndroidEmulatorDev
275
287
  host: `android-emulator://${emulator}`,
276
288
  } as const;
277
289
  },
278
- Effect.timeoutFail({
290
+ Effect.timeoutOrElse({
279
291
  duration: "1 minute",
280
- onTimeout: () =>
292
+ orElse: () =>
281
293
  new FridaDeviceAcquisitionError.FridaDeviceAcquisitionError({
282
294
  cause: "Timeout while acquiring Android emulator device",
283
295
  acquisitionMethod: "android-emulator",
284
296
  attempts: 1,
285
297
  }),
286
298
  }),
287
- Effect.catchIf(
288
- Predicate.or(PlatformError.isPlatformError, ParseResult.isParseError),
299
+ Effect.catchTag(
300
+ "PlatformError",
289
301
  (cause) =>
290
302
  new FridaDeviceAcquisitionError.FridaDeviceAcquisitionError({
291
303
  acquisitionMethod: "android-emulator",
@@ -305,22 +317,28 @@ export const acquireAndroidEmulatorDeviceConfig = (
305
317
  extraEmulatorArgs?: Array<string> | undefined;
306
318
  }
307
319
  | undefined
308
- ) =>
309
- Config.string("ANDROID_SDK").pipe(
310
- Config.map((androidSdk) => ({
311
- adbExecutable: path.join(androidSdk, "platform-tools", "adb"),
312
- emulatorExecutable: path.join(androidSdk, "emulator", "emulator"),
313
- })),
314
- // TODO: Should this be optional?
315
- Config.withDefault({
316
- adbExecutable: "adb",
317
- emulatorExecutable: "emulator",
318
- }),
319
- Effect.flatMap((androidSdk) =>
320
- acquireAndroidEmulatorDevice(name, {
321
- ...androidSdk,
322
- ...options,
323
- })
320
+ ): Effect.Effect<
321
+ FridaDevice.FridaDevice,
322
+ FridaDeviceAcquisitionError.FridaDeviceAcquisitionError | Config.ConfigError,
323
+ ChildProcessSpawner.ChildProcessSpawner | Path.Path | Scope.Scope
324
+ > =>
325
+ Effect.flatMap(Path.Path, (path) =>
326
+ Config.string("ANDROID_SDK").pipe(
327
+ Config.map((androidSdk) => ({
328
+ adbExecutable: path.join(androidSdk, "platform-tools", "adb"),
329
+ emulatorExecutable: path.join(androidSdk, "emulator", "emulator"),
330
+ })),
331
+ // TODO: Should this be optional?
332
+ Config.withDefault({
333
+ adbExecutable: "adb",
334
+ emulatorExecutable: "emulator",
335
+ }),
336
+ Effect.flatMap((androidSdk) =>
337
+ acquireAndroidEmulatorDevice(name, {
338
+ ...androidSdk,
339
+ ...options,
340
+ })
341
+ )
324
342
  )
325
343
  );
326
344
 
@@ -336,7 +354,7 @@ export const layerRemoteDevice = (
336
354
  address: string,
337
355
  options?: Frida.RemoteDeviceOptions | undefined
338
356
  ): Layer.Layer<FridaDevice.FridaDevice, FridaDeviceAcquisitionError.FridaDeviceAcquisitionError, never> =>
339
- Layer.scoped(Tag, acquireRemoteDevice(address, options));
357
+ Layer.effect(Tag, acquireRemoteDevice(address, options));
340
358
 
341
359
  /** @internal */
342
360
  export const layerUsbDevice = (
@@ -359,8 +377,8 @@ export const layerAndroidEmulatorDevice = (
359
377
  ): Layer.Layer<
360
378
  FridaDevice.FridaDevice,
361
379
  FridaDeviceAcquisitionError.FridaDeviceAcquisitionError,
362
- CommandExecutor.CommandExecutor
363
- > => Layer.scoped(Tag, acquireAndroidEmulatorDevice(name, options));
380
+ ChildProcessSpawner.ChildProcessSpawner
381
+ > => Layer.effect(Tag, acquireAndroidEmulatorDevice(name, options));
364
382
 
365
383
  /** @internal */
366
384
  export const layerAndroidEmulatorDeviceConfig = (
@@ -374,6 +392,6 @@ export const layerAndroidEmulatorDeviceConfig = (
374
392
  | undefined
375
393
  ): Layer.Layer<
376
394
  FridaDevice.FridaDevice,
377
- ConfigError.ConfigError | FridaDeviceAcquisitionError.FridaDeviceAcquisitionError,
378
- CommandExecutor.CommandExecutor
379
- > => Layer.scoped(Tag, acquireAndroidEmulatorDeviceConfig(name, options));
395
+ Config.ConfigError | FridaDeviceAcquisitionError.FridaDeviceAcquisitionError,
396
+ ChildProcessSpawner.ChildProcessSpawner | Path.Path
397
+ > => Layer.effect(Tag, acquireAndroidEmulatorDeviceConfig(name, options));