@efffrida/frida-tools 0.0.14 → 0.0.15

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 (92) hide show
  1. package/README.md +6 -10
  2. package/dist/{dts/FridaDevice.d.ts → FridaDevice.d.ts} +48 -4
  3. package/dist/FridaDevice.d.ts.map +1 -0
  4. package/dist/{esm/FridaDevice.js → FridaDevice.js} +22 -2
  5. package/dist/FridaDevice.js.map +1 -0
  6. package/dist/{dts/FridaDeviceAcquisitionError.d.ts → FridaDeviceAcquisitionError.d.ts} +2 -1
  7. package/dist/FridaDeviceAcquisitionError.d.ts.map +1 -0
  8. package/dist/{esm/FridaDeviceAcquisitionError.js → FridaDeviceAcquisitionError.js} +5 -1
  9. package/dist/FridaDeviceAcquisitionError.js.map +1 -0
  10. package/dist/FridaScript.d.ts +96 -0
  11. package/dist/FridaScript.d.ts.map +1 -0
  12. package/dist/FridaScript.js.map +1 -0
  13. package/dist/FridaSession.d.ts +72 -0
  14. package/dist/FridaSession.d.ts.map +1 -0
  15. package/dist/{esm/FridaSession.js → FridaSession.js} +10 -0
  16. package/dist/FridaSession.js.map +1 -0
  17. package/dist/FridaSessionError.d.ts.map +1 -0
  18. package/dist/FridaSessionError.js.map +1 -0
  19. package/dist/{dts/index.d.ts → index.d.ts} +8 -5
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/{esm/index.js → index.js} +3 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/{dts/internal → internal}/device.d.ts.map +1 -1
  24. package/dist/internal/device.js +189 -0
  25. package/dist/internal/device.js.map +1 -0
  26. package/dist/{dts/internal → internal}/script.d.ts.map +1 -1
  27. package/dist/internal/script.js +221 -0
  28. package/dist/internal/script.js.map +1 -0
  29. package/dist/{dts/internal → internal}/session.d.ts.map +1 -1
  30. package/dist/internal/session.js +124 -0
  31. package/dist/internal/session.js.map +1 -0
  32. package/package.json +54 -68
  33. package/src/FridaDevice.ts +92 -8
  34. package/src/FridaDeviceAcquisitionError.ts +6 -2
  35. package/src/FridaScript.ts +53 -31
  36. package/src/FridaSession.ts +31 -5
  37. package/src/FridaSessionError.ts +1 -1
  38. package/src/index.ts +9 -5
  39. package/src/internal/device.ts +311 -32
  40. package/src/internal/script.ts +265 -118
  41. package/src/internal/session.ts +135 -27
  42. package/FridaDevice/package.json +0 -6
  43. package/FridaDeviceAcquisitionError/package.json +0 -6
  44. package/FridaScript/package.json +0 -6
  45. package/FridaSession/package.json +0 -6
  46. package/FridaSessionError/package.json +0 -6
  47. package/dist/cjs/FridaDevice.js +0 -60
  48. package/dist/cjs/FridaDevice.js.map +0 -1
  49. package/dist/cjs/FridaDeviceAcquisitionError.js +0 -33
  50. package/dist/cjs/FridaDeviceAcquisitionError.js.map +0 -1
  51. package/dist/cjs/FridaScript.js +0 -40
  52. package/dist/cjs/FridaScript.js.map +0 -1
  53. package/dist/cjs/FridaSession.js +0 -45
  54. package/dist/cjs/FridaSession.js.map +0 -1
  55. package/dist/cjs/FridaSessionError.js +0 -37
  56. package/dist/cjs/FridaSessionError.js.map +0 -1
  57. package/dist/cjs/index.js +0 -18
  58. package/dist/cjs/index.js.map +0 -1
  59. package/dist/cjs/internal/device.js +0 -68
  60. package/dist/cjs/internal/device.js.map +0 -1
  61. package/dist/cjs/internal/script.js +0 -163
  62. package/dist/cjs/internal/script.js.map +0 -1
  63. package/dist/cjs/internal/session.js +0 -55
  64. package/dist/cjs/internal/session.js.map +0 -1
  65. package/dist/dts/FridaDevice.d.ts.map +0 -1
  66. package/dist/dts/FridaDeviceAcquisitionError.d.ts.map +0 -1
  67. package/dist/dts/FridaScript.d.ts +0 -80
  68. package/dist/dts/FridaScript.d.ts.map +0 -1
  69. package/dist/dts/FridaSession.d.ts +0 -56
  70. package/dist/dts/FridaSession.d.ts.map +0 -1
  71. package/dist/dts/FridaSessionError.d.ts.map +0 -1
  72. package/dist/dts/index.d.ts.map +0 -1
  73. package/dist/esm/FridaDevice.js.map +0 -1
  74. package/dist/esm/FridaDeviceAcquisitionError.js.map +0 -1
  75. package/dist/esm/FridaScript.js.map +0 -1
  76. package/dist/esm/FridaSession.js.map +0 -1
  77. package/dist/esm/FridaSessionError.js.map +0 -1
  78. package/dist/esm/index.js.map +0 -1
  79. package/dist/esm/internal/device.js +0 -55
  80. package/dist/esm/internal/device.js.map +0 -1
  81. package/dist/esm/internal/script.js +0 -155
  82. package/dist/esm/internal/script.js.map +0 -1
  83. package/dist/esm/internal/session.js +0 -44
  84. package/dist/esm/internal/session.js.map +0 -1
  85. package/dist/esm/package.json +0 -4
  86. package/index/package.json +0 -6
  87. /package/dist/{esm/FridaScript.js → FridaScript.js} +0 -0
  88. /package/dist/{dts/FridaSessionError.d.ts → FridaSessionError.d.ts} +0 -0
  89. /package/dist/{esm/FridaSessionError.js → FridaSessionError.js} +0 -0
  90. /package/dist/{dts/internal → internal}/device.d.ts +0 -0
  91. /package/dist/{dts/internal → internal}/script.d.ts +0 -0
  92. /package/dist/{dts/internal → internal}/session.d.ts +0 -0
