cleanup names for dirs

This commit is contained in:
bt3gl 2022-03-23 18:32:49 +04:00
parent 0904ec1dc4
commit 0da3cbd74a
80 changed files with 62 additions and 2263 deletions

62
maze-puzzle/README.md Normal file
View file

@ -0,0 +1,62 @@
# Follower Maze Code Challenge Instructions
Follower-Maze is a **client-server application** built for social networking.
Users follow other users, post messages, send private messages, unfollow users, and so on.
* Each of these actions is an _event_, created by an _event-source_ and sent to the _server_.
* Each event is processed by the _server_.
* The processing of the event may have follow-on effects on other _user-clients_.
The **server** listens for two kinds of **clients**:
* **1 event source**, which emits events.
* **N user-clients** that connect to the server.
#### Client/Server Protocol
* The protocol is line-based i.e. a LF control character terminates each message.
All strings are encoded in UTF-8.
* Data is transmitted over TCP sockets.
* The _event source_ connects on port 9090 and will start sending events as soon as the connection is accepted.
* The _user clients_ connect on port 9099 and identify themselves with their user ID.
For example, once connected a _user client_ may send down `2932\r\n`,
indicating that it is representing user 2932.
* After the identification is sent,
the user client starts waiting for events to be sent to them by the server.
#### Events
There are five possible events.
The table below describes payloads sent by the event source and what each represents:
```
| Payload | Sequence # | Type | From User Id | To User Id |
|---------------|------------|---------------|--------------|------------|
| 666|F|60|50 | 666 | Follow | 60 | 50 |
| 1|U|12|9 | 1 | Unfollow | 12 | 9 |
| 542532|B | 542532 | Broadcast | - | - |
| 43|P|32|56 | 43 | Private Msg | 32 | 56 |
| 634|S|32 | 634 | Status Update | 32 | - |
```
#### Event Rules
* Each message begins with a sequence number.
* However, the event source _does not send events in any given order_.
In particular, sequence number has no effect on the order in which events are sent.
* Events _may_ generate notifications for _user clients_.
If there is a respective target _user client_ connected,
the notification is routed according to the following rules:
* Follow: Only the `To User Id` is notified
* A follow event from user A to user B means that user A now follows user B.
* Unfollow: No clients are notified
* An unfollow event from user A to user B means that user A stopped following user B.
* Broadcast: All connected _user clients_ are notified
* Private Message: Only the `To User Id` is notified
* Status Update: All current followers of the `From User ID` are notified
* If there are no _user clients_ connected for a user,
any notifications for them are currently ignored.
_User clients_ are notified of events _in the correct sequence-number order_,
regardless of the order in which the _event source_ sent them.

View file

@ -1,3 +0,0 @@
.python-version
__pycache__
*.pyc

View file

