class Detector::Javascript::SocketIO

Overview

Detects a Socket.IO server: an import ... from "socket.io" / require("socket.io") (the server package; the browser client is socket.io-client, a different specifier), or a new Server( construct paired with a Socket.IO-only API call. Gates the Socket.IO analyzer, which emits inbound socket.on events as ws:// realtime endpoints.

Defined in:

detector/detectors/javascript/socketio.cr

Constant Summary

NEW_SERVER = /new Server\(/
PACKAGE_MARKER = /"socket\.io"\s*:/
SIGNAL = Regex.union(/from\s+['"]socket\.io['"]/, /require\(\s*['"]socket\.io['"]\s*\)/)
SOCKET_IO_API = Regex.union(/\.\s*of\s*\(\s*['"`]\//, /\.\s*broadcast\s*\.\s*emit\s*\(/, /\.\s*to\s*\((?:[^()]|\([^()]*\))*\)\s*\.\s*emit\s*\(/, /\b(?:io|socket)\s*\.\s*emit\s*\(/)

Socket.IO's own server API, required alongside the generic new Server( construct below.

new Server( used to be paired with a bare .on(, and neither name belongs to Socket.IO: ws, engine.io, @grpc/grpc-js, node:http and node:net all export a Server class, and .on( is in nearly every Node file. A plain ws server — const { Server } = require('ws')socket.on('message', …) — was therefore detected as Socket.IO, and the Socket.IO analyzer then reported its socket.on handlers as realtime ws:// endpoints that do not exist.

Namespaces (io.of("/admin")), room targeting (io.to(room).emit(…)), socket.broadcast.emit(…) and the io/socket emitters themselves have no counterpart in those libraries — a ws socket is written to with .send(, and a gRPC or node:net server has no emit surface at all. The room argument allows one level of nesting so io.to(roomFor(id)).emit(…) still counts.

Class Method Summary

Instance Method Summary

Instance methods inherited from class Detector

applicable?(filename : String) : Bool applicable?, base_relative_path(filename : String) : String base_relative_path, content_matches?(file_contents : String, markers : Regex) : Bool content_matches?, detect(filename : String, file_contents : String) : Bool detect, gemfile_dependency?(file_contents : String, gem_name : String) : Bool gemfile_dependency?, gemspec_dependency?(file_contents : String, gem_name : String) : Bool gemspec_dependency?, idempotent? : Bool idempotent?, logger : NoirLogger logger, name : String name, path_sensitive? : Bool path_sensitive?, record_unparsable_document(filename : String, error : Exception) : Nil record_unparsable_document

Constructor methods inherited from class Detector

new(options : Hash(String, YAML::Any)) new

Macros inherited from class Detector

detector_for(tech, extensions = nil, basenames = nil, path_segments = nil, idempotent = nil) detector_for

Class Method Detail

def self.tech_name : String #

The tech name without needing an instance, so the registry can be read off the classes themselves rather than from a parallel list.


[View source]

Instance Method Detail

def applicable?(filename : String) : Bool #
Description copied from class Detector

Cheap filename-only filter the detector pass uses to skip #detect on files the detector cannot possibly match. The default true preserves prior behavior (every detector runs on every file). Override with the same predicate the body of #detect starts with — e.g., filename.ends_with?(".py") for a Python framework detector — so the detector loop avoids the #detect dispatch on files outside the detector's language.

On large codebases (saleor's 4255 .py files) this lifts ~100 virtual #detect calls per file out of the hot loop because most detectors' inner first-line is exactly this kind of cheap filename check.


[View source]
def detect(filename : String, file_contents : String) : Bool #

[View source]
def set_name #

[View source]