@@ -1,21 +1,24 @@
1
1
  import type * as Scope from "effect/Scope";
2
- import type * as FridaScript from "../FridaScript.js";
2
+ import type * as FridaScript from "../FridaScript.ts";
3
3
 
4
+ import * as Path from "@effect/platform/Path";
5
+ import * as Cause from "effect/Cause";
4
6
  import * as Context from "effect/Context";
5
7
  import * as Deferred from "effect/Deferred";
6
8
  import * as Effect from "effect/Effect";
9
+ import * as Exit from "effect/Exit";
7
10
  import * as Function from "effect/Function";
8
11
  import * as Layer from "effect/Layer";
12
+ import * as Mailbox from "effect/Mailbox";
9
13
  import * as Option from "effect/Option";
10
14
  import * as Predicate from "effect/Predicate";
11
- import * as Queue from "effect/Queue";
15
+ import * as Schema from "effect/Schema";
12
16
  import * as Sink from "effect/Sink";
13
17
  import * as Stream from "effect/Stream";
14
- import * as Take from "effect/Take";
15
18
  import * as Frida from "frida";
16
- import * as FridaDevice from "../FridaDevice.js";
17
- import * as FridaSession from "../FridaSession.js";
18
- import * as FridaSessionError from "../FridaSessionError.js";
19
+
20
+ import * as FridaSession from "../FridaSession.ts";
21
+ import * as FridaSessionError from "../FridaSessionError.ts";
19
22
 
20
23
  /** @internal */