@ -1,50 +0,0 @@
## SoundCloud backend engineering code review interview exercise
### Introduction
Welcome to the SoundCloud backend Pull Request review challenge!
This repository contains a simulation of a microservice. The service is
intentionally simplistic. The code for the service is python, but only using
basic aspects of the language.
We intend this to read like pseudocode - and in particular
_we do not judge your submission based on your knowledge of Python_. Exposure to
any popular programming language(s) should be enough to effectively work this exercise.
You will be reviewing Pull Request #1 (present under the "Pull Requests" tab).
It's meant to be a simplified simulation of the sort of code review you might
encounter in your everyday work.
> Also see: [a quick introduction to GitHub pull requests](https://help.github.com/en/articles/about-pull-request-reviews#about-pull-request-reviews).
## Your challenge
The code should be robust and reasonably feature-complete.
We only want to merge this Pull Request once you and your (imaginary) colleague are confident that:
- The implementation actually supports the requested feature.
- The implementation is likely to work, including proper handling of
easily-foreseeable corner cases.
- The implementation is not easy for a mailcious user to attack.
- Other engineers can productively and confidently change this code, meaning:
- The code is clear and straightforward to reason through.
- It has high-quality unit tests and good test coverage.
- It's well-organized and code elements have intention-revealing names.
- There are comments, where those are useful.
In short, we are interested in gaining some insight into what is important to
you when you review code. We'll be concentrating especially on what you feel
is important to change, and the way you communicate that. Please provide change
requests, ask questions, and otherwise make comments as you normally would when
reviewing code.
The only GitHub review features that matter in this exercise are
commenting-related: commenting on the whole Pull Request, and comments targeted at specific
areas of the code. If you prefer to make use of Github's markdown features
in your comments, please feel free to do so - but if you haven't used these features
before, please do not use them.
As a general guideline, this exercise should take about 30-60 minutes. You are free
to take more (or less) time than that, however.
When you're done, please notify your SoundCloud recruiter contact via email.

Binary file not shown.

View file

@ -1,218 +0,0 @@
# Follower Maze Code Challenge Instructions
Thank you for applying for a backend engineering position at SoundCloud.
We ask engineering candidates to work and complete this code challenge.
Your submission is part of the basis upon which we evaluate your candidacy.
We hope its indeed challenging (and maybe even fun).
The challenge has two parts:
1. **Refactor** an existing client-server application and provide a README explaining your approach
1. **Extend** the server application with a small feature request
Our aim with these instructions is to leave no doubt about the details of the challenge and the expectations of
your submission - however, if you have questions of any sort _please ask_.
Also: please completely read the instructions before starting!
## Time Expectations
Based on internal testing, we roughly estimate the full Follower-Maze challenge will take
somewhere around 2-7 hours of your time.
* Our goal for this time budget is to keep the time investment predictable for candidates -
it is not intended to create significant “time pressure”.
* The length of time you take to submit your work is not a factor in how we review it.
* Many candidates will work the challenge and submit it back within a day or two.
Or some might submit it a week later, depending on personal circumstances.
## Discretion
In order to ensure continued fairness to all past and future participants,
please do not share any of the details of this code challenge with others,
including the work you submit.
## Anonymity
Code challenge submissions are treated as a form of [Blind Audition](https://en.wikipedia.org/wiki/Blind_audition):
reviewers dont have access to a candidates personal information,
including name and background.
Please help us by keeping your submission free of information
that would be revealing of who you are, or any personal attributes.
## Part 1: Follower Maze - Refactor
### Overview
Follower-Maze is a **client-server application** built for social networking.
Users follow other users, post messages, send private messages, unfollow users, and so on.
* Each of these actions is an _event_, created by an _event-source_ and sent to the _server_.
* Each event is processed by the _server_.
* The processing of the event may have follow-on effects on other _user-clients_.
The **server** listens for two kinds of **clients**:
* **1 event source**, which emits events.
* **N user-clients** that connect to the server.
The server routes certain events to certain clients, according to the social networking domain rules.
Please see the [Specification](#specification) section for more detail.
### Running the End-to-End Tester
Please see the repository [README](README.md) for instructions on how to run client/server implementations,
and the end-to-end tester.
You have the choice of one of six programming language implementations:
please choose the language youre most comfortable in,
and try running the client/server implementation for that language,
and then run the end-to-end tester to successful completion.
### Specification
This specification is just for your reference.
The provided implementations are based on this specification.
#### Client/Server Protocol
* The protocol is line-based i.e. a LF control character terminates each message.
All strings are encoded in UTF-8.
* Data is transmitted over TCP sockets.
* The _event source_ connects on port 9090 and will start sending events as soon as the connection is accepted.
* The _user clients_ connect on port 9099 and identify themselves with their user ID.
For example, once connected a _user client_ may send down `2932\r\n`,
indicating that it is representing user 2932.
* After the identification is sent,
the user client starts waiting for events to be sent to them by the server.
#### Events
There are five possible events.
The table below describes payloads sent by the event source and what each represents:
```
| Payload | Sequence # | Type | From User Id | To User Id |
|---------------|------------|---------------|--------------|------------|
| 666|F|60|50 | 666 | Follow | 60 | 50 |
| 1|U|12|9 | 1 | Unfollow | 12 | 9 |
| 542532|B | 542532 | Broadcast | - | - |
| 43|P|32|56 | 43 | Private Msg | 32 | 56 |
| 634|S|32 | 634 | Status Update | 32 | - |
```
#### Event Rules
* Each message begins with a sequence number.
* However, the event source _does not send events in any given order_.
In particular, sequence number has no effect on the order in which events are sent.
* Events _may_ generate notifications for _user clients_.
If there is a respective target _user client_ connected,
the notification is routed according to the following rules:
* Follow: Only the `To User Id` is notified
* A follow event from user A to user B means that user A now follows user B.
* Unfollow: No clients are notified
* An unfollow event from user A to user B means that user A stopped following user B.
* Broadcast: All connected _user clients_ are notified
* Private Message: Only the `To User Id` is notified
* Status Update: All current followers of the `From User ID` are notified
* If there are no _user clients_ connected for a user,
any notifications for them are currently ignored.
_User clients_ are notified of events _in the correct sequence-number order_,
regardless of the order in which the _event source_ sent them.
### Challenge
We have provided the client and server code - this is your starting point.
However, the code is in poor shape:
its condensed into giant blocks of code in a single file,
making it hard for you to understand, debug, and test.
**Your task is to refactor the code so that its _easy to test,
easy to understand and easy to maintain._**
### Guidance
**Time budget:** We roughly estimate this portion will take between 2-6 hours.
The **primary goals** of this exercise are:
* To provide structure and clarity to the server code base by introducing appropriate abstractions.
* To produce code that accommodates change and is easy to test.
* To maintain correctness of the server program.
* To document your approach.
Some **non-goals**:
* To introduce functionality that was not asked for.
* To make the solution production-ready.
* **HOWEVER** we ARE interested in what top improvements you think need to be made
to make it production-ready (see README details below).
* To write unit tests; your focus should be on writing testable code first,
and while you're free to write unit tests with this part of the exercise,
it is not a stated requirement here.
## Part 2: Follower Maze - Extension
### The Problem
In Part 1, we asked you to refactor an existing solution to the follower-maze problem.
The original specification of the problem leaves the question of bad event data unanswered,
and it simplifies the case of undeliverable notifications in case of user disconnects.
Your task is to **add a [Dead Letter Queue (DLQ)](https://en.wikipedia.org/wiki/Dead_letter_queue)
to the server**, so that undeliverable or malformed messages aren't lost.
“Dead letters” build up and ultimately must be dealt with.
For the purposes of this interview problem,
it's OK to simply log them to the console.
But please **include a short explanation in your README** of how you would store and process
that data in a production scenario.
### Guidance
**Time budget:** We roughly estimate this portion will take between 1-2 hours.
The **primary goals** of this exercise are:
* To introduce a DLQ implementation
* To capture messages that fall outside of the original specification of the protocol, for example:
* Malformed messages
* New message types not yet supported by the server
* To capture messages that cannot be delivered because the target user is not connected
* To write unit tests demonstrating the DLQ operates as specified
* To explain your approach to handling “dead letters” in production, in your README
Some **non-goals**:
* To introduce functionality or improvements that were not asked for
* To make the solution production-ready
(but you're welcome to comment on that aspect in your README file)
## Constraints
* Pick one of the six programming languages,
and base your solution on that languages initial implementation.
* Please restrict yourself to the standard library - dont introduce 3rd party libraries.
**Exception:** libraries that facilitate unit testing are acceptable.
## Deliverables
### Deliverables For Part 1
* Your refactored version of the code.
* A README file detailing:
* The approach you took and reasoning you applied to arrive at your final solution.
* Explanation of any design trade-offs or short-cuts you may have taken.
* How to run the server, in case you made changes to the setup we provided.
* The top priorities you would have for additions or modifications to make the solution production-ready.
_Remember: unit tests are not required for Part 1_
### Deliverables For Part 2
* A working implementation of a DLQ that handles messages falling into the categories specified above.
* Unit tests demonstrating the newly added functionality is working as specified.
* In your README:
* The approach you took and reasoning you applied to arrive at your final solution;
pay particular attention to design trade-offs or short-cuts you may have taken.
* Explanation of what more you would do with DLQ messages in a production scenario
(i.e. aside from simply logging them to the console).
## Tips
* Run the end-to-end tester regularly as you change code.
* We're looking for a simple, straight-forward, clean & concise solution.
* If you have technical questions, please ask!

View file

@ -1,56 +0,0 @@
**Please [read the instructions first](INSTRUCTIONS.md).**
## Running the server
### go
```
cd go
go run main.go
```
### java
```
cd java
gradle run
```
### js
```
cd js
node index.js
```
### python
Please use a modern version of python 3, managed via a tool like pyenv.
```
cd python
python main.py
```
### ruby
Please use a modern version of cruby, managed via a tool like rvm.
```
cd ruby
ruby main.rb
```
### scala
```
cd scala
sbt run
```
## About the tester
The tester makes socket connections to the server:
- The event source connects on port 9090 and will start sending events as soon as the connection is accepted.
- The user clients can connect on port 9099,
and communicate with server following events specification and rules outlined in challenge instructions.
## Running the tester
From the project root, run:
`tester/run100k.sh`

View file

@ -1,3 +0,0 @@
module followermaze
go 1.13

View file

@ -1,187 +0,0 @@
package main
import (
"bufio"
"fmt"
"io"
"log"
"net"
"strconv"
"strings"
)
const eventPort = 9090
const clientPort = 9099
func main() {
clientPool := make(map[int]net.Conn)
followRegistry := map[int]map[int]bool{}
seqNoToMessage := make(map[int][]string)
go func() {
lastSeqNo := 0
eventListener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", eventPort))
if err != nil {
log.Fatal(err)
}
defer eventListener.Close()
fmt.Printf("Listening for events on %d\n", eventPort)
outer:
for {
conn, err := eventListener.Accept()
if err != nil {
log.Fatal(err)
}
defer conn.Close()
reader := bufio.NewReader(conn)
for {
payloadRaw, err := reader.ReadString('\n')
if err == io.EOF {
conn.Close()
continue outer
} else if err != nil {
log.Fatal(err)
}
payload := strings.TrimSpace(payloadRaw)
fmt.Printf("Message received: %s\n", payload)
payloadParts := strings.Split(payload, "|")
incomingSeqNo, err := strconv.Atoi(payloadParts[0])
if err != nil {
log.Fatal(err)
}
seqNoToMessage[incomingSeqNo] = payloadParts
for {
nextMessage, ok := seqNoToMessage[lastSeqNo+1]
delete(seqNoToMessage, lastSeqNo+1)
if !ok {
break
}
nextPayload := strings.Join(nextMessage, "|") + "\n"
kind := strings.TrimSpace(nextMessage[1])
switch kind {
case "F":
fromUserID, err := strconv.Atoi(nextMessage[2])
if err != nil {
log.Fatal(err)
}
toUserID, err := strconv.Atoi(nextMessage[3])
if err != nil {
log.Fatal(err)
}
if _, ok := followRegistry[toUserID]; !ok {
followRegistry[toUserID] = make(map[int]bool)
}
followers, _ := followRegistry[toUserID]
followers[fromUserID] = true
clientConn, ok := clientPool[toUserID]
if ok {
fmt.Fprint(clientConn, nextPayload)
}
case "U":
fromUserID, err := strconv.Atoi(nextMessage[2])
if err != nil {
log.Fatal(err)
}
toUserID, err := strconv.Atoi(nextMessage[3])
if err != nil {
log.Fatal(err)
}
if followers, ok := followRegistry[toUserID]; ok {
delete(followers, fromUserID)
}
case "P":
toUserID, err := strconv.Atoi(nextMessage[3])
if err != nil {
log.Fatal(err)
}
if clientConn, ok := clientPool[toUserID]; ok {
fmt.Fprint(clientConn, nextPayload)
}
case "B":
for _, clientConn := range clientPool {
fmt.Fprint(clientConn, nextPayload)
}
case "S":
fromUserID, err := strconv.Atoi(nextMessage[2])
if err != nil {
log.Fatal(err)
}
if followers, ok := followRegistry[fromUserID]; ok {
for follower := range followers {
clientConn, ok := clientPool[follower]
if ok {
fmt.Fprint(clientConn, nextPayload)
}
}
}
}
lastSeqNo = lastSeqNo + 1
}
}
}
}()
eventListener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", clientPort))
if err != nil {
log.Fatal(err)
}
defer eventListener.Close()
fmt.Printf("Listening for client requests on %d\n", clientPort)
for {
conn, err := eventListener.Accept()
if err != nil {
log.Fatal(err)
}
reader := bufio.NewReader(conn)
userIDRaw, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}
userIDStr := strings.TrimSpace(userIDRaw)
userID, err := strconv.Atoi(userIDStr)
if err != nil {
log.Fatal(err)
}
clientPool[userID] = conn
fmt.Printf("User connected: %d (%d total)\n", userID, len(clientPool))
}
}

View file

@ -1,26 +0,0 @@
buildscript {
repositories {
mavenCentral()
jcenter()
}
}
apply plugin: 'java'
apply plugin: 'application'
mainClassName = "com.soundcloud.maze.Main"
tasks.withType(JavaCompile) {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
repositories {
mavenCentral()
jcenter()
}
dependencies {
testCompile "junit:junit:4.12"
testCompile "org.assertj:assertj-core:1.7.1"
}

View file

@ -1,84 +0,0 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -1,153 +0,0 @@
package com.soundcloud.maze;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import static java.util.Collections.emptySet;
public class Main {
private final static int EVENT_PORT = 9090;
private final static int CLIENT_PORT = 9099;
private static long lastSeqNo = 0L;
public static void main(String[] args) {
Map<Long, Socket> clientPool = new ConcurrentHashMap<>();
Map<Long, List<String>> seqNoToMessage = new HashMap<>();
Map<Long, Set<Long>> followRegistry = new HashMap<>();
new Thread(() -> {
System.out.println("Listening for events on " + EVENT_PORT);
try (Socket eventSocket = new ServerSocket(EVENT_PORT).accept()) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(eventSocket.getInputStream()))) {
reader.lines().forEach(payload -> {
System.out.println("Message received: " + payload);
List<String> payloadParts = Arrays.asList(payload.split("\\|"));
seqNoToMessage.put(Long.parseLong(payloadParts.get(0)), payloadParts);
while (seqNoToMessage.containsKey(lastSeqNo + 1)) {
List<String> nextMessage = seqNoToMessage.get(lastSeqNo + 1);
String nextPayload = String.join("|", nextMessage);
long seqNo = Long.parseLong(nextMessage.get(0));
String kind = nextMessage.get(1);
switch (kind) {
case "F": {
long fromUserId = Long.parseLong(nextMessage.get(2));
long toUserId = Long.parseLong(nextMessage.get(3));
Set<Long> followers = followRegistry.getOrDefault(toUserId, new HashSet<>());
followers.add(fromUserId);
followRegistry.put(toUserId, followers);
try {
Socket socket = clientPool.get(toUserId);
if (socket != null) {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write(nextPayload + "\n");
writer.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
break;
case "U": {
long fromUserId = Long.parseLong(nextMessage.get(2));
long toUserId = Long.parseLong(nextMessage.get(3));
Set<Long> followers = followRegistry.getOrDefault(toUserId, new HashSet<>());
followers.remove(fromUserId);
followRegistry.put(toUserId, followers);
}
break;
case "P": {
long toUserId = Long.parseLong(nextMessage.get(3));
try {
Socket socket = clientPool.get(toUserId);
if (socket != null) {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write(nextPayload + "\n");
writer.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
break;
case "B": {
clientPool.values().forEach(socket -> {
try {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write(nextPayload + "\n");
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
break;
case "S": {
long fromUserId = Long.parseLong(nextMessage.get(2));
Set<Long> followers = followRegistry.getOrDefault(fromUserId, emptySet());
followers.forEach(follower -> {
try {
Socket socket = clientPool.get(follower);
if (socket != null) {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write(nextPayload + "\n");
writer.flush();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
break;
}
lastSeqNo = seqNo;
}
});
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(() -> {
System.out.println("Listening for client requests on " + CLIENT_PORT);
try {
ServerSocket serverSocket = new ServerSocket(CLIENT_PORT);
Socket clientSocket = serverSocket.accept();
while (clientSocket != null) {
BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String userId = reader.readLine();
if (userId != null) {
clientPool.put(Long.parseLong(userId), clientSocket);
System.out.println("User connected: " + userId + " (" + clientPool.size() + " total)");
}
clientSocket = serverSocket.accept();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}).start();
}
}

View file

@ -1,124 +0,0 @@
const net = require("net");
const readline = require("readline");
const EVENT_PORT = 9090;
const CLIENT_PORT = 9099;
const clientPool = {};
const followRegistry = {};
let lastSeqNo = 0;
net
.createServer(socket => {
const seqNoToMessage = {};
const readInterface = readline.createInterface({ input: socket });
readInterface.on("line", payload => {
console.log(`Message received: ${payload}`);
const payloadParts = payload.split("|");
seqNoToMessage[parseInt(payloadParts[0])] = payloadParts;
while (seqNoToMessage[lastSeqNo + 1]) {
const nextMessage = seqNoToMessage[lastSeqNo + 1];
const nextPayload = nextMessage.join("|");
const seqNo = parseInt(nextMessage[0]);
const kind = nextMessage[1];
switch (kind) {
case "F":
{
const fromUserId = parseInt(nextMessage[2]);
const toUserId = parseInt(nextMessage[3]);
const followers = followRegistry[toUserId] || new Set([]);
followers.add(fromUserId);
followRegistry[toUserId] = followers;
const socket = clientPool[toUserId];
if (socket) {
socket.write(nextPayload + "\n");
}
}
break;
case "U":
{
const fromUserId = parseInt(nextMessage[2]);
const toUserId = parseInt(nextMessage[3]);
const followers = followRegistry[toUserId] || new Set([]);
followers.delete(fromUserId);
followRegistry[toUserId] = followers;
}
break;
case "P":
{
const toUserId = parseInt(nextMessage[3]);
const socket = clientPool[toUserId];
if (socket) {
socket.write(nextPayload + "\n");
}
}
break;
case "B":
{
for (let toUserId in clientPool) {
const socket = clientPool[toUserId];
if (socket) {
socket.write(nextPayload + "\n");
}
}
}
break;
case "S":
{
const fromUserId = parseInt(nextMessage[2]);
const followers = followRegistry[fromUserId] || new Set([]);
followers.forEach(follower => {
const socket = clientPool[follower];
if (socket) {
socket.write(nextPayload + "\n");
}
});
}
break;
}
lastSeqNo = seqNo;
}
});
})
.listen(EVENT_PORT, "127.0.0.1", err => {
if (err) {
throw err;
}
console.log(`Listening for events on ${EVENT_PORT}`);
});
net
.createServer(clientSocket => {
const readInterface = readline.createInterface({ input: clientSocket });
readInterface.on("line", userIdString => {
if (userIdString != null) {
clientPool[parseInt(userIdString)] = clientSocket;
console.log(
`User connected: ${userIdString} (${clientPool.length} total)`
);
}
});
})
.listen(CLIENT_PORT, "127.0.0.1", err => {
if (err) {
throw err;
}
console.log(`Listening for client requests on ${CLIENT_PORT}`);
});

View file

@ -1,988 +0,0 @@
{
"name": "js",
"version": "0.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/code-frame": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
"integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
"dev": true,
"requires": {
"@babel/highlight": "^7.0.0"
}
},
"@babel/highlight": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
"integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
"dev": true,
"requires": {
"chalk": "^2.0.0",
"esutils": "^2.0.2",
"js-tokens": "^4.0.0"
}
},
"acorn": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
"integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
"dev": true
},
"acorn-jsx": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz",
"integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==",
"dev": true
},
"ajv": {
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ansi-escapes": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
"integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
"dev": true
},
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
},
"astral-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
"dev": true
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true
},
"cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
"integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
"dev": true,
"requires": {
"restore-cursor": "^2.0.0"
}
},
"cli-width": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
"dev": true
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
"dev": true,
"requires": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"dev": true,
"requires": {
"ms": "^2.1.1"
}
},
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true
},
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true,
"requires": {
"esutils": "^2.0.2"
}
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"eslint": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.2.2.tgz",
"integrity": "sha512-mf0elOkxHbdyGX1IJEUsNBzCDdyoUgljF3rRlgfyYh0pwGnreLc0jjD6ZuleOibjmnUWZLY2eXwSooeOgGJ2jw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"ajv": "^6.10.0",
"chalk": "^2.1.0",
"cross-spawn": "^6.0.5",
"debug": "^4.0.1",
"doctrine": "^3.0.0",
"eslint-scope": "^5.0.0",
"eslint-utils": "^1.4.2",
"eslint-visitor-keys": "^1.1.0",
"espree": "^6.1.1",
"esquery": "^1.0.1",
"esutils": "^2.0.2",
"file-entry-cache": "^5.0.1",
"functional-red-black-tree": "^1.0.1",
"glob-parent": "^5.0.0",
"globals": "^11.7.0",
"ignore": "^4.0.6",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"inquirer": "^6.4.1",
"is-glob": "^4.0.0",
"js-yaml": "^3.13.1",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.3.0",
"lodash": "^4.17.14",
"minimatch": "^3.0.4",
"mkdirp": "^0.5.1",
"natural-compare": "^1.4.0",
"optionator": "^0.8.2",
"progress": "^2.0.0",
"regexpp": "^2.0.1",
"semver": "^6.1.2",
"strip-ansi": "^5.2.0",
"strip-json-comments": "^3.0.1",
"table": "^5.2.3",
"text-table": "^0.2.0",
"v8-compile-cache": "^2.0.3"
}
},
"eslint-scope": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
"integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
"dev": true,
"requires": {
"esrecurse": "^4.1.0",
"estraverse": "^4.1.1"
}
},
"eslint-utils": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
"integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
"dev": true,
"requires": {
"eslint-visitor-keys": "^1.0.0"
}
},
"eslint-visitor-keys": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
"dev": true
},
"espree": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz",
"integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==",
"dev": true,
"requires": {
"acorn": "^7.0.0",
"acorn-jsx": "^5.0.2",
"eslint-visitor-keys": "^1.1.0"
}
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
},
"esquery": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
"integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
"dev": true,
"requires": {
"estraverse": "^4.0.0"
}
},
"esrecurse": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
"integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
"dev": true,
"requires": {
"estraverse": "^4.1.0"
}
},
"estraverse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"dev": true
},
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true
},
"external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
"dev": true,
"requires": {
"chardet": "^0.7.0",
"iconv-lite": "^0.4.24",
"tmp": "^0.0.33"
}
},
"fast-deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
"dev": true
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
"dev": true
},
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
"figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
"integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5"
}
},
"file-entry-cache": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
"integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
"dev": true,
"requires": {
"flat-cache": "^2.0.1"
}
},
"flat-cache": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
"integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
"dev": true,
"requires": {
"flatted": "^2.0.0",
"rimraf": "2.6.3",
"write": "1.0.3"
}
},
"flatted": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
"integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"functional-red-black-tree": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
"dev": true
},
"glob": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"glob-parent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz",
"integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
},
"globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"dev": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"import-fresh": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz",
"integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==",
"dev": true,
"requires": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
}
},
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"inquirer": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
"integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
"dev": true,
"requires": {
"ansi-escapes": "^3.2.0",
"chalk": "^2.4.2",
"cli-cursor": "^2.1.0",
"cli-width": "^2.0.0",
"external-editor": "^3.0.3",
"figures": "^2.0.0",
"lodash": "^4.17.12",
"mute-stream": "0.0.7",
"run-async": "^2.2.0",
"rxjs": "^6.4.0",
"string-width": "^2.1.0",
"strip-ansi": "^5.1.0",
"through": "^2.3.6"
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
},
"is-glob": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-promise": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
"integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
"dev": true
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
"js-yaml": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
"dev": true
},
"levn": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
"dev": true,
"requires": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"mimic-fn": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
"minimist": "0.0.8"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"mute-stream": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
"dev": true
},
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
"dev": true
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"onetime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
"dev": true,
"requires": {
"mimic-fn": "^1.0.0"
}
},
"optionator": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
"integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
"dev": true,
"requires": {
"deep-is": "~0.1.3",
"fast-levenshtein": "~2.0.4",
"levn": "~0.3.0",
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2",
"wordwrap": "~1.0.0"
}
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"requires": {
"callsites": "^3.0.0"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-key": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
"dev": true
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
"dev": true
},
"prettier": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz",
"integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==",
"dev": true
},
"progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
"regexpp": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
"integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
"dev": true
},
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
"restore-cursor": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
"dev": true,
"requires": {
"onetime": "^2.0.0",
"signal-exit": "^3.0.2"
}
},
"rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"run-async": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
"integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
"dev": true,
"requires": {
"is-promise": "^2.1.0"
}
},
"rxjs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz",
"integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
"dev": true,
"requires": {
"shebang-regex": "^1.0.0"
}
},
"shebang-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true
},
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true
},
"slice-ansi": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
"integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.0",
"astral-regex": "^1.0.0",
"is-fullwidth-code-point": "^2.0.0"
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
},
"dependencies": {
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
"ansi-regex": "^3.0.0"
}
}
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
}
}
},
"strip-json-comments": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
"integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
},
"table": {
"version": "5.4.6",
"resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
"integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
"dev": true,
"requires": {
"ajv": "^6.10.2",
"lodash": "^4.17.14",
"slice-ansi": "^2.1.0",
"string-width": "^3.0.0"
},
"dependencies": {
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
}
}
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
"tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"dev": true,
"requires": {
"os-tmpdir": "~1.0.2"
}
},
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
"dev": true
},
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
"dev": true,
"requires": {
"prelude-ls": "~1.1.2"
}
},
"uri-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
"dev": true,
"requires": {
"punycode": "^2.1.0"
}
},
"v8-compile-cache": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
"integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
"dev": true
},
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"dev": true,
"requires": {
"isexe": "^2.0.0"
}
},
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
"dev": true
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"write": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
"integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
"dev": true,
"requires": {
"mkdirp": "^0.5.1"
}
}
}
}

