Ember.js octane acceptance test

I don’t understand why fillIn can’t find ‘data-test-code-nav-selector’.

I have paused the test and using the devtools I copied this DOM excerpt. It displays as expected. As you can see, the select tag has the expected attribute.

<select label="codingDropdown" class="h-full bg-gray-50 rounded shadow border py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" data-test-code-nav-selector="">
    <option value="PDF_I10_PCS_APPENDICES">ICD-10 PCS Appendices</option>
    <option value="PDF_I10_PCS_REFERENCE_MANUAL">ICD-10-PCS Reference Manual</option>
</select>

Then, in the test.js file, I’m calling fillIn with the same attribute.

await this.pauseTest();
fillIn('data-test-code-nav-selector', 'PDF_I10_PCS_REFERENCE_MANUAL');

But the test server states:

global failure: Error: Element not found when calling `fillIn('data-test-code-nav-selector')`.@ 10974 ms
Source:     
Error: Element not found when calling `fillIn('data-test-code-nav-selector')`.

The Ember.js forum states: ask your question on stackoverflow.

Running tsc on an eslint.config.js raises error

I am running tsc --skipLibCheck with the files below:

eslint.config.js:

// @ts-check
import jslint from "@eslint/js"

export default []

tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "target": "ES5",
    "useDefineForClassFields": true,
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ES2022",
    "skipLibCheck": true,
    "allowJs": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
  },
  "include": ["src"],
  "files": ["eslint.config.js"]
}

I get the following error:

eslint.config - error TS7016: Could not find a declaration file for module '@eslint/js'. './node_modules/@eslint/js/src/index.js' implicitly has an 'any' type.

How to do to ignore type check on imports?

Empty string in an array coming back undefined [closed]

I have narrowed my issue down to the following code. I don’t understand what weird javascript thing is happening here. An explanation would be greatly appreciated.

let test = ["Test"];
test.concat([""]);
console.log(test); //> ["Test", ""]
console.log(test[1]); //> "undefined"  <-Confused by this
test[0] += "1"; //> ["Test1", ""]
test[1] += "1"; //> ["Test1", "undefined1"]  <-How I found it.

As pointed out in the comments, I don’t get why the concatenation says it worked, but then I get an “undefined” instead of an empty “string”?

How do I modify friction to make a Spinning Circle stop at a specific angle?

I am creating a Spinning Wheel game in Javascript. When a button is clicked, a random velocity is set. When the stop-button is clicked, a friction is applied to the velocity. When fricition is activated, every frame this happens:

// friction = 0.98;
velocity = velocity * friction;
theAngle = theAngle + velocity;

When velocity < 0.002, the wheel will stop.

What I want to do here is, is modify the friction variable so that the Wheel stops at an angle I can pick myself.

So far I have succeeded in calculating the stopAngle, and the number of frames the wheel spins.

let angle = theAngle // the current angle (before stopping)
let speed = velocity;
let frames = 0;
let willStopAtAngle = angle;
const targetSpeed = 0.002;
while (speed > targetSpeed) {
speed *= config.friction; 
frames = frames + 1; 
angle = angle + speed; 
}

I have also calculated the angle I want the wheel to stop, and I know this is correct.

const diff = stopAngle - (angle % (Math.PI * 2));
const actualStopAngle = angle + stopAngle

I feel like I know enough, but I just don’t know how to calculate the right friction

friction = (actualStopAngle - theAngle)/frames // I thought something like this might work;

Thanks!

HTML JAVASCRIPT providing links between different html files

This code in adminPage.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Admin Page</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            font-family: Arial, sans-serif;
        }

        nav {
            background-color: #333;
            overflow: hidden;
        }

        nav ul {
            list-style-type: none;
            margin: 0;
            padding: 0;
            overflow: hidden;
        }

        nav ul li {
            float: left;
        }

        nav ul li a {
            display: block;
            color: white;
            text-align: center;
            padding: 15px; /* Küçültüldü */
            text-decoration: none;
        }

        nav ul li a:hover {
            background-color: #555;
        }

        .page {
            display: none;
            padding: 20px;
        }

    </style>
