oorja 2.0.8 → 2.1.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.
package/README.md CHANGED
@@ -9,11 +9,11 @@ cli tool that allows you to share your terminal online conveniently. Check out [
9
9
 
10
10
 
11
11
  <p align="center">
12
- <img width="600" src="https://teletype.oorja.io/images/cli-demo.svg">
12
+ <img width="600" src="https://oorja.io/images/cli-demo.svg">
13
13
  </p>
14
14
 
15
15
  <p align="center">
16
- <img src="https://teletype.oorja.io/images/teletype-session.png">
16
+ <img src="https://oorja.io/images/teletype-session.png">
17
17
  </p>
18
18
 
19
19
  Your stream can be view-only or collaboration enabled (command-line flag).
@@ -95,9 +95,10 @@ Will also allow participants to write to your terminal!
95
95
  text: chalk.bold('Creating space with TeleType app'),
96
96
  discardStdin: false,
97
97
  }).start();
98
+ const now = new Date();
98
99
  const { roomKey } = await app
99
100
  .createRoom({
100
- roomName: 'Untitled Space',
101
+ roomName: `Teletype session - ${os.hostname()} @ ${now.getHours()}:${now.getMinutes().toString().padStart(2, '0')}`,
101
102
  apps: {
102
103
  defaultFocus: '39',
103
104
  appList: [
@@ -1,5 +1,5 @@
1
1
  import { URL } from 'url';
2
- export declare const CLI_VERSION = 2.4;
2
+ export declare const CLI_VERSION = 2.5;
3
3
  import Conf from 'conf';
4
4
  export declare const config: Conf<string>;
5
5
  export type env = 'local' | 'prod';
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- export const CLI_VERSION = 2.4;
2
+ export const CLI_VERSION = 2.5;
3
3
  import Conf from 'conf';
4
4
  export const config = new Conf({
5
5
  projectName: 'oorja',
@@ -5,9 +5,12 @@ export declare class ApiClientError extends Error {
5
5
  }
6
6
  export declare class ConnectClient {
7
7
  private config;
8
- private client;
8
+ private baseURL;
9
+ private headers;
10
+ private timeout;
9
11
  private socket?;
10
12
  constructor(env: env, region: string);
13
+ private _fetch;
11
14
  fetchCliManifest: () => Promise<CliManifest>;
12
15
  fetchSessionUser: () => Promise<User>;
13
16
  createRoom: ({ roomName, apps }: CreateRoomOptions) => Promise<Room>;
@@ -1,8 +1,6 @@
1
1
  // backend api client
2
- import https from 'https';
3
- import axios, { AxiosError } from 'axios';
4
2
  import { Encoder, Decoder } from '@msgpack/msgpack';
5
- const encoder = new Encoder({ ignoreUndefined: true });
3
+ const encoder = new Encoder();
6
4
  const decoder = new Decoder();
7
5
  import { defaultParser } from './resources.js';
8
6
  import { getConnectConfig } from '../config.js';
@@ -13,28 +11,51 @@ export class ApiClientError extends Error {
13
11
  }
14
12
  export class ConnectClient {
15
13
  config;
16
- client;
14
+ baseURL;
15
+ headers;
16
+ timeout;
17
17
  socket;
18
18
  constructor(env, region) {
19
19
  const config = getConnectConfig(env, region);
20
- this.client = axios.create({
21
- httpsAgent: new https.Agent({
22
- minVersion: 'TLSv1.2',
23
- maxVersion: 'TLSv1.2',
24
- }),
25
- baseURL: connectBaseURL(config.host),
26
- timeout: 5000,
27
- responseType: 'json',
28
- headers: {
29
- 'x-access-token': config.token || '',
30
- },
31
- });
20
+ this.baseURL = connectBaseURL(config.host);
21
+ this.headers = {
22
+ 'x-access-token': config.token || '',
23
+ 'Content-Type': 'application/json',
24
+ };
25
+ this.timeout = 5000;
32
26
  this.config = config;
33
27
  }
28
+ // Private helper to encapsulate fetch with timeout and consistent error handling.
29
+ async _fetch(path, options = {}) {
30
+ const controller = new AbortController();
31
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
32
+ try {
33
+ const response = await fetch(`${this.baseURL}${path}`, {
34
+ ...options,
35
+ headers: this.headers,
36
+ signal: controller.signal,
37
+ });
38
+ if (!response.ok) {
39
+ // Throw an object that mimics the AxiosError structure for the handleError function.
40
+ throw { response };
41
+ }
42
+ return response;
43
+ }
44
+ catch (error) {
45
+ if (error.name === 'AbortError') {
46
+ throw new Error('Request timed out');
47
+ }
48
+ throw error; // Re-throw other errors (like the custom {response} or network errors)
49
+ }
50
+ finally {
51
+ clearTimeout(timeoutId);
52
+ }
53
+ }
34
54
  fetchCliManifest = async () => {
35
55
  try {
36
- const response = await this.client.get('/cli');
37
- return camelcaseKeys(response.data);
56
+ const response = await this._fetch('/cli');
57
+ const data = await response.json();
58
+ return camelcaseKeys(data);
38
59
  }
39
60
  catch (error) {
40
61
  return handleError(error);
@@ -42,8 +63,9 @@ export class ConnectClient {
42
63
  };
43
64
  fetchSessionUser = async () => {
44
65
  try {
45
- const response = await this.client.get('/session/user');
46
- return defaultParser(response.data.data);
66
+ const response = await this._fetch('/session/user');
67
+ const data = await response.json();
68
+ return defaultParser(data.data);
47
69
  }
48
70
  catch (error) {
49
71
  return handleError(error);
@@ -58,8 +80,12 @@ export class ConnectClient {
58
80
  },
59
81
  };
60
82
  try {
61
- const response = await this.client.post('/rooms', body);
62
- return defaultParser(response.data.data);
83
+ const response = await this._fetch('/rooms', {
84
+ method: 'POST',
85
+ body: JSON.stringify(body),
86
+ });
87
+ const data = await response.json();
88
+ return defaultParser(data.data);
63
89
  }
64
90
  catch (error) {
65
91
  return handleError(error);
@@ -67,8 +93,9 @@ export class ConnectClient {
67
93
  };
68
94
  createAnonymousUser = async () => {
69
95
  try {
70
- const response = await this.client.post('/session/anon');
71
- return response.data.access_token;
96
+ const response = await this._fetch('/session/anon', { method: 'POST' });
97
+ const data = await response.json();
98
+ return data.access_token;
72
99
  }
73
100
  catch (error) {
74
101
  return handleError(error);
@@ -76,11 +103,15 @@ export class ConnectClient {
76
103
  };
77
104
  accessTokenFromRoomParticipantOTP = async (roomId, otp) => {
78
105
  try {
79
- const response = await this.client.post('/access_tokens/from_room_participant_otp', {
80
- room_id: roomId,
81
- otp: otp,
106
+ const response = await this._fetch('/access_tokens/from_room_participant_otp', {
107
+ method: 'POST',
108
+ body: JSON.stringify({
109
+ room_id: roomId,
110
+ otp: otp,
111
+ }),
82
112
  });
83
- return response.data.data.token;
113
+ const data = await response.json();
114
+ return data.data.token;
84
115
  }
85
116
  catch (error) {
86
117
  return handleError(error);
@@ -88,8 +119,9 @@ export class ConnectClient {
88
119
  };
89
120
  fetchRoom = async (roomId) => {
90
121
  try {
91
- const response = await this.client.get(`/rooms/${roomId}`);
92
- return defaultParser(response.data.data);
122
+ const response = await this._fetch(`/rooms/${roomId}`);
123
+ const data = await response.json();
124
+ return defaultParser(data.data);
93
125
  }
94
126
  catch (error) {
95
127
  return handleError(error);
@@ -182,7 +214,10 @@ export class ConnectClient {
182
214
  }
183
215
  const connectBaseURL = (host) => `https://${host}/api/v1`;
184
216
  const handleError = (error) => {
185
- if (error instanceof AxiosError) {
217
+ // This function was originally designed for AxiosError.
218
+ // The custom _fetch method now throws an object with a `response` key
219
+ // for HTTP errors (`{ response }`), allowing this logic to remain unchanged.
220
+ if (error?.response) {
186
221
  const { response } = error;
187
222
  if (response) {
188
223
  switch (response.status) {
@@ -27,6 +27,7 @@ export const teletypeApp = (options) => {
27
27
  resizeBestFit(term, userDimensions);
28
28
  };
29
29
  return new Promise((resolve, reject) => {
30
+ let sessionCount = 0;
30
31
  const channel = options.joinChannel({
31
32
  channel: `teletype:${options.roomKey.roomId}`,
32
33
  params: {
@@ -44,15 +45,32 @@ export const teletypeApp = (options) => {
44
45
  cols: dimensions.cols,
45
46
  rows: dimensions.rows,
46
47
  cwd: options.process.cwd(),
47
- // @ts-ignore
48
48
  env: options.process.env,
49
49
  });
50
+ setTimeout(() => {
51
+ if (options.shell.includes('bash')) {
52
+ stdout.write('Adjusting shell prompt to show streaming indicator\n');
53
+ term.write("export PS1='📡 [streaming] '$PS1\n");
54
+ }
55
+ if (options.shell.includes('zsh')) {
56
+ stdout.write('Adjusting shell prompt to show streaming indicator\n');
57
+ term.write("PROMPT='📡 [streaming] '$PROMPT\n");
58
+ }
59
+ if (options.shell.includes('fish')) {
60
+ stdout.write('Adjusting shell prompt to show streaming indicator\n');
61
+ term.write('functions -c fish_prompt __orig_fish_prompt; ' +
62
+ "function fish_prompt; echo -n '📡 [streaming] '; __orig_fish_prompt; end\n");
63
+ }
64
+ }, 100); // wait for shell to be ready
50
65
  // track own dimensions and keep it up to date
51
66
  setInterval(reEvaluateOwnDimensions, 1000);
52
67
  term.onData((d) => {
53
68
  stdout.write(d);
54
- // revisit: is it worth having one letter names, instead of something descriptive
55
- // does it really save bytes?
69
+ if (sessionCount < 2) {
70
+ // 1 sub for own channel session
71
+ // < 2 means no subscribers. no point pushing data.
72
+ return;
73
+ }
56
74
  channel.push('new_msg', {
57
75
  t: MessageType.OUT,
58
76
  b: true,
@@ -102,8 +120,11 @@ export const teletypeApp = (options) => {
102
120
  }
103
121
  }
104
122
  },
105
- handleSessionJoin: (s) => { },
123
+ handleSessionJoin: (s) => {
124
+ sessionCount++;
125
+ },
106
126
  handleSessionLeave: (s) => {
127
+ sessionCount -= 1;
107
128
  s && delete userDimensions[s];
108
129
  resizeBestFit(term, userDimensions);
109
130
  },
@@ -85,5 +85,5 @@
85
85
  ]
86
86
  }
87
87
  },
88
- "version": "2.0.8"
88
+ "version": "2.1.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.0.8",
4
+ "version": "2.1.0",
5
5
  "keywords": [
6
6
  "teletype",
7
7
  "terminal",
@@ -45,34 +45,33 @@
45
45
  ]
46
46
  },
47
47
  "dependencies": {
48
- "@msgpack/msgpack": "3.0.0-beta3",
49
- "@oclif/core": "^4.2.6",
50
- "@oclif/plugin-help": "^6.2.24",
51
- "axios": "^1.7.9",
48
+ "@msgpack/msgpack": "2.8.0",
49
+ "@oclif/core": "^4.4.0",
50
+ "@oclif/plugin-help": "^6.2.29",
52
51
  "camelcase-keys": "^9.1.3",
53
52
  "chalk": "^5.4.1",
54
53
  "conf-cli": "0.1.9",
55
- "haversine-distance": "^1.2.3",
56
- "inquirer": "^12.4.1",
54
+ "haversine-distance": "^1.2.4",
55
+ "inquirer": "^12.6.3",
57
56
  "node-pty": "^1.0.0",
58
57
  "ora": "^8.2.0",
59
- "phoenix": "1.7.19",
58
+ "phoenix": "1.7.21",
60
59
  "terminal-size": "^4.0.0"
61
60
  },
62
61
  "devDependencies": {
63
62
  "@oclif/prettier-config": "^0.2.1",
64
- "@types/inquirer": "^9.0.7",
63
+ "@types/inquirer": "^9.0.8",
65
64
  "@types/node": "^22.10.2",
66
65
  "@types/phoenix": "^1.6.6",
67
- "eslint": "^9.17.0",
68
- "eslint-config-oclif": "^5.2.2",
69
- "eslint-config-oclif-typescript": "^3.1.13",
66
+ "eslint": "^9.29.0",
67
+ "eslint-config-oclif": "^6.0.72",
68
+ "eslint-config-oclif-typescript": "^3.1.14",
70
69
  "eslint-config-prettier": "^9.1.0",
71
- "oclif": "^4.17.25",
72
- "prettier": "^3.4.2",
73
- "shx": "^0.3.4",
70
+ "oclif": "^4.19.0",
71
+ "prettier": "^3.5.3",
72
+ "shx": "^0.4.0",
74
73
  "ts-node": "^10.9.2",
75
- "typescript": "^5.7.2"
74
+ "typescript": "^5.8.3"
76
75
  },
77
76
  "engines": {
78
77
  "node": ">=22.10.0"