View file

@ -1,11 +0,0 @@
{
"name": "js",
"version": "0.0.1",
"description": "follower maze refactoring challenge in javascript",
"main": "index.js",
"author": "SoundCloud",
"devDependencies": {
"prettier": "1.18.2",
"eslint": "6.2.2"
}
}

View file

@ -1,98 +0,0 @@
import socket
import threading
EVENT_PORT = 9090
CLIENT_PORT = 9099
client_pool = {}
follow_registry = {}
seq_no_to_message = {}
def event_server():
last_seq_no = 0
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.bind(("127.0.0.1", EVENT_PORT))
server.listen()
print("Listening for events on %d" % EVENT_PORT)
while True:
event_socket, address = server.accept()
print('Accepted connection from {}:{}'.format(address[0], address[1]))
with event_socket:
with event_socket.makefile() as socket_file:
for payload_line in socket_file:
payload = payload_line.strip()
print("Message received: %s" % payload)
payload_parts = payload.split("|")
seq_no_to_message[int(payload_parts[0])] = payload_parts
while last_seq_no + 1 in seq_no_to_message:
next_seq_no = last_seq_no + 1
next_message = seq_no_to_message[next_seq_no]
del seq_no_to_message[next_seq_no]
kind = next_message[1]
next_payload = "|".join(next_message)
if kind == "F":
from_user_id = int(next_message[2])
to_user_id = int(next_message[3])
if to_user_id not in follow_registry:
follow_registry[to_user_id] = set([])
follow_registry[to_user_id].add(from_user_id)
#if to_user_id in client_pool:
# client_pool[to_user_id].sendall(bytes(next_payload + "\n", "UTF-8"))
elif kind == "U":
from_user_id = int(next_message[2])
to_user_id = int(next_message[3])
if to_user_id not in follow_registry:
follow_registry[to_user_id] = set([])
follow_registry[to_user_id].remove(from_user_id)
elif kind == "P":
to_user_id = int(next_message[3])
if to_user_id in client_pool:
client_pool[to_user_id].sendall(bytes(next_payload + "\n", "UTF-8"))
elif kind == "B":
for client_id in client_pool:
client_pool[client_id].sendall(bytes(next_payload + "\n", "UTF-8"))
elif kind == "S":
from_user_id = int(next_message[2])
if from_user_id in follow_registry:
for follower in follow_registry[from_user_id]:
if follower in client_pool:
client_pool[follower].sendall(bytes(next_payload + "\n", "UTF-8"))
last_seq_no = next_seq_no
def client_connection_server():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.bind(("127.0.0.1", CLIENT_PORT))
server.listen()
print("Listening for client requests on %d" % CLIENT_PORT)
while True:
client_socket, address = server.accept()
with client_socket.makefile() as f:
user_id_string = f.readline()
if user_id_string:
user_id = int(user_id_string)
client_pool[user_id] = client_socket
print("User connected: %d (%d total)" % (user_id, len(client_pool)))
if __name__ == "__main__":
thread1 = threading.Thread(target=event_server)
thread1.start()
thread2 = threading.Thread(target=client_connection_server)
thread2.start()
thread1.join()
thread2.join()

