Repository navigation
London | 26-SDC-July | Raihan Sharif | Sprint 2 | Chat-app #124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1d38a28
919a6d9
d5af26b
5200c6f
ef6d1e5
507640d
d7df7b5
e42720b
49ceee5
8f48f94
fe110b9
40dbadd
b5aec51
d31c582
afd1068
80805e0
be3e08f
4306653
590918b
0f9a390
630a3ae
0f632b0
034791b
8b2c69d
09d08ab
4f625eb
065aa34
06b6430
ef22de1
ef2383b
b46622d
67ff4c3
90417f9
69f05c2
2e7dc03
8fa296a
1756f54
ee4caca
8717b46
b551c70
e801458
425817f
5f28f18
2180255
806633e
3361d8e
f98b3e0
6ffe037
2dc2778
a548c4d
d484bf9
59383a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| # Logs | ||
| logs | ||
| *.log | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
| lerna-debug.log* | ||
|
|
||
| # Diagnostic reports (https://nodejs.org/api/report.html) | ||
| report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json | ||
|
|
||
| # Runtime data | ||
| pids | ||
| *.pid | ||
| *.seed | ||
| *.pid.lock | ||
|
|
||
| # Directory for instrumented libs generated by jscoverage/JSCover | ||
| lib-cov | ||
|
|
||
| # Coverage directory used by tools like istanbul | ||
| coverage | ||
| *.lcov | ||
|
|
||
| # nyc test coverage | ||
| .nyc_output | ||
|
|
||
| # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) | ||
| .grunt | ||
|
|
||
| # Bower dependency directory (https://bower.io/) | ||
| bower_components | ||
|
|
||
| # node-waf configuration | ||
| .lock-wscript | ||
|
|
||
| # Compiled binary addons (https://nodejs.org/api/addons.html) | ||
| build/Release | ||
|
|
||
| # Dependency directories | ||
| node_modules/ | ||
| jspm_packages/ | ||
|
|
||
| # Snowpack dependency directory (https://snowpack.dev/) | ||
| web_modules/ | ||
|
|
||
| # TypeScript cache | ||
| *.tsbuildinfo | ||
|
|
||
| # Optional npm cache directory | ||
| .npm | ||
|
|
||
| # Optional eslint cache | ||
| .eslintcache | ||
|
|
||
| # Optional stylelint cache | ||
| .stylelintcache | ||
|
|
||
| # Optional REPL history | ||
| .node_repl_history | ||
|
|
||
| # Output of 'npm pack' | ||
| *.tgz | ||
|
|
||
| # Yarn Integrity file | ||
| .yarn-integrity | ||
|
|
||
| # dotenv environment variable files | ||
| .env | ||
| .env.* | ||
| !.env.example | ||
|
|
||
| # parcel-bundler cache (https://parceljs.org/) | ||
| .cache | ||
| .parcel-cache | ||
|
|
||
| # Next.js build output | ||
| .next | ||
| out | ||
|
|
||
| # Nuxt.js build / generate output | ||
| .nuxt | ||
| dist | ||
| .output | ||
|
|
||
| # Gatsby files | ||
| .cache/ | ||
| # Comment in the public line in if your project uses Gatsby and not Next.js | ||
| # https://nextjs.org/blog/next-9-1#public-directory-support | ||
| # public | ||
|
|
||
| # vuepress build output | ||
| .vuepress/dist | ||
|
|
||
| # vuepress v2.x temp directory | ||
| .temp | ||
|
|
||
| # Sveltekit cache directory | ||
| .svelte-kit/ | ||
|
|
||
| # vitepress build output | ||
| **/.vitepress/dist | ||
|
|
||
| # vitepress cache directory | ||
| **/.vitepress/cache | ||
|
|
||
| # Docusaurus cache and generated files | ||
| .docusaurus | ||
|
|
||
| # Serverless directories | ||
| .serverless/ | ||
|
|
||
| # FuseBox cache | ||
| .fusebox/ | ||
|
|
||
| # DynamoDB Local files | ||
| .dynamodb/ | ||
|
|
||
| # Firebase cache directory | ||
| .firebase/ | ||
|
|
||
| # TernJS port file | ||
| .tern-port | ||
|
|
||
| # Stores Visual Studio Code versions used for testing Visual Studio Code extensions | ||
| .vscode-test | ||
|
|
||
| # pnpm | ||
| .pnpm-store | ||
|
|
||
| # yarn v3 | ||
| .pnp.* | ||
| .yarn/* | ||
| !.yarn/patches | ||
| !.yarn/plugins | ||
| !.yarn/releases | ||
| !.yarn/sdks | ||
| !.yarn/versions | ||
|
|
||
| # Vite files | ||
| vite.config.js.timestamp-* | ||
| vite.config.ts.timestamp-* | ||
| .vite/ | ||
|
|
||
| CORS_ORIGINS | ||
|
|
||
| #VS Code | ||
| .vscode |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| .env | ||
| .env.* | ||
| .git | ||
| node_modules | ||
| npm-debug.log |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| FROM node:26.7.0-alpine | ||
| ENV NODE_ENV=production | ||
| WORKDIR /app | ||
| COPY . . | ||
| RUN npm ci --omit=dev | ||
| USER node | ||
| ENTRYPOINT ["node", "pollingApp.js"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| class EventStream { | ||
| constructor() { | ||
| this.events = []; | ||
| this.sequence = 0; | ||
|
|
||
| // waiters are HTTP polling requests | ||
| this.waiters = new Set(); | ||
|
|
||
| // subscribers are websocket connections. | ||
| this.subscribers = new Set(); | ||
| } | ||
|
|
||
| /** | ||
| * Adds an event to the event stream. {sequnce, type, timestamp, data}. | ||
| * | ||
| * @param {"message.liked" | "message.disliked" | "message.created"} type - The type of event (e.g. "like"). | ||
| * @param {*} data - The message that was added or changed, and the changed fields. | ||
| * @returns The event object, with a sequence number | ||
| */ | ||
| append(type, data) { | ||
| const event = { | ||
| sequence: ++this.sequence, | ||
| type, | ||
| timestamp: Date.now(), | ||
| data, | ||
| }; | ||
|
|
||
| this.events.push(event); | ||
| this.notifyWaiters(); | ||
| this.notifySubscribers(); | ||
| return event; | ||
| } | ||
|
|
||
| /** | ||
| * | ||
| * @param {number} sequence - returns events with sequence number > given sequence number. | ||
| * @returns list of events. | ||
| */ | ||
| getAfter(sequence) { | ||
| return this.events.filter((event) => event.sequence > sequence); | ||
| } | ||
|
|
||
| // websocket subscriber | ||
| subscribe(callback) { | ||
| const subscriber = { | ||
| cursor: null, | ||
| callback: callback, | ||
| }; | ||
| this.subscribers.add(subscriber); | ||
| return subscriber; | ||
| } | ||
|
|
||
| unsubscribe(subscriber) { | ||
| this.subscribers.delete(subscriber); | ||
| } | ||
|
|
||
| // WebSocket subscribers | ||
| notifySubscribers() { | ||
| for (const subscriber of this.subscribers) { | ||
| if (subscriber.cursor === null) { | ||
| continue; | ||
| } | ||
|
|
||
| const events = this.getAfter(subscriber.cursor); | ||
|
|
||
| if (events.length === 0) { | ||
| continue; | ||
| } | ||
|
|
||
| try { | ||
| subscriber.callback(events); | ||
| } catch (err) { | ||
| console.error("subscriber failed", err); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Polling (HTTP) connections waiting for new events | ||
| * If no new events are available, keeps connection open for specified time. | ||
| * @param {number} after - returns events after this sequence number. | ||
| * @param {*} timeout - How long to wait for a new event before closing the connection. | ||
| * @returns A promise which resolves with a list of new events or empty list | ||
| * if timer expires. | ||
| */ | ||
| waitForEvents(after, timeout = 30_000) { | ||
| const existing = this.getAfter(after); | ||
|
|
||
| if (existing.length > 0) { | ||
| return Promise.resolve(existing); | ||
| } | ||
|
|
||
| return new Promise((resolve) => { | ||
| const waiter = { | ||
| after, | ||
| resolve, | ||
| timer: setTimeout(() => { | ||
| this.waiters.delete(waiter); | ||
| resolve([]); | ||
| }, timeout), | ||
| }; | ||
|
|
||
| this.waiters.add(waiter); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Sends new events for waiting HTTP requests, if there are any events to send. | ||
| */ | ||
| notifyWaiters() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the difference between a "waiter" and a "subscriber"?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this code waiters are the polling requests, and subscribers are WebSocket connections. I was going to create a more general "subscriber" interface so that both WebSocket and HTTP, but I didn't have the time to get it done. I've added some comments to make this clearer. |
||
| for (const waiter of this.waiters) { | ||
| const events = this.getAfter(waiter.after); | ||
|
|
||
| if (events.length === 0) { | ||
| continue; | ||
| } | ||
|
|
||
| clearTimeout(waiter.timer); | ||
| this.waiters.delete(waiter); | ||
| waiter.resolve(events); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export { EventStream }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| export class Message { | ||
| static #nextId = 0; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain what the static keyword here means?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a class field, not an instance field. It belongs to the class itself rather than any one individual instance of the message class. |
||
|
|
||
| /** | ||
| * | ||
| * @param {string} username the message sender | ||
| * @param {*} msg_body text content of the message | ||
| */ | ||
| constructor(username, msg_body, likes = 0, dislikes = 0) { | ||
| this.id = Message.#nextId++; | ||
| this.username = username; | ||
| this.msg_body = msg_body; | ||
| this.createdAt = new Date(); | ||
| this.likes = likes; | ||
| this.dislikes = dislikes; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| class ValidationError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "ValidationError"; | ||
| } | ||
| } | ||
|
|
||
| class NotFoundError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "NotFoundError"; | ||
| } | ||
| } | ||
|
|
||
| export { ValidationError, NotFoundError }; |
Uh oh!
There was an error while loading. Please reload this page.