oorja 2.1.7 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,17 @@
1
- import { determineENV, getoorjaConfig, INVALID_ROOM_LINK_MESSAGE, setENVAccessToken, } from '../config.js';
1
+ import { getoorjaConfig, INVALID_STREAM_KEY_MESSAGE } from '../config.js';
2
2
  import { teletypeApp } from '../teletype/index.js';
3
3
  import { ConnectClient } from '../connect/index.js';
4
- import { URL, URLSearchParams } from 'url';
5
4
  import { importKey, createRoomKey, exportKey } from '../encryption.js';
6
- import { loginByRoomOTP, preflight, promptAuth, resumeSession, validateCliVersion } from './preflight.js';
5
+ import { promptAuth, validateCliVersion } from './preflight.js';
7
6
  import { getRegion } from './client.js';
8
7
  import ora from 'ora';
9
- import { printExitMessage } from '../utils.js';
8
+ import { Future, printExitMessage } from '../utils.js';
9
+ import { Unauthorized } from '../connect/errors.js';
10
+ import { exit } from '../exit.js';
11
+ import chalk from 'chalk';
10
12
  export class InvalidRoomLink extends Error {
11
13
  }
12
- class OORJA {
14
+ export class OORJA {
13
15
  config;
14
16
  connectClient;
15
17
  user;
@@ -30,11 +32,10 @@ class OORJA {
30
32
  linkForRoom = (roomKey) => {
31
33
  return `${oorjaURL(this.config)}/rooms?id=${roomKey.roomId}#${exportKey(roomKey.key)}`;
32
34
  };
33
- getRoomKey(roomLink) {
34
- const url = parseRoomURL(roomLink);
35
+ getRoomKey(streamKey) {
35
36
  return {
36
- key: importKey(url.hash),
37
- roomId: getRoomId(url),
37
+ key: importKey(streamKey.encryptionSecret),
38
+ roomId: streamKey.roomId,
38
39
  };
39
40
  }
40
41
  teletype = (options) => {
@@ -45,67 +46,150 @@ class OORJA {
45
46
  });
46
47
  };
47
48
  }
48
- const parseRoomURL = (roomLink) => {
49
- const url = new URL(roomLink);
50
- if (!url.hash || !getRoomId(url)) {
51
- printExitMessage(INVALID_ROOM_LINK_MESSAGE);
52
- process.exit(3);
49
+ export const parseStreamKey = (streamKey) => {
50
+ if (!streamKey.startsWith('sk-')) {
51
+ printExitMessage(INVALID_STREAM_KEY_MESSAGE);
52
+ exit(3);
53
53
  }
54
- return url;
55
- };
56
- const getRoomId = (roomURL) => {
57
- const params = new URLSearchParams(roomURL.search);
58
- return params.get('id') || undefined;
54
+ const parts = streamKey.split(':');
55
+ if (parts.length !== 2) {
56
+ printExitMessage(INVALID_STREAM_KEY_MESSAGE);
57
+ exit(3);
58
+ }
59
+ const [token, rest] = parts;
60
+ const [roomId, encryptionSecret] = rest.split('#');
61
+ if (!roomId || !encryptionSecret) {
62
+ printExitMessage(INVALID_STREAM_KEY_MESSAGE);
63
+ exit(3);
64
+ }
65
+ return {
66
+ roomId,
67
+ token,
68
+ encryptionSecret,
69
+ };
59
70
  };
60
71
  const oorjaURL = (config) => {
61
72
  const { host } = config;
62
73
  return `https://${host}`;
63
74
  };
64
75
  const linkForTokenGen = (config) => `${oorjaURL(config)}/access_token`;
65
- const init = async (env, options = {}) => {
66
- const config = getoorjaConfig(env);
67
- const spinner = ora({
68
- text: 'Connecting...',
69
- discardStdin: true,
70
- }).start();
71
- const region = await getRegion();
72
- let connectClient = new ConnectClient(env, region);
73
- await validateCliVersion(connectClient);
74
- let user = await resumeSession(env, connectClient, options.roomId);
75
- spinner.succeed('Online');
76
- if (!user) {
77
- let token = '';
78
- if (options.roomId) {
79
- token = await loginByRoomOTP(connectClient, options.roomId);
76
+ export class App {
77
+ config;
78
+ connectionCheckFailed = false;
79
+ connectionCheckFuture;
80
+ connectClient = null;
81
+ constructor(config) {
82
+ this.config = config;
83
+ this.establishConnection();
84
+ this.connectionCheckFuture = new Future();
85
+ }
86
+ init = async (streamKey) => {
87
+ let spinner = ora({
88
+ text: 'Checking connectivity..',
89
+ discardStdin: true,
90
+ }).start();
91
+ await this.connectionCheckFuture.promise;
92
+ if (this.connectionCheckFailed) {
93
+ spinner.fail('There seems to be a connection issue. Check your connection or try again later. Does https://connect.oorja.io load for you?');
94
+ exit(56);
95
+ return Promise.reject();
80
96
  }
81
- else {
82
- token = await promptAuth(connectClient, linkForTokenGen(config));
83
- if (!token) {
84
- printExitMessage('Token not provided :(');
85
- process.exit(12);
97
+ spinner.succeed('Online');
98
+ const oorjaConfig = getoorjaConfig(this.config.getEnv());
99
+ let user = undefined;
100
+ try {
101
+ if (!streamKey) {
102
+ user = await this.tryResumeSession();
103
+ if (!user) {
104
+ const token = await promptAuth(this.connectClient, linkForTokenGen(oorjaConfig));
105
+ if (!token) {
106
+ printExitMessage('Token not provided :(');
107
+ exit(12);
108
+ }
109
+ this.config.setAccessToken(token);
110
+ this.connectClient.setAccessToken(token);
111
+ spinner = ora({
112
+ text: 'Authenticating',
113
+ discardStdin: false,
114
+ }).start();
115
+ user = await this.connectClient?.fetchSessionUser();
116
+ }
117
+ }
118
+ else {
119
+ spinner = ora({
120
+ text: 'Authenticating',
121
+ discardStdin: false,
122
+ }).start();
123
+ this.connectClient.setAccessToken(streamKey.token);
124
+ user = await this.connectClient?.fetchSessionUser(true);
86
125
  }
87
126
  }
88
- setENVAccessToken(env, token);
89
- }
90
- await connectClient.destroy();
91
- connectClient = new ConnectClient(env, region);
92
- user = await preflight(env, connectClient);
93
- return new OORJA(config, connectClient, user);
94
- };
95
- let currentEnv;
96
- let oorja = null;
97
- export const getApp = async (options = {}) => {
98
- const { roomLink } = options;
99
- const roomURL = roomLink ? parseRoomURL(roomLink) : undefined;
100
- const env = determineENV(roomURL);
101
- if (oorja) {
102
- if (env !== currentEnv) {
103
- return Promise.reject('Attempt to run different env in same session');
127
+ catch (e) {
128
+ this.config.setAccessToken('');
129
+ if (e instanceof Unauthorized) {
130
+ spinner.fail();
131
+ printExitMessage('Your access token failed authentication, resetting...');
132
+ exit(33);
133
+ return Promise.reject();
134
+ }
135
+ else {
136
+ spinner.fail();
137
+ printExitMessage('Something went wrong :(');
138
+ }
139
+ throw e;
104
140
  }
105
- return Promise.resolve(oorja);
106
- }
107
- const app = await init(env, { roomId: roomURL ? getRoomId(roomURL) : undefined });
108
- currentEnv = env;
109
- oorja = app;
110
- return app;
111
- };
141
+ spinner.succeed(`Authenticated ✅: Welcome ${user.name}`);
142
+ if (user.profileType === 'anon') {
143
+ // don't persist tokens for anonymous users
144
+ this.config.setAccessToken('');
145
+ console.log(chalk.yellowBright("You're an anonymous user. CLI will not remember the auth-token"));
146
+ }
147
+ await this.socketConnect();
148
+ return new OORJA(oorjaConfig, this.connectClient, user);
149
+ };
150
+ establishConnection = async () => {
151
+ try {
152
+ const region = await getRegion();
153
+ const connectClient = new ConnectClient(this.config.getEnv(), region, this.config.getAccessToken());
154
+ await validateCliVersion(connectClient);
155
+ this.connectClient = connectClient;
156
+ }
157
+ catch (e) {
158
+ this.connectionCheckFailed = true;
159
+ }
160
+ finally {
161
+ this.connectionCheckFuture.resolve();
162
+ }
163
+ };
164
+ tryResumeSession = async () => {
165
+ const personalAccessToken = this.config.getAccessToken();
166
+ if (!personalAccessToken)
167
+ return;
168
+ try {
169
+ return await this.connectClient.fetchSessionUser();
170
+ }
171
+ catch (e) {
172
+ if (e instanceof Unauthorized) {
173
+ this.config.setAccessToken(''); // reset
174
+ return;
175
+ }
176
+ throw e;
177
+ }
178
+ };
179
+ socketConnect = async () => {
180
+ const spinner = ora({
181
+ text: 'Connecting..',
182
+ discardStdin: false,
183
+ }).start();
184
+ return this.connectClient.establishSocket()
185
+ .then(() => {
186
+ spinner.succeed('Connected').clear();
187
+ return;
188
+ })
189
+ .catch((e) => {
190
+ spinner.fail('Socket connection failure..');
191
+ exit(61);
192
+ return Promise.reject(e);
193
+ });
194
+ };
195
+ }
@@ -1,9 +1,3 @@
1
- import { env } from '../config.js';
2
1
  import { ConnectClient } from '../connect/index.js';
3
- import { User } from '../connect/types.js';
4
- export declare const promptRoomParticipantOTP: () => Promise<string>;
5
2
  export declare const promptAuth: (connectClient: ConnectClient, generateTokenLink: string) => Promise<string>;
6
- export declare const loginByRoomOTP: (connectClient: ConnectClient, roomId: string) => Promise<string>;
7
3
  export declare const validateCliVersion: (connectClient: ConnectClient) => Promise<void>;
8
- export declare const resumeSession: (env: env, connectClient: ConnectClient, roomId?: string) => Promise<User | null>;
9
- export declare const preflight: (env: env, connectClient: ConnectClient) => Promise<User>;
@@ -1,9 +1,8 @@
1
1
  import chalk from 'chalk';
2
2
  import inquirer from 'inquirer';
3
- import ora from 'ora';
4
- import { setENVAccessToken, CLI_VERSION, getENVAccessToken } from '../config.js';
5
- import { BadRequest, Unauthorized } from '../connect/errors.js';
3
+ import { CLI_VERSION } from '../config.js';
6
4
  import { printExitMessage } from '../utils.js';
5
+ import { exit } from '../exit.js';
7
6
  const promptToken = () => inquirer
8
7
  .prompt([
9
8
  {
@@ -13,16 +12,6 @@ const promptToken = () => inquirer
13
12
  },
14
13
  ])
15
14
  .then((answers) => answers.accessToken);
16
- export const promptRoomParticipantOTP = () => inquirer
17
- .prompt([
18
- {
19
- type: 'input',
20
- name: 'otp',
21
- message: 'Please enter your OTP for authentication (switch to teletype app in the room and click on blue "Generate OTP" button):',
22
- },
23
- ])
24
- .then((answers) => answers.otp);
25
- const OTP_HELP_MESSAGE = "You can generate OTP from the room, it's in the instruction steps";
26
15
  export const promptAuth = async (connectClient, generateTokenLink) => {
27
16
  const ANON = 'Proceed as an anonymous user';
28
17
  const SIGN_IN = 'Sign-in with oorja';
@@ -45,88 +34,10 @@ export const promptAuth = async (connectClient, generateTokenLink) => {
45
34
  }
46
35
  throw Error('Unexpected input');
47
36
  };
48
- export const loginByRoomOTP = async (connectClient, roomId) => {
49
- const otp = await promptRoomParticipantOTP();
50
- if (!otp) {
51
- console.log('OTP not provided :(');
52
- printExitMessage(OTP_HELP_MESSAGE);
53
- process.exit(213);
54
- }
55
- try {
56
- return await connectClient.accessTokenFromRoomParticipantOTP(roomId, otp);
57
- }
58
- catch (e) {
59
- if (e instanceof BadRequest) {
60
- console.log(chalk.redBright('Invalid otp. It may have expired.'));
61
- printExitMessage(OTP_HELP_MESSAGE);
62
- process.exit();
63
- }
64
- throw e;
65
- }
66
- };
67
37
  export const validateCliVersion = async (connectClient) => {
68
38
  const manifest = await connectClient.fetchCliManifest();
69
39
  if (manifest.cliVersion > CLI_VERSION) {
70
40
  printExitMessage(chalk.redBright('Your oorja cli is outdated. Please run: npm update -g oorja'));
71
- process.exit(1);
72
- }
73
- };
74
- export const resumeSession = async (env, connectClient, roomId) => {
75
- const token = getENVAccessToken(env);
76
- if (!token)
77
- return null;
78
- // try to validate authorization with existing token
79
- try {
80
- const user = await connectClient.fetchSessionUser();
81
- if (roomId) {
82
- await connectClient.fetchRoom(roomId);
83
- }
84
- return user;
85
- }
86
- catch (e) {
87
- if (e instanceof Unauthorized) {
88
- setENVAccessToken(env, '');
89
- return null;
90
- }
91
- throw e;
92
- }
93
- };
94
- export const preflight = async (env, connectClient) => {
95
- const spinner = ora({
96
- text: 'Authenticating',
97
- discardStdin: false,
98
- }).start();
99
- try {
100
- const user = await connectClient.fetchSessionUser();
101
- spinner.succeed(`Authenticated: Welcome ${user.name}`);
102
- if (user.profileType === 'anon') {
103
- // don't persist tokens for anonymous users
104
- setENVAccessToken(env, '');
105
- console.log(chalk.yellowBright("You're an anonymous user. CLI will not remember the auth-token"));
106
- }
107
- spinner.start('Connecting..');
108
- return connectClient
109
- .establishSocket()
110
- .then(() => {
111
- spinner.succeed('Connected').clear();
112
- return user;
113
- })
114
- .catch((e) => {
115
- spinner.fail('Socket connection failure..');
116
- throw e;
117
- });
118
- }
119
- catch (e) {
120
- setENVAccessToken(env, '');
121
- if (e instanceof Unauthorized) {
122
- spinner.fail();
123
- printExitMessage('Your access token failed authentication, resetting...');
124
- process.exit(33);
125
- }
126
- else {
127
- spinner.fail();
128
- printExitMessage('Something went wrong :(');
129
- }
130
- throw e;
41
+ exit(1);
131
42
  }
132
43
  };
@@ -7,6 +7,12 @@ export type TeletypeOptions = {
7
7
  shell: string;
8
8
  multiplex: boolean;
9
9
  process: NodeJS.Process;
10
- joinChannel: (options: JoinChannelOptions<any>) => Channel;
10
+ joinChannel: (options: JoinChannelOptions<TeletypeChannelParams, unknown>) => Channel;
11
+ };
12
+ type TeletypeChannelParams = {
13
+ username: string;
14
+ hostname: string;
15
+ multiplexed: boolean;
11
16
  };
12
17
  export declare const teletypeApp: (options: TeletypeOptions) => Promise<unknown>;
18
+ export {};
@@ -5,6 +5,7 @@ import chalk from 'chalk';
5
5
  import { Unauthorized } from '../connect/errors.js';
6
6
  import { encrypt, decrypt } from '../encryption.js';
7
7
  import { Future, printExitMessage } from '../utils.js';
8
+ import { exit } from '../exit.js';
8
9
  var MessageType;
9
10
  (function (MessageType) {
10
11
  MessageType["IN"] = "i";
@@ -98,7 +99,7 @@ export const teletypeApp = (options) => {
98
99
  },
99
100
  onClose: () => {
100
101
  printExitMessage(chalk.redBright('connection closed, terminated stream.'));
101
- process.exit(3);
102
+ exit(3);
102
103
  },
103
104
  onError: (err) => {
104
105
  if (err instanceof Unauthorized) {
@@ -107,7 +108,7 @@ export const teletypeApp = (options) => {
107
108
  else {
108
109
  printExitMessage(chalk.redBright('connection error, terminated stream.'));
109
110
  }
110
- process.exit(4);
111
+ exit(4);
111
112
  },
112
113
  onMessage: ({ from: { session }, t, d }) => {
113
114
  switch (t) {
@@ -118,6 +119,12 @@ export const teletypeApp = (options) => {
118
119
  case MessageType.IN:
119
120
  const data = decrypt(d, options.roomKey);
120
121
  const userId = session.split(':')[0];
122
+ const userType = session.split(':')[2];
123
+ if (userType === 'task') {
124
+ printExitMessage(chalk.redBright(`unexpected input from user: ${userId} with task-token, terminating stream for safety. Please report this issue`));
125
+ exit(5);
126
+ return;
127
+ }
121
128
  if (options.multiplex) {
122
129
  term.write(data);
123
130
  return;
@@ -127,7 +134,7 @@ export const teletypeApp = (options) => {
127
134
  }
128
135
  else {
129
136
  printExitMessage(chalk.redBright(`unexpected input from user: ${userId}, terminating stream for safety. Please report this issue`));
130
- process.exit(5);
137
+ exit(5);
131
138
  }
132
139
  }
133
140
  },
@@ -1,5 +1,5 @@
1
1
  export declare const printExitMessage: (printMessage: string) => void;
2
- export declare const promptRoomLink: () => Promise<any>;
2
+ export declare const promptStreamKey: () => Promise<string>;
3
3
  export declare class Future<T> {
4
4
  promise: Promise<T>;
5
5
  resolve?: (arg: T) => void;
package/dist/lib/utils.js CHANGED
@@ -3,15 +3,15 @@ import { writeSync } from 'fs';
3
3
  export const printExitMessage = (printMessage) => {
4
4
  writeSync(process.stdout.fd, printMessage);
5
5
  };
6
- export const promptRoomLink = async () => {
7
- const { spaceLink } = await inquirer.prompt([
6
+ export const promptStreamKey = async () => {
7
+ const { streamKey } = await inquirer.prompt([
8
8
  {
9
9
  type: 'input',
10
- name: 'spaceLink',
11
- message: 'Enter the space secret link (copy URL from address bar in your browser):',
10
+ name: 'streamKey',
11
+ message: 'Enter the stream-key (copy from teletype app within the space):',
12
12
  },
13
13
  ]);
14
- return spaceLink;
14
+ return streamKey;
15
15
  };
16
16
  export class Future {
17
17
  promise;
@@ -25,15 +25,15 @@
25
25
  "tty"
26
26
  ],
27
27
  "args": {
28
- "space": {
29
- "name": "space"
28
+ "streamKey": {
29
+ "name": "streamKey"
30
30
  }
31
31
  },
32
32
  "description": "Launch a terminal streaming session in oorja.",
33
33
  "examples": [
34
- "\u001b[94m$ teletype\u001b[39m\nWill prompt to choose streaming destination - existing space or create a new one.\n\n",
35
- "\u001b[94m$ teletype 'https://oorja.io/spaces?id=foo#key'\u001b[39m\nWill stream to the space specified by secret link, you must have joined the space before streaming.\n\n",
36
- "\u001b[94m$ teletype -m\u001b[39m\nWill also allow participants to write to your terminal!\n\n"
34
+ "\u001b[94m$ teletype\u001b[39m\nWill prompt to choose streaming destination - either enter a stream key for an existing space or create a new space.\n\n",
35
+ "\u001b[94m$ teletype 'sk-xxxx:space-id#encryption-secret'\u001b[39m\nWill stream to the space using the secret stream-key. NOTE: stream-keys are personal (generated for you in the teletype app at oorja.io), do not accept them from other people, nor should\nyou share your stream-keys with others.\n\n",
36
+ "\u001b[94m$ teletype -m\u001b[39m\nWill also allow participants to write to your terminal! Collaboration mode must be explicitly enabled.\n\n"
37
37
  ],
38
38
  "flags": {
39
39
  "help": {
@@ -47,7 +47,7 @@
47
47
  "char": "s",
48
48
  "description": "shell to use. e.g. bash, fish",
49
49
  "name": "shell",
50
- "default": "/usr/bin/zsh",
50
+ "default": "bash",
51
51
  "hasDynamicHelp": false,
52
52
  "multiple": false,
53
53
  "type": "option"
@@ -85,5 +85,5 @@
85
85
  ]
86
86
  }
87
87
  },
88
- "version": "2.1.7"
88
+ "version": "2.2.0"
89
89
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "oorja",
3
3
  "description": "stream terminals to the web and more.",
4
- "version": "2.1.7",
4
+ "version": "2.2.0",
5
5
  "keywords": [
6
6
  "teletype",
7
7
  "terminal",
@@ -38,40 +38,34 @@
38
38
  "oclif": {
39
39
  "bin": "oorja",
40
40
  "commands": "./dist/commands",
41
- "dirname": "oorja",
42
- "plugins": [
43
- "@oclif/plugin-help",
44
- "conf-cli"
45
- ]
41
+ "dirname": "oorja"
46
42
  },
47
43
  "dependencies": {
48
- "@msgpack/msgpack": "2.8.0",
49
- "@oclif/core": "^4.4.0",
50
- "@oclif/plugin-help": "^6.2.29",
51
- "camelcase-keys": "^9.1.3",
52
- "chalk": "^5.4.1",
53
- "conf-cli": "0.1.9",
44
+ "@msgpack/msgpack": "3.1.2",
45
+ "@oclif/core": "^4.5.3",
46
+ "@oclif/plugin-help": "^6.2.32",
47
+ "camelcase-keys": "^10.0.0",
48
+ "chalk": "^5.6.2",
54
49
  "haversine-distance": "^1.2.4",
55
- "inquirer": "^12.6.3",
50
+ "inquirer": "^12.9.4",
56
51
  "node-pty": "^1.0.0",
57
52
  "ora": "^8.2.0",
58
- "phoenix": "1.7.21",
53
+ "phoenix": "1.8.1",
59
54
  "terminal-size": "^4.0.0"
60
55
  },
61
56
  "devDependencies": {
62
57
  "@oclif/prettier-config": "^0.2.1",
63
- "@types/inquirer": "^9.0.8",
58
+ "@types/inquirer": "^9.0.9",
64
59
  "@types/node": "^22.10.2",
65
60
  "@types/phoenix": "^1.6.6",
66
- "eslint": "^9.29.0",
67
- "eslint-config-oclif": "^6.0.72",
68
- "eslint-config-oclif-typescript": "^3.1.14",
69
- "eslint-config-prettier": "^9.1.0",
70
- "oclif": "^4.19.0",
71
- "prettier": "^3.5.3",
61
+ "eslint": "^9.35.0",
62
+ "eslint-config-oclif": "^6.0.102",
63
+ "eslint-config-prettier": "^10.1.8",
64
+ "oclif": "^4.22.18",
65
+ "prettier": "^3.6.2",
72
66
  "shx": "^0.4.0",
73
67
  "ts-node": "^10.9.2",
74
- "typescript": "^5.8.3"
68
+ "typescript": "^5.9.2"
75
69
  },
76
70
  "engines": {
77
71
  "node": ">=22.10.0"