</head>
<body>
<nav>
    <ul>
        <li><a href="#" onclick="showDoctorPage()">Doktor İşlemleri</a></li>
        <li><a href="#" onclick="showHastaPage()">Hasta İşlemleri</a></li>
        <li><a href="#">Rapor İşlemleri</a></li>
    </ul>
</nav>

<div id="doctorPage" style="display: none;">

</div>
<div id="hastaPage" style="display: none;">

</div>

<script>

    function showDoctorPage() {
        // Doktor sayfasını göstermek için işlevi uygula
    }


   function showHastaPage() {
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            document.getElementById("hastaPage").innerHTML = this.responseText;
        }
    };
    xhttp.open("GET", "/admin/page/patients", true); // /admin/page/patients endpoint'ine GET isteği yapılıyor
    xhttp.send();
}

</script>



</body>
</html>

This code in patientOp.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{%  block title  %}Hasta İşlemleri{% endblock %}</title>
    <style>
        .add-form {
            display: none;
            margin-top: 20px;
        }

        form label {
            display: block;
            margin-bottom: 5px;
        }

        form input {
            width: calc(25% - 7px); /* Genişlik küçültüldü */
            padding: 8px;
            margin-bottom: 10px;
            margin-top: 10px; /* Yukarı kaydırma */
            border: 1px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
        }

        form button {
            padding: 8px 16px; /* Küçültüldü */
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }

        button.delete-button {
            background-color: #d42f2f;
            border: none;
            color: white;
            padding: 7px 12px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
        }

        button.add-button {
            background-color: #04AA6D; /* Green */
            border: none;
            color: white;
            padding: 7px 12px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            /* İstediğiniz miktarı ayarlayın */
        }

        button.save-button {
            background-color: #669ee7;
            border: none;
            color: white;
            padding: 7px 12px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
        }

        button.edit-button {
            background-color: #ccf627; /* Green */
            border: none;
            color: white;
            padding: 7px 12px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
        }

        button:hover {
            opacity: 0.8;
        }

        table {
            width: 70%;
            border-collapse: collapse;
            margin: 0;
        }

        table th, table td {
            padding: 6px; /* Küçültüldü */
            border-bottom: 1px solid #ddd;
            text-align: left;
        }

        table th {
            background-color: #464a47;
            color: rgb(255, 255, 255);
        }
    </style>
</head>
<body>
<h1>{% block header %}Hasta İşlemleri{% endblock %}</h1>

{% block content %}
<h1>Hasta Ekle</h1>
<button class="add-button" onclick="showAddHastaForm()">Hasta Ekle</button>
<div id="addHastaForm" class="add-form" style="display: none;">
    <form id="hastaForm" action="{{ url_for('admin_patient_page') }}" method="POST">
        <label for="username">Kullanıcı Adı:</label>
        <input type="text" id="username" name="patient_username"><br>
        <label for="password">Şifre:</label>
        <input type="password" id="password" name="patient_password"><br>
        <label for="firstName">Ad:</label>
        <input type="text" id="firstName" name="patient_name"><br>
        <label for="lastName">Soyad:</label>
        <input type="text" id="lastName" name="patient_surname"><br>
        <label for="birthday">Doğum Tarihi:</label>
        <input type="text" id="birthday" name="patient_birth_date"><br>
        <label for="gender">Cinsiyet:</label>
        <input type="text" id="gender" name="patient_gender"><br>
        <label for="adres">Adres:</label>
        <input type="text" id="adres" name="patient_address"><br>
        <label for="telephone">Tel No:</label>
        <input type="text" id="telephone" name="patient_phone"><br>
        <button type="submit" class="save-button">Kaydet</button>
    </form>
