airdcpp-web / airdcpp-apisocket-js

Javascript connector for AirDC++ Web API
7 stars 0 forks source link

Socket authentication in standalone mode #15

Closed rishighan closed 3 years ago

rishighan commented 3 years ago

Based on the docs here: https://github.com/airdcpp-web/airdcpp-apisocket-js/blob/master/GUIDE.md#connect

I have started a POC for a simple test task of post ing a message to a hub. So far, this is what I have naively done:

./utils/airdcpp.socket.service.ts

import { Socket } from "airdcpp-apisocket";
import WebSocket from "ws";

const options = {
  url: "wss://xxx.xxx.com/api/v1/",
  autoReconnect: false,
  reconnectInterval: 5,
  logLevel: "verbose",
  ignoredListenerEvents: [
    "transfer_statistics",
    "hash_statistics",
    "hub_counts_updated",
  ],
};

const APISocket = Socket(options, WebSocket as any);
type SocketType = typeof APISocket;
export { SocketType as APISocket };

export default APISocket;

server.ts

import SocketService from "./utils/airdcpp.socket.service";

SocketService.post("wss://x.x.x.x:5601/session/authorize", {
  username: "admin",
  password: "password",
});

const foo = async () => {
  SocketService.post("/hubs/chat_message", {
    text: "Sent through WebSocket",
    hub_urls: ["nmdcs://foo.foo:411"],
  });
};

The assumption here is that SocketService would give me a socket that has already established a connection with the AirDC++ socket server. Please correct me if I assumed wrong. Presumably, I am not following the correct flow, because I get this:

Attempting to send request on a non-authenticated socket: /hubs/chat_message

...

UnhandledPromiseRejectionWarning: Not authorized

What is the correct flow to achieve what I am trying to do?

maksis commented 3 years ago

You should call the connect method instead of wss://x.x.x.x:5601/session/authorize, similar to how it's done in https://github.com/airdcpp-web/airdcpp-apisocket-js#minimal-example

await SocketService.connect("admin", "password)

You may also provide the credentials in the options that are being passed to the constructor as it's done in the example and then you can call connect without any arguments.

rishighan commented 3 years ago

Cool, so something like this then?

const foo = SocketService.connect("admin", "password");
foo.then((data) => {
  SocketService.post("/hubs/chat_message", {
    text: "Sent through WebSocket",
    hub_urls: ["nmdcs://foo.net:411"],
  });
});

Note: Using .then() because top level await is causing tslint problems. It worked and this is what was shown in the hub:

Your message will be delayed for proxy lookup of your IP address.
19:09:18
li***
Sent through WebSocket

Is this the right pattern?

maksis commented 3 years ago

If it works, yeah. I haven't tested it, but I don't see why the example shown in the README wouldn't work also in the browser:

const API = require('airdcpp-apisocket');

const settings = {
    url: 'ws://localhost:5600/api/v1/',
    username: 'exampleuser',
    password: 'examplepass'
};

const socket = API.Socket(settings, WebSocket);

const onUserConnected = function(user) {
    // Show a status message in that particular hub
    socket.post('hubs/status_message', {
        hub_urls: [ user.hub_url ],
        text: user.nick + ' joined the hub',
        severity: 'info'
    });
};

socket.onConnected = function() {
    socket.addListener('hubs', 'hub_user_connected', onUserConnected);
};

socket.connect();

Very much the only difference to using the socket in a Node.js app is that you should use browser's native websocket implementation instead of a custom websocket dependency (as Node.js doesn't have a native websocket implementation).

rishighan commented 3 years ago

Thanks, I was able to perform a search operation successfully;

// I had to do this, since tslint complained about properties on the instance object.
interface SearchInstance {
  current_search_id: string;
  expires_in: number;
  id: number;
  owner: string;
  query: Record<string, unknown>;
  queue_time: number;
  queued_count: number;
  result_count: number;
  searches_sent_ago: number;
}

const foo = SocketService.connect("admin", "password");
foo.then(async (data) => {
  const instance: SearchInstance = await SocketService.post("search");
  await sleep(10000);

  const searchInfo = await SocketService.post(
    `search/${instance.id}/hub_search`,
    {
      query: {
        pattern: "H.P. Lovecraft",
        file_type: "compressed",
        extensions: ["cbz", "cbr"],
      },
      hub_urls: [
        "nmdcs://foo:411",
        "dchub://foo.yoo.net",
        "dchub://foo.1",
      ],
      priority: 1,
    },
  );
  await sleep(10000); // <- this was necessary
  const results = await SocketService.get(`search/${instance.id}/results/0/5`);
  console.log(results);
});

2 quick follow-up questions:

  1. I had to put a timeout of 10 seconds before querying for the search results, otherwise I get 0. Is this normal?
  2. Is there a pattern to teardown the socket connection after the search results have been retrieved and dealt with?
maksis commented 3 years ago
  1. I had to put a timeout of 10 seconds before querying for the search results, otherwise I get 0. Is this normal?

I recommend reading the introduction at https://airdcpp.docs.apiary.io/#reference/searching and the examples provided for a more reliable way to handle searching

  1. Is there a pattern to teardown the socket connection after the search results have been retrieved and dealt with?

Calling the disconnect method should be enough

rishighan commented 3 years ago

Cool, thanks for your help with this, closing this "issue", since the original one has been addressed.