21
24
  export const FridaScriptTypeId: FridaScript.FridaScriptTypeId = Symbol.for(
@@ -28,182 +31,326 @@ export const Tag = Context.GenericTag<FridaScript.FridaScript>("@efffrida/frida-
28
31
  /** @internal */
29
32
  export const isFridaScript = (u: unknown): u is FridaScript.FridaScript => Predicate.hasProperty(u, FridaScriptTypeId);
30
33
 
34
+ /** @internal */
35
+ export const compile = Function.dual<
36
+ (
37
+ options?: Frida.CompilerOptions | undefined
38
+ ) => (path: string) => Effect.Effect<string, FridaSessionError.FridaSessionError, Scope.Scope>,
39
+ (
40
+ path: string,
41
+ options?: Frida.CompilerOptions | undefined
42
+ ) => Effect.Effect<string, FridaSessionError.FridaSessionError, Scope.Scope>
43
+ >(
44
+ (arguments_) => Predicate.isString(arguments_[0]),
45
+ (path: string, options?: Frida.CompilerOptions | undefined) =>
46
+ Effect.asyncEffect<string, FridaSessionError.FridaSessionError, never, never, never, Scope.Scope>(
47
+ Effect.fnUntraced(function* (resume) {
48
+ // https://github.com/frida/frida-compile/blob/e81ae27369466c69868fc6ee36c0f227bbfe340c/src/cli.ts#L173-L182
49
+ interface Diagnostic {
50
+ category: string;
51
+ code: number;
52
+ text: string;
53
+ file?: {
54
+ path: string;
55
+ line: number;
56
+ character: number;
57
+ };
58
+ }
59
+
60
+ const formatDiagnostic = (diagnostic: Diagnostic): FridaSessionError.FridaSessionError => {
61
+ const location = diagnostic.file
62
+ ? `${diagnostic.file.path}:${diagnostic.file.line}:${diagnostic.file.character}`
63
+ : undefined;
64
+ const message = `TS${diagnostic.code}: ${diagnostic.text}`;
65
+ const cause = location ? `${location} - ${message}` : message;
66
+ return new FridaSessionError.FridaSessionError({ cause, when: "compile" });
67
+ };
68
+
69
+ const compileErrors: Array<Diagnostic> = [];
70
+ const compiler = new Frida.Compiler();
71
+
72
+ const onOutput = (bundle: string) => resume(Effect.succeed(bundle));
73
+ yield* Effect.addFinalizer(() => Effect.sync(() => compiler.output.disconnect(onOutput)));
74
+ compiler.output.connect(onOutput);
75
+
76
+ const onDiagnostic = (diagnostic: Array<Diagnostic>) => {
77
+ for (const diag of diagnostic) {
78
+ if (diag.category === "error") {
79
+ compileErrors.push(diag);
80
+ }
81
+ }
82
+ };
83
+ yield* Effect.addFinalizer(() => Effect.sync(() => compiler.diagnostics.disconnect(onDiagnostic)));
84
+ compiler.diagnostics.connect(onDiagnostic);
85
+
86
+ const onFinished = () => {
87
+ if (compileErrors.length > 0) {
88
+ resume(
89
+ Effect.failCauseSync(() => {
90
+ const [first, ...rest] = compileErrors;
91
+ let cause = Cause.fail(formatDiagnostic(first));
92
+ for (const diag of rest) {
93
+ cause = Cause.parallel(cause, Cause.fail(formatDiagnostic(diag)));
94
+ }
95
+ return cause;
96
+ })
97
+ );
98
+ }
99
+ };
100
+ yield* Effect.addFinalizer(() => Effect.sync(() => compiler.finished.disconnect(onFinished)));
101
+ compiler.finished.connect(onFinished);
102
+
103
+ const cancellable = new Frida.Cancellable();
104
+ compiler
105
+ .build(
106
+ path,
107
+ {
108
+ externals: options?.externals,
109
+ platform: options?.platform ?? Frida.JsPlatform.Gum,
110
+ typeCheck: options?.typeCheck ?? Frida.TypeCheckMode.Full,
111
+ sourceMaps: options?.sourceMaps ?? Frida.SourceMaps.Included,
112
+ compression: options?.compression ?? Frida.JsCompression.None,
113
+ bundleFormat: options?.bundleFormat ?? Frida.BundleFormat.Esm,
114
+ outputFormat: options?.outputFormat ?? Frida.OutputFormat.Unescaped,
115
+ },
116
+ cancellable
117
+ )
118
+ .catch((error) =>
119
+ resume(
120
+ Effect.fail(
121
+ new FridaSessionError.FridaSessionError({
122
+ cause: error,
123
+ when: "compile",
124
+ })
125
+ )
126
+ )
127
+ );
128
+
129
+ return Effect.sync(() => cancellable.cancel());
130
+ })
131
+ )
132
+ );
133
+
31
134
  /** @internal */
32
135
  export const load = Function.dual<
33
136
  (
34
- options?: (Frida.ScriptOptions & { readonly resume?: boolean | undefined }) | undefined
137
+ options?: FridaScript.LoadOptions | undefined
35
138
  ) => (
36
- source: string | Buffer
139
+ entrypoint: URL
37
140
  ) => Effect.Effect<
38
141
  FridaScript.FridaScript,
39
142
  FridaSessionError.FridaSessionError,
40
- FridaSession.FridaSession | FridaDevice.FridaDevice | Scope.Scope
143
+ Path.Path | FridaSession.FridaSession | Scope.Scope
41
144
  >,
42
145
  (
43
- source: string | Buffer,
44
- options?: (Frida.ScriptOptions & { readonly resume?: boolean | undefined }) | undefined
146
+ entrypoint: URL,
147
+ options?: FridaScript.LoadOptions | undefined
45
148
  ) => Effect.Effect<
46
149
  FridaScript.FridaScript,
47
150
  FridaSessionError.FridaSessionError,
48
- FridaSession.FridaSession | FridaDevice.FridaDevice | Scope.Scope
151
+ Path.Path | FridaSession.FridaSession | Scope.Scope
49
152
  >
50
153
  >(
51
- (arguments_) => Predicate.isString(arguments_[0]) || Buffer.isBuffer(arguments_[0]),
154
+ (arguments_) => Predicate.hasProperty(arguments_[0], "href"),
52
155
  Effect.fnUntraced(
53
- function* (
54
- source: string | Buffer,
55
- options?: (Frida.ScriptOptions & { readonly resume?: boolean | undefined }) | undefined
56
- ) {
57
- const { device } = yield* FridaDevice.FridaDevice;
156
+ function* (entrypoint: URL, options?: FridaScript.LoadOptions | undefined) {
157
+ const path = yield* Path.Path;
58
158
  const { session } = yield* FridaSession.FridaSession;
59
159
 
60
- const script = Predicate.isString(source)
61
- ? yield* Effect.tryPromise({
62
- try: () => session.createScript(source, { runtime: Frida.ScriptRuntime.V8, ...options }),
63
- catch: (cause) => new FridaSessionError.FridaSessionError({ cause, when: "compile" }),
64
- })
65
- : yield* Effect.tryPromise({
66
- try: () => session.createScriptFromBytes(source, { runtime: Frida.ScriptRuntime.V8, ...options }),
67
- catch: (cause) => new FridaSessionError.FridaSessionError({ cause, when: "compile" }),
68
- });
69
-
70
- const messageQueue =
71
- yield* Queue.unbounded<
72
- Take.Take<{ message: any; data: Option.Option<Buffer> }, FridaSessionError.FridaSessionError>
73
- >();
74
-
75
- let scriptDefectCause: unknown = undefined;
160
+ const source = yield* path
161
+ .fromFileUrl(entrypoint)
162
+ .pipe(Effect.flatMap(compile(options)))
163
+ .pipe(Effect.scoped)
164
+ .pipe(
165
+ Effect.timeoutFail({
166
+ duration: "1 minute",
167
+ onTimeout: () =>
168
+ new FridaSessionError.FridaSessionError({
169
+ when: "compile",
170
+ cause: "TypeScript compilation timed out",
171
+ }),
172
+ })
173
+ )
174
+ .pipe(
175
+ Effect.catchTag(
176
+ "BadArgument",
177
+ (platformCause) =>
178
+ new FridaSessionError.FridaSessionError({
179
+ when: "compile",
180
+ cause: platformCause,
181
+ })
182
+ )
183
+ );
184
+
185
+ const script = yield* Effect.tryPromise({
186
+ try: (signal) => {
187
+ const cancellable = new Frida.Cancellable();
188
+ signal.onabort = () => cancellable.cancel();
189
+ return session.createScript(
190
+ source,
191
+ {
192
+ runtime: Frida.ScriptRuntime.V8,
193
+ ...options,
194
+ },
195
+ cancellable
196
+ );
197
+ },
198
+ catch: (cause) =>
199
+ new FridaSessionError.FridaSessionError({
200
+ cause,
201
+ when: "compile",
202
+ }),
203
+ });
204
+
205
+ const destroyed = yield* Deferred.make<void, never>();
206
+ const scriptError = yield* Deferred.make<unknown, never>();
207
+ const mailbox = yield* Mailbox.make<
208
+ { message: unknown; data: Option.Option<Buffer> },
209
+ FridaSessionError.FridaSessionError
210
+ >(options?.messageMailboxCapacity);
211
+ yield* Effect.addFinalizer(() => mailbox.shutdown); // TODO: is this needed?
212
+
213
+ const destroyedHandler: Frida.ScriptDestroyedHandler = () => {
214
+ Deferred.unsafeDone(destroyed, Effect.void);
215
+ mailbox.unsafeDone(
216
+ Exit.fail(
217
+ new FridaSessionError.FridaSessionError({
218
+ when: "message",
219
+ cause: "Script destroyed",
220
+ })
221
+ )
222
+ );
223
+ };
224
+ yield* Effect.addFinalizer(() => Effect.sync(() => script.destroyed.disconnect(destroyedHandler)));
225
+ script.destroyed.connect(destroyedHandler);
226
+
76
227
  const messageHandler: Frida.ScriptMessageHandler = (message: Frida.Message, data: Buffer | null): void => {
77
228
  switch (message.type) {
78
229
  case Frida.MessageType.Error: {
79
- const error = new Error();
80
- error.name = "FridaScriptDefect";
81
- error.stack = message.stack ?? "";
82
- error.message = message.description.replace(/Error: /, "") ?? "";
83
- scriptDefectCause = error;
84
- const sessionError = new FridaSessionError.FridaSessionError({
85
- when: "message",
86
- cause: error.cause,
87
- });
88
- const asTake = Take.fail(sessionError);
89
- Queue.unsafeOffer(messageQueue, asTake);
230
+ const cause = new Error();
231
+ cause.name = "FridaScriptDefect";
232
+ cause.stack = message.stack ?? "";
233
+ cause.message = message.description.replace(/^\w{0,}: /, "") ?? "";
234
+ Deferred.unsafeDone(scriptError, Exit.succeed(cause));
235
+ mailbox.unsafeDone(
236
+ Exit.fail(
237
+ new FridaSessionError.FridaSessionError({
238
+ when: "message",
239
+ cause,
240
+ })
241
+ )
242
+ );
90
243
  break;
91
244
  }
92
245
 
93
246
  case Frida.MessageType.Send: {
94
- const asTake = Take.of({ message: message.payload, data: Option.fromNullable(data) });
95
- Queue.unsafeOffer(messageQueue, asTake);
247
+ mailbox.unsafeOffer({
248
+ message: message.payload,
249
+ data: Option.fromNullable(data),
250
+ });
96
251
  break;
97
252
  }
98
253
 
99
- default:
100
- Function.absurd(message);
254
+ default: {
255
+ return Function.absurd(message);
256
+ }
101
257
  }
102
258
  };
103
-
104
- const disconnectMessageHandler = Effect.sync(() => script.message.disconnect(messageHandler));
105
- yield* Effect.addFinalizer(() => Effect.flatMap(messageQueue.shutdown, () => disconnectMessageHandler));
259
+ yield* Effect.addFinalizer(() => Effect.sync(() => script.message.disconnect(messageHandler)));
106
260
  script.message.connect(messageHandler);
107
261
 
108
- const stream = Stream.fromQueue(messageQueue).pipe(Stream.flattenTake);
262
+ const failIfScriptError = (when: FridaSessionError.FridaSessionError["when"]) =>
263
+ Effect.if(Deferred.isDone(scriptError), {
264
+ onFalse: () => Effect.void,
265
+ onTrue: () =>
266
+ Effect.flatMap(
267
+ Deferred.await(scriptError),
268
+ (cause) => new FridaSessionError.FridaSessionError({ cause, when })
269
+ ),
270
+ });
271
+
272
+ const failIfDestroyed = (when: FridaSessionError.FridaSessionError["when"]) =>
273
+ Effect.if(Deferred.isDone(destroyed), {
274
+ onFalse: () => Effect.void,
275
+ onTrue: () => new FridaSessionError.FridaSessionError({ cause: "Script is destroyed", when }),
276
+ });
277
+
278
+ const stream = yield* Stream.share(
279
+ Mailbox.toStream(mailbox),
280
+ options?.streamShareOptions ?? {
281
+ replay: 100,
282
+ capacity: "unbounded",
283
+ }
284
+ );
285
+
109
286
  const sink = Sink.forEach<
110
- { message: any; data: Option.Option<Buffer> },
287
+ { message: unknown; data: Option.Option<Buffer> },
111
288
  void,
112
289
  FridaSessionError.FridaSessionError,
113
290
  never
114
- >(({ data, message }) => {
115
- if (Predicate.isNotUndefined(scriptDefectCause)) {
116
- return Effect.fail(
117
- new FridaSessionError.FridaSessionError({
118
- when: "rpcCall",
119
- cause: scriptDefectCause,
120
- })
121
- );
122
- } else {
123
- return Effect.sync(() => script.post(message, Option.getOrNull(data)));
124
- }
125
- });
291
+ >(
292
+ Effect.fnUntraced(function* ({ data, message }) {
293
+ yield* failIfDestroyed("message");
294
+ yield* failIfScriptError("message");
295
+ script.post(message, Option.getOrNull(data));
296
+ })
297
+ );
126
298
 
127
- const destroyed = yield* Deferred.make<void, never>();
128
- const destroyedHandler = () => Deferred.unsafeDone(destroyed, Effect.void);
129
- script.destroyed.connect(destroyedHandler);
130
-
131
- const callExport = (exportName: string) =>
299
+ const callExport = <A, I, R>(exportName: string, schema: Schema.Schema<A, I, R>) =>
132
300
  Effect.fn(`call frida export ${exportName}`)(function* (...args: Array<any>) {
133
301
  yield* Effect.annotateCurrentSpan("args", args);
134
-
135
- const isDestroyed = yield* Deferred.isDone(destroyed);
136
- if (isDestroyed) {
137
- return yield* new FridaSessionError.FridaSessionError({
138
- when: "rpcCall",
139
- cause: "Script is destroyed",
140
- });
141
- }
142
-
143
- if (Predicate.isNotUndefined(scriptDefectCause)) {
144
- return yield* new FridaSessionError.FridaSessionError({
145
- when: "rpcCall",
146
- cause: scriptDefectCause,
147
- });
148
- }
149
-
302
+ yield* failIfDestroyed("rpcCall");
303
+ yield* failIfScriptError("rpcCall");
150
304
  const result = yield* Effect.tryPromise({
151
305
  try: () => script.exports[exportName](...args) as Promise<unknown>,
152
306
  catch: (cause) => new FridaSessionError.FridaSessionError({ when: "rpcCall", cause }),
153
307
  });
154
-
155
308
  yield* Effect.annotateCurrentSpan("result", result);
156
- return result;
309
+ return yield* Schema.decodeUnknown(schema)(result);
157
310
  });
158
311
 
159
312
  yield* Effect.tryPromise({
160
- try: () => script.load(),
313
+ try: (signal) => {
314
+ const cancellable = new Frida.Cancellable();
315
+ signal.onabort = () => cancellable.cancel();
316
+ return script.load(cancellable);
317
+ },
161
318
  catch: (cause) => new FridaSessionError.FridaSessionError({ cause, when: "load" }),
162
319
  });
163
320
 
164
- if (options?.resume === true) {
165
- yield* Effect.tryPromise({
166
- try: () => device.resume(session.pid),
167
- catch: (cause) => new FridaSessionError.FridaSessionError({ cause, when: "resume" }),
168
- });
169
- }
170
-
171
321
  return {
172
322
  sink,
173
- script,
174
323
  stream,
324
+ script,
175
325
  destroyed,
176
326
  callExport,
327
+ scriptError,
177
328
  [FridaScriptTypeId]: FridaScriptTypeId,
178
329
  } as const;
179
330
  },
180
- Effect.acquireRelease(({ script }: FridaScript.FridaScript) => Effect.promise(() => script.unload()))
331
+ Effect.acquireRelease(({ script }: FridaScript.FridaScript) =>
332
+ Effect.promise((signal) => {
333
+ const cancellable = new Frida.Cancellable();
334
+ signal.onabort = () => cancellable.cancel();
335
+ return script.unload(cancellable);
336
+ })
337
+ )
181
338
  )
182
339
  );
183
340
 
184
341
  /** @internal */
185
342
  export const layer = Function.dual<
186
343
  (
187
- options?: (Frida.ScriptOptions & { readonly resume?: boolean | undefined }) | undefined
344
+ options?: FridaScript.LoadOptions | undefined
188
345
  ) => (
189
- source: string | Buffer
190
- ) => Layer.Layer<
191
- FridaScript.FridaScript,
192
- FridaSessionError.FridaSessionError,
193
- FridaSession.FridaSession | FridaDevice.FridaDevice
194
- >,
346
+ entrypoint: URL
347
+ ) => Layer.Layer<FridaScript.FridaScript, FridaSessionError.FridaSessionError, FridaSession.FridaSession>,
195
348
  (
196
- source: string | Buffer,
197
- options?: (Frida.ScriptOptions & { readonly resume?: boolean | undefined }) | undefined
198
- ) => Layer.Layer<
199
- FridaScript.FridaScript,
200
- FridaSessionError.FridaSessionError,
201
- FridaSession.FridaSession | FridaDevice.FridaDevice
202
- >
349
+ entrypoint: URL,
350
+ options?: FridaScript.LoadOptions | undefined
351
+ ) => Layer.Layer<FridaScript.FridaScript, FridaSessionError.FridaSessionError, FridaSession.FridaSession>
203
352
  >(
204
- (arguments_) => Predicate.isString(arguments_[0]),
205
- (
206
- source: string | Buffer,
207
- options?: (Frida.ScriptOptions & { readonly resume?: boolean | undefined }) | undefined
208
- ) => Layer.scoped(Tag, load(source, options))
353
+ (arguments_) => Predicate.hasProperty(arguments_[0], "href"),
354
+ (entrypoint: URL, options?: FridaScript.LoadOptions | undefined) =>
355
+ Layer.scoped(Tag, load(entrypoint, options)).pipe(Layer.provide(Path.layer))
209
356
  );
@@ -1,13 +1,15 @@
1
1
  import type * as Scope from "effect/Scope";
2
- import type * as Frida from "frida";
3
- import type * as FridaSession from "../FridaSession.js";
2
+ import type * as FridaSession from "../FridaSession.ts";
4
3
 
5
4
  import * as Context from "effect/Context";
6
5
  import * as Effect from "effect/Effect";
7
6
  import * as Layer from "effect/Layer";
7
+ import * as Match from "effect/Match";
8
8
  import * as Predicate from "effect/Predicate";
9
- import * as FridaDevice from "../FridaDevice.js";
10
- import * as FridaSessionError from "../FridaSessionError.js";
9
+ import * as Frida from "frida";
10
+
11
+ import * as FridaDevice from "../FridaDevice.ts";
12
+ import * as FridaSessionError from "../FridaSessionError.ts";
11
13
 
12
14
  /** @internal */
13
15
  export const FridaSessionTypeId: FridaSession.FridaSessionTypeId = Symbol.for(
@@ -21,21 +23,70 @@ export const Tag = Context.GenericTag<FridaSession.FridaSession>("@efffrida/frid
21
23
  export const isFridaSession = (u: unknown): u is FridaSession.FridaSession =>
22
24
  Predicate.hasProperty(u, FridaSessionTypeId);
23
25
 
26
+ /** @internal */
27
+ export const frontmost = (
28
+ options?: Frida.FrontmostQueryOptions | undefined
29
+ ): Effect.Effect<Frida.Application, FridaSessionError.FridaSessionError, FridaDevice.FridaDevice> =>
30
+ Effect.flatMap(FridaDevice.FridaDevice, ({ device }) =>
31
+ Effect.tryPromise((signal) => {
32
+ const cancellable = new Frida.Cancellable();
33
+ signal.onabort = () => cancellable.cancel();
34
+ return device.getFrontmostApplication(options, cancellable);
35
+ })
36
+ )
37
+ .pipe(Effect.flatMap(Effect.fromNullable))
38
+ .pipe(Effect.mapError((cause) => new FridaSessionError.FridaSessionError({ cause, when: "attach" })));
39
+
24
40
  /** @internal */
25
41
  export const spawn = (
26
- program: string | Array<string>,
42
+ program: string | ReadonlyArray<string>,
27
43
  options?: Frida.SpawnOptions | undefined
28
- ): Effect.Effect<number, FridaSessionError.FridaSessionError, FridaDevice.FridaDevice | Scope.Scope> =>
29
- Effect.flatMap(FridaDevice.FridaDevice, ({ device }) =>
30
- Effect.acquireRelease(
31
- Effect.tryPromise({
32
- try: () => device.spawn(program, options),
33
- catch: (cause) => new FridaSessionError.FridaSessionError({ cause, when: "spawn" }),
34
- }),
35
- (pid) => Effect.promise(() => device.kill(pid))
36
- )
44
+ ): Effect.Effect<number, FridaSessionError.FridaSessionError, FridaDevice.FridaDevice | Scope.Scope> => {
45
+ const spawnEffect = Effect.flatMap(FridaDevice.FridaDevice, ({ device }) =>
46
+ Effect.tryPromise({
47
+ try: (signal) => {
48
+ const cancellable = new Frida.Cancellable();
49
+ signal.onabort = () => cancellable.cancel();
50
+ return device.spawn(program as string | Array<string>, options, cancellable);
51
+ },
52
+ catch: (cause) =>
53
+ new FridaSessionError.FridaSessionError({
54
+ cause,
55
+ when: "spawn",
56
+ }),
57
+ })
37
58
  );
38
59
 
60
+ const resumeEffect = (pid: number) =>
61
+ Effect.flatMap(FridaDevice.FridaDevice, ({ device }) =>
62
+ Effect.tryPromise({
63
+ try: (signal) => {
64
+ const cancellable = new Frida.Cancellable();
65
+ signal.onabort = () => cancellable.cancel();
66
+ return device.resume(pid, cancellable);
67
+ },
68
+ catch: (cause) =>
69
+ new FridaSessionError.FridaSessionError({
70
+ cause,
71
+ when: "resume",
72
+ }),
73
+ })
74
+ );
75
+
76
+ const release = (pid: number) =>
77
+ Effect.flatMap(FridaDevice.FridaDevice, ({ device }) =>
78
+ Effect.promise((signal) => {
79
+ const cancellable = new Frida.Cancellable();
80
+ signal.onabort = () => cancellable.cancel();
81
+ return device.kill(pid, cancellable);
82
+ })
83
+ );
84
+
85
+ const acquire = Effect.tap(spawnEffect, resumeEffect);
86
+ const resource = Effect.acquireRelease(acquire, release);
87
+ return resource;
88
+ };
89
+
39
90
  /** @internal */
40
91
  export const attach = (
41
92
  target: Frida.TargetProcess,
@@ -44,30 +95,87 @@ export const attach = (
44
95
  FridaSession.FridaSession,
45
96
  FridaSessionError.FridaSessionError,
46
97
  FridaDevice.FridaDevice | Scope.Scope
47
- > =>
48
- Effect.flatMap(FridaDevice.FridaDevice, ({ device }) =>
49
- Effect.acquireRelease(
50
- Effect.map(
51
- Effect.tryPromise({
52
- try: () => device.attach(target, options),
53
- catch: (cause) => new FridaSessionError.FridaSessionError({ cause, when: "attach" }),
98
+ > => {
99
+ const acquire = Effect.flatMap(FridaDevice.FridaDevice, ({ device }) =>
100
+ Effect.tryPromise({
101
+ try: (signal) => {
102
+ const cancellable = new Frida.Cancellable();
103
+ signal.onabort = () => cancellable.cancel();
104
+ return device.attach(target, options, cancellable);
105
+ },
106
+ catch: (cause) =>
107
+ new FridaSessionError.FridaSessionError({
108
+ cause,
109
+ when: "attach",
110
+ }),
111
+ })
112
+ );
113
+
114
+ const release = (session: Frida.Session) =>
115
+ Effect.promise((signal) => {
116
+ const cancellable = new Frida.Cancellable();
117
+ signal.onabort = () => cancellable.cancel();
118
+ return session.detach(cancellable);
119
+ });
120
+
121
+ const resource = Effect.acquireRelease(acquire, release);
122
+ return Effect.map(
123
+ resource,
124
+ (session) =>
125
+ ({
126
+ session,
127
+ resume: Effect.tryPromise((signal) => {
128
+ const cancellable = new Frida.Cancellable();
129
+ signal.onabort = () => cancellable.cancel();
130
+ return session.resume(cancellable);
131
+ }),
132
+ enableChildGating: Effect.tryPromise((signal) => {
133
+ const cancellable = new Frida.Cancellable();
134
+ signal.onabort = () => cancellable.cancel();
135
+ return session.enableChildGating(cancellable);
136
+ }),
137
+ disableChildGating: Effect.tryPromise((signal) => {
138
+ const cancellable = new Frida.Cancellable();
139
+ signal.onabort = () => cancellable.cancel();
140
+ return session.disableChildGating(cancellable);
54
141
  }),
55
- (session) => ({ session, [FridaSessionTypeId]: FridaSessionTypeId }) as const
56
- ),
57
- ({ session }: FridaSession.FridaSession) => Effect.promise(() => session.detach())
58
- )
142
+ setupPeerConnection: (opts?: Frida.PeerOptions | undefined) =>
143
+ Effect.tryPromise((signal) => {
144
+ const cancellable = new Frida.Cancellable();
145
+ signal.onabort = () => cancellable.cancel();
146
+ return session.setupPeerConnection(opts, cancellable);
147
+ }),
148
+ joinPortal: (address: string, opts?: Frida.PortalOptions | undefined) =>
149
+ Effect.tryPromise((signal) => {
150
+ const cancellable = new Frida.Cancellable();
151
+ signal.onabort = () => cancellable.cancel();
152
+ return session.joinPortal(address, opts, cancellable);
153
+ }),
154
+ [FridaSessionTypeId]: FridaSessionTypeId,
155
+ }) as const
59
156
  );
157
+ };
60
158
 
61
159
  /** @internal */
62
160
  export const layer = (
63
- target: string,
161
+ target: number | string | ReadonlyArray<string>,
64
162
  options?: (Frida.SpawnOptions & Frida.SessionOptions) | undefined
65
163
  ): Layer.Layer<FridaSession.FridaSession, FridaSessionError.FridaSessionError, FridaDevice.FridaDevice> =>
66
164
  Layer.scoped(
67
165
  Tag,
68
166
  Effect.gen(function* () {
69
- const pid = yield* spawn(target, options);
167
+ const pid = yield* Match.value(target).pipe(
168
+ Match.when(Match.number, (pid) => Effect.succeed(pid)),
169
+ Match.when(Match.string, (proc) => spawn(proc)),
170
+ Match.orElse((proc) => spawn(proc))
171
+ );
70
172
  const session = yield* attach(pid, options);
71
173
  return session;
72
174
  })
73
175
  );
176
+
177
+ /** @internal */
178
+ export const layerFrontmost = (
179
+ options?: Frida.FrontmostQueryOptions | undefined
180
+ ): Layer.Layer<FridaSession.FridaSession, FridaSessionError.FridaSessionError, FridaDevice.FridaDevice> =>
181
+ Layer.unwrapEffect(Effect.map(frontmost(options), (app) => layer(app.pid)));