</div>
<div id="hastaList">
    <h2>Eklenen Hastalar</h2>
    <table id="hastalar">
        <thead>
        <tr>
            <th>Kullanıcı Adı</th>
            <th>Şifre</th>
            <th>Ad</th>
            <th>Soyad</th>
            <th>Doğum Tarihi</th>
            <th>Cinsiyet</th>
            <th>Adres</th>
            <th>Tel No</th>
            <th>İşlemler</th>
        </tr>
        </thead>
        <tbody>
        {% for patient in patients %}
        <tr>
            <td>{{ patient.patient_username }}</td>
            <td>{{ patient.patient_password }}</td>
            <td>{{ patient.patient_name }}</td>
            <td>{{ patient.patient_surname }}</td>
            <td>{{ patient.patient_birth_date }}</td>
            <td>{{ patient.patient_gender }}</td>
            <td>{{ patient.patient_address }}</td>
            <td>{{ patient.patient_phone }}</td>
            <td>
                <button class="edit-button" onclick="editHasta(this)">Düzenle</button>
                <form action="{{ url_for('delete_patient', patient_id=patient.patient_id) }}" method="POST" style="display: inline;">
                    <button type="submit" class="delete-button">Sil</button>
                </form>
            </td>
        </tr>
        {% endfor %}
        </tbody>
    </table>
</div>

{% endblock %}
<script>
    function showAddHastaForm() {
        var addHastaForm = document.getElementById('addHastaForm');
        addHastaForm.style.display = 'block';

        // Formun içindeki inputları temizle
        var inputs = addHastaForm.querySelectorAll('input');
        inputs.forEach(input => {
            input.value = '';
        });
    }

    function editHasta(button) {
        var row = button.parentNode.parentNode;
        var cells = row.getElementsByTagName('td');
        var username = cells[0].textContent;
        var password = cells[1].textContent;
        var firstName = cells[2].textContent;
        var lastName = cells[3].textContent;
        var birthday = cells[4].textContent;
        var gender = cells[5].textContent;
        var adres = cells[6].textContent;
        var telephone = cells[7].textContent;

        document.getElementById('username').value = username;
        document.getElementById('password').value = password;
        document.getElementById('firstName').value = firstName;
        document.getElementById('lastName').value = lastName;
        document.getElementById('birthday').value = birthday;
        document.getElementById('gender').value = gender;
        document.getElementById('adres').value = adres;
        document.getElementById('telephone').value = telephone;

        row.parentNode.removeChild(row);
    }
</script>

</body>
</html>

I can’t make a connection between two different codes here.

First of all, when I click on ‘Patient Operations’ in the adminPage, I want the interface in patientOp.html to appear. However, this interface must go directly under the bar. Additionally, they both need to be in different HTML files. How can I do that?

Multiplication in Nested For Loops

Can someone please explain this function and it operation for my understanding. I came a crossed this exercise and I did it perfectly but, I am still having problem understanding how the 30 resulted in the console output. The parameter replacement value are nested array which index are 1, 3, and 5. The variable age is multiple by the parameter as display in the code below. A brief explanation over the code below will be a great help.

const jboy =function(am){
  let age = 2;
  for(let k = 0; k < am.length; k++){
    for(let ek = 0; ek < am[k].length; ek++){
      age = age * am[k][ek];
    }
  }
  return age;
};

console.log(jboy([[1], [3], [5]])) // output = 30

I tried understanding the above code and it operation but, I still have some holes in my foundation.

How to process streaming data from a node API in Nextjs 14?