View file

@ -1,106 +0,0 @@
require 'set'
require 'socket'
EVENT_PORT = 9090
CLIENT_PORT = 9099
client_pool = {}
seq_no_to_message = {}
follow_registry = {}
last_seq_no = 0
thread1 = Thread.new do
puts("Listening for events on #{EVENT_PORT}")
server = TCPServer.open(EVENT_PORT)
loop do
Thread.fork(server.accept) do |event_socket|
event_socket.each_line do |payload|
puts("Message received: #{payload}")
payload_parts = payload.split('|')
seq_no_to_message[payload_parts[0].to_i] = payload_parts
while seq_no_to_message[last_seq_no + 1]
next_message = seq_no_to_message[last_seq_no + 1]
next_payload = next_message.join('|')
seq_no = next_message[0].to_i
kind = next_message[1].strip
case kind
when 'F'
from_user_id = next_message[2].to_i
to_user_id = next_message[3].to_i
followers = follow_registry[to_user_id] || Set.new
followers << from_user_id
follow_registry[to_user_id] = followers
socket = client_pool[to_user_id]
if socket
socket.puts(next_payload)
socket.flush
end
when 'U'
from_user_id = next_message[2].to_i
to_user_id = next_message[3].to_i
followers = follow_registry[to_user_id] || Set.new
followers.delete(from_user_id)
follow_registry[to_user_id] = followers
when 'P'
to_user_id = next_message[3].to_i
socket = client_pool[to_user_id]
if socket
socket.puts(next_payload)
socket.flush
end
when 'B'
client_pool.values.each do |socket|
socket.puts(next_payload)
socket.flush
end
when 'S'
from_user_id = next_message[2].to_i
followers = follow_registry[from_user_id] || Set.new
followers.each do |follower|
socket = client_pool[follower]
if socket
socket.puts(next_payload)
socket.flush
end
end
end
last_seq_no = seq_no
end
end
event_socket.close
end
end
end
thread2 = Thread.new do
puts("Listening for client requests on #{CLIENT_PORT}")
server = TCPServer.open(CLIENT_PORT)
loop do
Thread.fork(server.accept) do |socket|
user_id_string = socket.gets
if user_id_string
user_id = user_id_string.to_i
client_pool[user_id] = socket
puts("User connected: #{user_id} (#{client_pool.size} total)")
end
end
end
end
thread1.join
thread2.join