I have a streaming endpoint that is an LLM and gives a response like gpt that I am hitting using my proxy node server. This is my nodejs post API code:

  const postData = {
    messages: messages,
    model_name,
    stream: true,
    temperature: temperature,
    top_p: 1,
  };

  try {
    const headers = {
      "Content-Type": "application/json",
      apiKey: process.env.LLM_API_KEY,
    };

    const response = await axios.post(llmChatUri, postData, {
      headers: headers,
      responseType: "stream",
    });

    res.setHeader("Content-Type", "application/json");
    response.data.pipe(res);

I want to get the data the API returns in nextjs frontend chunk by chunk. The problem I face is with parsing the data as some chunks are lost resulting in data loss.

I tried using socket io and more techniques to turn data into a JSON obj so that I can extract the answer one by one and show in the frontend but either I face data loss and data transformation from string to JSON has been as hassle as well.

Allow only one . (dot) to Regex

I am using below regex for my project to validate customer name.

ALPHA_SPECIAL_CHAR = /^[a-zA-Z'"-sçéâêîôûàèìòùëïüÇÉÂÊÎÔÛÀÈÌÒÙËÏÜ]+$/

Now I want to add new requirement which will allow only one dot (.) to this regex. How to modify this existing regex.

E.g

name = JOHN. // this should be valid
name = JOHN.. // this should be invalid

process is not defined — ReferenceError

Resolving the “process is not defined” error in a Create React App project required configuring webpack’s resolve.fallback option to provide polyfills or mock implementations for Node.js core modules that are not available in the browser environment.

const { override } = require('customize-cra');
const webpack = require('webpack');

module.exports = override((config) => {
  config.resolve.fallback = {
    ...config.resolve.fallback,
    'process': false, // Set 'process' to false instead of using a mock
    'zlib': require.resolve('browserify-zlib'),
    'querystring': require.resolve('querystring-es3'),
    'stream': require.resolve('stream-browserify'),
    'path': require.resolve('path-browserify'),
    'fs': false,
    'timers': require.resolve('timers-browserify'),
    'crypto': require.resolve('crypto-browserify'),
    'http': require.resolve('stream-http'),
    'https': require.resolve('https-browserify'),
    'net': require.resolve('net-browserify'),
    'url': require.resolve('url/'),
    'assert': require.resolve('assert/'),
    'vm': require.resolve('vm-browserify'),
    'async_hooks': false,
    'child_process': false,
  };

  // Exclude the 'destroy' package
  config.module.rules.push({
    test: //node_modules/destroy//,
    use: 'null-loader',
  });

  // Resolve conflicting values for 'process.env'
  config.plugins.push(
    new webpack.EnvironmentPlugin({
      NODE_ENV: process.env.NODE_ENV === 'debug',
    })
  );

  return config;
});

Error That i Get

process is not defined
ReferenceError: process is not defined
    at ./node_modules/readable-stream/lib/_stream_writable.js (http://localhost:3000/static/js/bundle.js:130687:18)
    at options.factory (http://localhost:3000/static/js/bundle.js:227078:31)
    at __webpack_require__ (http://localhost:3000/static/js/bundle.js:226477:32)
    at fn (http://localhost:3000/static/js/bundle.js:226736:21)
    at ./node_modules/readable-stream/readable-browser.js (http://localhost:3000/static/js/bundle.js:131512:20)
    at options.factory (http://localhost:3000/static/js/bundle.js:227078:31)
    at __webpack_require__ (http://localhost:3000/static/js/bundle.js:226477:32)
    at fn (http://localhost:3000/static/js/bundle.js:226736:21)
    at ./node_modules/browserify-sign/browser/index.js (http://localhost:3000/static/js/bundle.js:33021:14)
    at options.factory (http://localhost:3000/static/js/bundle.js:227078:31)
    at __webpack_require__ (http://localhost:3000/static/js/bundle.js:226477:32)
    at fn (http://localhost:3000/static/js/bundle.js:226736:21)
    at ./node_modules/crypto-browserify/index.js (http://localhost:3000/static/js/bundle.js:40757:12)
    at options.factory (http://localhost:3000/static/js/bundle.js:227078:31)
    at __webpack_require__ (http://localhost:3000/static/js/bundle.js:226477:32)
    at fn (http://localhost:3000/static/js/bundle.js:226736:21)
    at ./node_modules/etag/index.js (http://localhost:3000/static/js/bundle.js:58075:14)
    at options.factory (http://localhost:3000/static/js/bundle.js:227078:31)
    at __webpack_require__ (http://localhost:3000/static/js/bundle.js:226477:32)
    at fn (http://localhost:3000/static/js/bundle.js:226736:21)

Report WhatsApp py

I try write script to report number in WhatsApp business
that scans for spam or other illegal activity
I can’t get to the whatsapp request to report a user, I would appreciate some help and how to get to the request

hello ,
I try write script to report number in WhatsApp business
that scans for spam or other illegal activity
I can’t get to the whatsapp request to report a user, I would appreciate some help and how to get to the request
Thank you

Characters in non-English language packs not being logged by keypress

I have changed my keyboard to a non-English language (Korean) and am looking to log characters inside my onKeyDown.

I came across this stack overflow post about using keypress instead but wasn’t able to get that to work either. Here is my jsfiddle.

I have a div with a keypress event. If you place your cursor/place focus on the blue div and start typing, you’d notice that irrespective of the language, it still logs English characters on the console.

$("#divtarget").keypress(function(event) {
var charCode = event.which; // charCode will contain the code of the character inputted
var theChar = String.fromCharCode(charCode); // theChar will contain the actual character
  console.log(charCode, theChar);
});

How can I get Korean characters to show?

passport.js req.user is undefined in every route expect for one

I am very new to backend so forgive me if this sounds really basic but I am trying to use passport-google-oauth20 to let users log in through Google and then pass the user information to the ‘/’ route.

This is the part of my passport.js file:

passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.CLIENT_ID!,
      clientSecret: process.env.CLIENT_SECRET!,
      callbackURL: process.env.CALLBACK_URL,
    },
    async (accessToken, refreshToken, profile, done) => {
      // Get the user data from Google
      const newUser = {
        googleId: profile.id,
        displayName: profile.displayName,
      };

      try {
        // Check if the user exists in the database
        let user = await User.findOne({ googleId: profile.id });
        if (user) {
          done(null, user);
        } else {
          // Add the user to the database
          user = await User.create(newUser);
          done(null, user);
        }
      } catch (err) {
        console.error(err);
      }
    },
  ),
);

passport.serializeUser(function (user, done) {
  done(null, user);
});

passport.deserializeUser(async function (id, done) {
  const user = await User.findById(id);
  done(null, user);
});

Deserializing works great cause I checked and it returns the user info, but when the user gets redirected after a successful callback I can’t access req.user in any of the routes.
Those are my routes:


// Google auth
authRouter.get(
  '/auth/google',
  passport.authenticate('google', { scope: ['profile'] }),
);

authRouter.get(
  '/auth/google/callback',
  passport.authenticate('google', {
    failureRedirect: process.env.CLIENT_URL }),
  (req: Request, res: Response) => {
    // Successful authentication, redirect home
    res.redirect(process.env.CLIENT_URL!);
  },
);

I made this just to see if req.user would work here but it is undefined.

authRouter.get('/', (req: Request, res: Response) => {
  console.log(req.user); 
});

I can’t access req.user anywhere outside of the ‘auth/google/callback’ route. What am I doing wrong?

const app = express();
app.use(
  cors({
    origin: process.env.CLIENT_URL,
    methods: 'GET,POST,PUT,DELETE',
    credentials: true,
  }),
);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(
  session({
    secret: process.env.SECRET!,
    resave: false,
    saveUninitialized: true,
  }),
);
app.use(passport.initialize());
app.use(passport.session());

connectDB();
app.use(authRouter);

Please help, I’ve looked everywhere.

How to use the Frida Interceptor from Python

I working on a plugin for Binary Ninja where one of the features is to trace functions using Frida. The plugin is written in python (using python 3.10) but the Frida commands are in JavaScript. I am trying to load some JS code and make Frida run it (It is part of my understanding that Frida provides its own VM for JS, but I may have misunderstood).

This code is an object representing the function being traced. The trace_template is the JS code I want to run.

import frida

class Tracee:
    session: frida.core.Session
    pid: int
    active: bool
    def __init__(self, session, pid):
        self.session = session
        self.pid = pid
        self.active = False

    def __eq__(self, other):
        if not isinstance(other, Tracee):
            return NotImplemented
        return self.pid == other.pid

    def __hash__(self):
        return hash(self.pid)

trace_template = """
const base_mod = Process.getModuleByName('{binary_name}').base;
const func_ptr = ptr(base_mod.add({func_addr}));

Interceptor.attach(func_ptr, {{
    onEnter: function(args) {{
        console.log('Entered function! had argument: ', args[0].toInt32().toString(16));
        var msg = {{
            'event': 'call',
            'args': args[0].toInt32().toString(16)
        }};
        send(msg);
    }},
    onLeave: function(retval) {{
        console.log('Returning from function: ', retval.toInt32().toString(16));
        var msg = {{
            'event': 'return',
            'args': retval.toInt32().toString(16)
        }};
        send(msg);
    }}
}});
send({{
    'maybeidk': DebugSymbol.fromName('{func_name}')
}})
"""

This is the function which uses the trace_template and is supposed to run it.

def trace_functions(self, tracee: Tracee):
    t = self.targets[0]
    fn = self.bv.file.original_filename.split('/')[-1]
    formatted = trace_template.format(
                    binary_name=fn,
                    func_addr=t.lowest_address,
                    func_name=t.name)
    slog.log_warn(formatted)
    script = tracee.session.create_script(formatted)
    script.on('message', lambda message,data: self._reactor.schedule(
            lambda: self._on_message(tracee.pid, message)
    ))
    script.load()
    self._device.resume(tracee.pid)

And that function is part of this file of code, which I’m adding in case more context is necessary:

import json
import binaryninja as bn
import frida
import time
import threading

from enum import Enum
from pathlib import Path
from frida_tools.application import Reactor
from typing import Optional, List, Callable
from json import loads

from .settings import SETTINGS
from .log import system_logger as slog, gadget_logger as glog
from .helper import TRACE_TAG, get_functions_by_tag, mark_func
from .trace import Tracee, trace_template

__portal_thread = None
_stop_event = threading.Event()

class PortalAction(Enum):
    INIT = 1
    TRACE = 2

def stop_server(bv: bn.BinaryView) -> None:
    """
    Stops server with stop_event
    """
    global __portal_thread, _stop_event
    if __portal_thread is None:
        slog.log_warn("Portal thread not up")
        bn.show_message_box(
                "Error: Portal not instantiated",
                "Frida-portal thread is not running.",
                bn.MessageBoxButtonSet.OKButtonSet,
                bn.MessageBoxIcon.ErrorIcon)
        return
    slog.log_info("Stopping server...")
    _stop_event.set()
    __portal_thread.join()
    __portal_thread = None
    _stop_event.clear()
    slog.log_info("Server closed.")

def mark_function(bv: bn.BinaryView, func: bn.Function):
    """
    Uses helper to mark function and schedule a reload of all marked functions
    """
    global __portal_thread
    mark_func(bv, func)
    if is_portal_running():
        __portal_thread.app.schedule_reload()

def is_portal_running() -> bool:
    return __portal_thread is not None and __portal_thread.is_alive()

def clear_traced_functions(bv: bn.BinaryView):
    if not is_portal_running():
        slog.log_warn("No functions to clear!")
        return
    for mark in __portal_thread.app.targets:
        mark_func(bv, mark)
    __portal_thread.app.schedule_reload()


def start_server(bv: bn.BinaryView) -> None:
    """
    Start server thread with PortalApp running.
    Run thread as daemon to not block and be closed when exiting.
    """
    global __portal_thread
    if __portal_thread is not None:
        bn.show_message_box(
                "Error",
                "Error: Portal already running",
                bn.MessageBoxButtonSet.OKButtonSet,
                bn.MessageBoxIcon.ErrorIcon)
        slog.log_warn("Portal server already created!")
    else:
        slog.log_info('Starting frida portal...')
        __portal_thread = threading.Thread(target=instance_app, args=(bv, ))
        __portal_thread.daemon = True
        __portal_thread.start()

def instance_app(bv: bn.BinaryView) -> None:
    global __portal_thread
    app = PortalApp(bv)
    __portal_thread.app = app
    app.run()

class PortalApp:
    """
    Portal app based on example found under frida_python
    Uses Reactor from frida_tools to handle async tasks
    """
    bv: bn.BinaryView
    _reactor: Reactor
    _session: Optional[frida.core.Session]
    cluster_params: frida.EndpointParameters
    control_params: frida.EndpointParameters
    action: PortalAction
    targets: List[bn.Function]
    tracees: List[Tracee]

    def __init__(self, bv: bn.BinaryView):
        self._reactor = Reactor(run_until_return=self.handle)
        self.bv = bv
        try:
            cluster_ip      = bv.query_metadata('fridalens_cluster_ip')
            cluster_port    = int(bv.query_metadata('fridalens_cluster_port'))
            control_ip      = bv.query_metadata('fridalens_control_ip')
            control_port    = int(bv.query_metadata('fridalens_control_port'))
        except:
            print('No saved settings, using defailtnAddress=127.0.0.1ncluster port=27052ncontrol port=27042')
            cluster_ip      = "127.0.0.1"
            cluster_port    = 27052
            control_ip      = "127.0.0.1"
            control_port    = 27042

        cluster_params = frida.EndpointParameters(
            address=cluster_ip,
            port=cluster_port
        )
        control_params = frida.EndpointParameters(
            address=control_ip, port=control_port, authentication=None)

        service = frida.PortalService(cluster_params, control_params)
        self._service = service
        self._device = service.device
        self._session = None
        self.action = PortalAction.INIT
        self.targets = get_functions_by_tag(bv, TRACE_TAG)
        self.tracees = []

        service.on("node-connected", lambda *args: self._reactor.schedule(
            lambda: self._on_node_connected(*args)
            ))
        service.on("node-joined", lambda *args: self._reactor.schedule(
            lambda: self._on_node_joined(*args)
            ))
        service.on("node-left", lambda *args: self._reactor.schedule(
            lambda: self._on_node_left(*args)
            ))
        service.on("node-disconnected", lambda *args: self._reactor.schedule(
            lambda: self._on_node_disconnected(*args)
            ))
        service.on("message", lambda *args: self._reactor.schedule(
            lambda: self._on_message(*args)
            ))
        service.on("controller-connected", lambda *args: self._reactor.schedule(
            lambda: self._on_controller_connected(*args)
            ))
        service.on("controller-disconnected", lambda *args: self._reactor.schedule(
            lambda: self._on_controller_disconnected(*args)
            ))
        service.on("subscribe", lambda *args: self._reactor.schedule(
            lambda: self._on_subscribe(*args)
            ))

    def _on_node_connected(self, connection_id, remote_addr):
        slog.log_info(f"Node connected: {connection_id}, Address: {remote_addr}")

    def _on_node_disconnected(self, connection_id, remote_addr):
        slog.log_info(f"Node disconnected: {connection_id}, Address: {remote_addr}")

    def _on_detached(self, pid: int, reason: str):
        slog.log_info(f"[DETACH] Tracee: {pid} reason: {reason}")

    def _on_node_joined(self, connection_id, app):
        """
        Happens along with connecting, but passes application info
        Do most of logic here
        """
        slog.log_info(f"Node joined: {connection_id}, Application: {app}")
        sesh = self._device.attach(app.pid)
        #TODO
        sesh.on("detached",
                lambda reason: self._reactor.schedule(
                    lambda: self._on_detached(app.pid, reason)
            ))
        tracee = Tracee(sesh, app.pid)
        if tracee not in self.tracees:
                self.tracees.append(tracee)
                self.action = PortalAction.TRACE
        else:
            slog.log_warn(f"Repeated tracee triggered node_joined; PID: {tracee.pid}")

    def _on_node_left(self, connection_id, app):
        """
        Happens along with disconnecting, but passes application info
        Do most of logic here
        """
        slog.log_info(f"Node left: {connection_id}, Address: {app}")
        self.tracees = [tracee for tracee in self.tracees if tracee.pid != app.pid]
        #TODO rename to idle or something similar
        if len(self.tracees) == 0:
            self.action = PortalAction.INIT

    def _on_controller_connected(self, connection_id, remote_addr):
        slog.log_info(f"Controller Connected: {connection_id}, Address: {remote_addr}")

    def _on_controller_disconnected(self, connection_id, remote_addr):
        slog.log_info(f"Controller disconnected: {connection_id}, Address: {remote_addr}")


    def _on_message(self, pid: int, msg: str):
        """
        Takes messages sent from gadgets/controllers and handles them
        Msg is sent as str, convert to dict using json.loads()
        """
        msg = loads(msg)
        match msg['type']:
            case "log":
                payload = msg['payload']
                level = msg['level']
                glog.log_info(f"[PID {pid}-{level}] {payload}")
            case _:
                glog.log_warn(f"[PID {pid}] {msg}")

    def _on_subscribe(self, connection_id):
        slog.log_info(f'New subscription: {connection_id}')

    def run(self):
        slog.log_info("Starting portal run")
        self._reactor.schedule(self._start)
        self._reactor.run()

    def _start(self):
        self._service.start()
        self._device.enable_spawn_gating()

    def _stop(self):
        for tracee in self.tracees:
            self._reactor.schedule(lambda: tracee.session.detach())
        self._service.stop()

    def _reload(self):
        targets = get_functions_by_tag(self.bv, TRACE_TAG)
        self.targets = targets
        slog.log_info(f"Targets found: {targets}")
        if len(self.tracees) > 0:
            self.action = PortalAction.TRACE

    def schedule_reload(self):
        """
        KISS for now ig
        """
        self._reactor.schedule(self._reload)

    def trace_functions(self, tracee: Tracee):
        t = self.targets[0]
        fn = self.bv.file.original_filename.split('/')[-1]
        formatted = trace_template.format(
                        binary_name=fn,
                        func_addr=t.lowest_address,
                        func_name=t.name)
        slog.log_warn(formatted)
        script = tracee.session.create_script(formatted)
        script.on('message', lambda message,data: self._reactor.schedule(
                lambda: self._on_message(tracee.pid, message)
        ))
        script.load()
        self._device.resume(tracee.pid)

    def handle(self, reactor):
        global _stop_event
        while not _stop_event.is_set():
            match self.action:
                case PortalAction.INIT:
                    slog.log_info("Idling...")
                    if len(self.targets) > 0:
                        slog.log_info(f"Functions marked: {self.targets}")
                case PortalAction.TRACE:
                    slog.log_info(f"Tracees: {list(map(lambda t: t.pid, self.tracees))}")
                    if len(self.targets) <= 0:
                        slog.log_warn("No functions to trace yet!")
                    else:
                        for t in list(filter(lambda x: not x.active, self.tracees)):
                            t.active = True
                            self._reactor.schedule(
                                    lambda: self.trace_functions(t)
                            )
            time.sleep(1)
        slog.log_info("Finishing up")
        self._stop()