View file

@ -1,3 +0,0 @@
name := "maze"
scalaVersion := "2.12.8"

View file

@ -1 +0,0 @@
sbt.version = 1.2.8

View file

@ -1,140 +0,0 @@
package com.soundcloud.maze
import java.io._
import java.net.{ServerSocket, Socket}
import scala.collection.concurrent.TrieMap
import scala.collection.mutable
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.io.Source
import scala.util.Try
import scala.collection.JavaConverters._
object Main {
private val EventPort = 9090
private val ClientPort = 9099
private var lastSeqNo = 0L
def main(args: Array[String]): Unit = {
val clientPool = new TrieMap[Long, Socket]
val messagesBySeqNo = new mutable.HashMap[Long, List[String]]
val followRegistry = new mutable.HashMap[Long, Set[Long]]
implicit val ec = ExecutionContext.global
val eventsAsync = Future {
println(s"Listening for events on $EventPort")
val eventSocket = new ServerSocket(EventPort).accept()
Try {
val reader = new BufferedReader(new InputStreamReader(eventSocket.getInputStream()))
Try {
reader.lines().iterator().asScala.foreach { payload =>
println(s"Message received: $payload")
val message = payload.split("\\|").toList
messagesBySeqNo += message(0).toLong -> message
while (messagesBySeqNo.get(lastSeqNo + 1L).isDefined) {
val nextMessage = messagesBySeqNo(lastSeqNo + 1)
messagesBySeqNo -= lastSeqNo + 1L
val nextPayload = nextMessage.mkString("|")
val seqNo = nextMessage(0).toLong
val kind = nextMessage(1)
kind match {
case "F" =>
val fromUserId = nextMessage(2).toLong
val toUserId = nextMessage(3).toLong
val followers = followRegistry.getOrElse(toUserId, Set.empty)
val newFollowers = followers + fromUserId
followRegistry.put(toUserId, newFollowers)
clientPool.get(toUserId).foreach { socket =>
val writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
writer.write(s"$nextPayload\n")
writer.flush()
}
case "U" =>
val fromUserId = nextMessage(2).toLong
val toUserId = nextMessage(3).toLong
val followers = followRegistry.getOrElse(toUserId, Set.empty)
val newFollowers = followers - fromUserId
followRegistry.put(toUserId, newFollowers)
case "P" =>
val toUserId = nextMessage(3).toLong
clientPool.get(toUserId).foreach { socket =>
val writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
writer.write(s"$nextPayload\n")
writer.flush()
}
case "B" =>
clientPool.values.foreach { socket =>
val writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
writer.write(s"$nextPayload\n")
writer.flush()
}
case "S" =>
val fromUserId = nextMessage(2).toLong
val followers = followRegistry.getOrElse(fromUserId, Set.empty)
followers.foreach { follower =>
clientPool.get(follower).foreach { socket =>
val writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
writer.write(s"$nextPayload\n")
writer.flush()
}
}
}
lastSeqNo = seqNo
}
}
}
if (reader != null) reader.close()
}
if (eventSocket != null) eventSocket.close()
}
val clientsAsync = Future {
println(s"Listening for client requests on $ClientPort")
val serverSocket = new ServerSocket(ClientPort)
var maybeClientSocket = Option(serverSocket.accept())
while (maybeClientSocket.nonEmpty) {
maybeClientSocket.foreach { clientSocket =>
val bufferedSource = Source.fromInputStream(clientSocket.getInputStream())
val userId = bufferedSource.bufferedReader().readLine()
if (userId != null) {
clientPool.put(userId.toLong, clientSocket)
println(s"User connected: $userId (${clientPool.size} total)")
}
maybeClientSocket = Option(serverSocket.accept())
}
}
}
Await.result(Future.sequence(Seq(eventsAsync, clientsAsync)), Duration.Inf)
}
}

View file

@ -1,6 +0,0 @@
#!/bin/bash -ex
this_dir=$(dirname $0)
export totalEvents=100000
export concurrencyLevel=1
time java -server -Xmx1G -jar "${this_dir}/follower-maze-2.0.jar"

View file

@ -1,6 +0,0 @@
#!/bin/bash -ex
this_dir=$(dirname $0)
export totalEvents=30
export concurrencyLevel=1
time java -server -Xmx1G -jar "${this_dir}/follower-maze-2.0.jar"