8000 wip edlink by mrfinch · Pull Request #8013 · codecombat/codecombat · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

wip edlink #8013

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

wip edlink #8013

wants to merge 2 commits into from

Conversation

mrfinch
Copy link
Contributor
@mrfinch mrfinch commented Jun 12, 2025

fixes eng-1833

Summary by CodeRabbit

  • New Features
    • Added ClassLink as a new social login option for both authentication and account creation modals.
  • Improvements
    • Unified and simplified the login flow for Schoology and ClassLink using a shared authentication handler.
    • Updated login error messages to be more generic and inclusive of multiple providers.
    • Refreshed Clever login button images for a consistent look.
  • Style
    • Enhanced styling for Clever, Schoology, and ClassLink login buttons for improved visual consistency.
  • Localization
    • Simplified login failure message keys and removed Schoology-specific localization strings.

Copy link
Contributor
coderabbitai bot commented Jul 1, 2025

Walkthrough

A new OAuth2 login handler for ClassLink was introduced, mirroring the refactored Schoology login flow to use a shared EdlinkBaseHandler class. The UI was updated to add ClassLink as a login option and to generalize login failure messages. Related styles and localization keys were updated, and the Clever login button image was changed.

Changes

File(s) Change Summary
app/core/social-handlers/EdlinkBaseHandler.js Added new EdlinkBaseHandler class to manage OAuth2 login via popup and storage event.
app/core/social-handlers/ClassLinkHandler.js Added new ClassLinkHandler extending EdlinkBaseHandler for ClassLink OAuth2 login.
app/core/social-handlers/SchoologyHandler.js Refactored to extend EdlinkBaseHandler, removing custom logic and methods.
app/views/core/AuthModal.js Updated to use handler classes for Schoology and new ClassLink; added event handler for ClassLink login.
app/locale/en.js Replaced schoology_login_failed with generic login_failed; removed schoology_signup.
app/styles/modal/auth-modal.sass Updated styles for Clever/ClassLink images and login buttons.
app/styles/modal/create-account-modal/basic-info-view.sass Unified styles for Schoology/ClassLink login; tweaked Clever logo styling and gaps.
app/templates/core/auth-modal.pug Added ClassLink login button; updated Clever image; minor whitespace fixes.
app/templates/core/create-account-modal/basic-info-view.pug Added ClassLink login option for teacher/student signup; updated Clever/Schoology UI.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AuthModal
    participant SchoologyHandler
    participant ClassLinkHandler
    participant EdlinkBaseHandler
    participant OAuthProvider

    User->>AuthModal: Clicks Schoology or ClassLink login button
    AuthModal->>SchoologyHandler: onClickSchoologyLoginButton()
    AuthModal->>ClassLinkHandler: onClickClasslinkLoginButton()
    SchoologyHandler->>EdlinkBaseHandler: logInWithEdlink()
    ClassLinkHandler->>EdlinkBaseHandler: logInWithEdlink()
    EdlinkBaseHandler->>OAuthProvider: Open OAuth popup with state
    OAuthProvider-->>EdlinkBaseHandler: Write result to localStorage
    EdlinkBaseHandler->>AuthModal: Resolve login result
    AuthModal->>User: Reload page or show login failure
Loading

Poem

A new link hops into view,
ClassLink joins the login crew!
With shared code, Schoology’s neat,
OAuth flows now can’t be beat.
Buttons styled and logos blue,
Rabbits cheer for something new—
Logins made easy, just for you!

((\
( -.-)
o_(")(")

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.38.1)
app/locale/en.js
🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm warn EBADENGINE Unsupported engine {
npm warn EBADENGINE package: 'treema@0.0.0',
npm warn EBADENGINE required: { node: '0.8.x' },
npm warn EBADENGINE current: { node: 'v24.3.0', npm: '11.4.2' }
npm warn EBADENGINE }
npm warn skipping integrity check for git dependency ssh://git@github.com/codecombat/treema.git
npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-07-01T06_42_59_313Z-debug-0.log

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor
@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (4)
app/styles/modal/auth-modal.sass (2)

250-259: Hard-coded width risks truncation in i18n – prefer min-width

Changing gap to 5px is fine, but the button still uses a strict width: 130px. Longer translations (e.g. German, Finnish) routinely overflow. Consider switching to min-width so the button can grow when necessary.

 .signup-with-button
@@
-  width: 130px
+  min-width: 130px

170-175: Duplicate declarations for #schoology-login-btn – consolidate to one block

#schoology-login-btn is styled twice (lines 170-175 and 260-264). The second block overrides the first border colour, increasing maintenance cost and cognitive load.

Merge the common rules into a single selector (now that #classlink-login-btn shares them) to follow DRY:

-#schoology-login-btn
-  z-index: 100
-  border: 1px solid black
-  background-color: white
-  color: black
+// shared styling
+#schoology-login-btn,
+#classlink-login-btn
+  z-index: 100
+  border: 1px solid #117bbc
+  background-color: white
+  color: black

Then delete the later duplicate block.

Also applies to: 260-264

app/templates/core/create-account-modal/basic-info-view.pug (1)

31-36: Consider localization consistency for social login buttons.

The Schoology button changed from using data-i18n to hardcoded text "Schoology", and ClassLink also uses hardcoded "ClassLink". While this might be intentional for brand consistency, consider whether these provider names should be localized or kept as-is across all locales.

app/views/core/AuthModal.js (1)

328-328: Improve error handling specificity.

Consider providing more specific error messages for different failure scenarios rather than a generic "login failed" message. This would improve user experience by helping users understand what went wrong.

- noty({ text: $.i18n.t('login.login_failed'), layout: 'topCenter', type: 'error', timeout: 5000, killer: false, dismissQueue: true })
+ noty({ text: $.i18n.t('login.edlink_login_failed'), layout: 'topCenter', type: 'error', timeout: 5000, killer: false, dismissQueue: true })

Alternatively, pass the provider name to create more specific error messages:

+ const providerName = handler.constructor.name.replace('Handler', '')
+ noty({ text: $.i18n.t('login.provider_login_failed', { provider: providerName }), layout: 'topCenter', type: 'error', timeout: 5000, killer: false, dismissQueue: true })

Also applies to: 338-338

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b338db8 and 1861315.

⛔ Files ignored due to path filters (3)
  • app/assets/images/pages/modal/auth/classlink-logo-small.png is 8000 excluded by !**/*.png
  • app/assets/images/pages/modal/auth/classlink-logo-text.png is excluded by !**/*.png
  • app/assets/images/pages/modal/auth/clever-logo-blue.png is excluded by !**/*.png
📒 Files selected for processing (9)
  • app/core/social-handlers/ClassLinkHandler.js (1 hunks)
  • app/core/social-handlers/EdlinkBaseHandler.js (1 hunks)
  • app/core/social-handlers/SchoologyHandler.js (1 hunks)
  • app/locale/en.js (1 hunks)
  • app/styles/modal/auth-modal.sass (2 hunks)
  • app/styles/modal/create-account-modal/basic-info-view.sass (2 hunks)
  • app/templates/core/auth-modal.pug (2 hunks)
  • app/templates/core/create-account-modal/basic-info-view.pug (1 hunks)
  • app/views/core/AuthModal.js (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
app/core/social-handlers/ClassLinkHandler.js (2)
app/core/social-handlers/SchoologyHandler.js (1)
  • EdlinkBaseHandler (1-1)
app/views/core/AuthModal.js (2)
  • require (21-21)
  • ClassLinkHandler (23-23)
app/core/social-handlers/EdlinkBaseHandler.js (3)
app/core/social-handlers/SchoologyHandler.js (1)
  • EdlinkBaseHandler (1-1)
app/core/social-handlers/ClassLinkHandler.js (1)
  • EdlinkBaseHandler (1-1)
app/lib/user-utils.js (1)
  • localStorage (2-2)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: ESLint CI
  • GitHub Check: Node.js CI (20.19.2)
🔇 Additional comments (8)
app/core/social-handlers/ClassLinkHandler.js (1)

1-7: Clean implementation following the established pattern.

The ClassLinkHandler correctly extends EdlinkBaseHandler with the appropriate provider name. The implementation is consistent with the refactored approach used for other Edlink providers.

app/styles/modal/create-account-modal/basic-info-view.sass (2)

37-55: Good styling consistency for Edlink-based providers.

The grouped styling for .schoology-login,.classlink-login and their respective buttons is appropriate since both providers now share the same OAuth2 base handler implementation. The consistent styling reinforces their similar functionality.


61-65: Visual improvements for Clever logo styling.

The updated Clever logo styling with background, padding, and border creates better visual consistency with other social login buttons.

app/templates/core/create-account-modal/basic-info-view.pug (1)

27-27: Updated Clever logo image source.

The image source change from clever_sso_button@2x.png to clever-logo-blue.png aligns with the styling updates in the SASS file.

app/templates/core/auth-modal.pug (3)

81-81: Updated Clever logo image source for consistency.

The image source change aligns with the updates in the create-account modal and styling files.


87-89: ClassLink integration follows established pattern.

The new ClassLink login button implementation is consistent with the existing social login button pattern and integrates well with the new handler architecture.


122-122: Clever SSO button image asset exists and is correctly referenced

Verified that the clever_sso_button@2x.png file lives at
app/assets/images/pages/modal/auth/clever_sso_button@2x.png
which aligns with the src="/images/pages/modal/auth/clever_sso_button@2x.png" reference in app/templates/core/auth-modal.pug. No further action required.

app/views/core/AuthModal.js (1)

43-43: LGTM! Event handler properly added for ClassLink button.

The event handler follows the existing pattern and correctly maps to the new login method.

Comment on lines 232 to 236
.clever-login-img
height: 43px
background: white
padding: 5px

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Padding increases rendered height – add box-sizing to avoid visual mis-alignment

The element has a fixed height: 43px, yet the new padding: 5px will make the rendered box 53 px tall unless box-sizing: border-box is applied. On small screens this can break vertical centring inside the .network-login button.

 .clever-login-img
   height: 43px
   background: white
   padding: 5px
+  box-sizing: border-box   // keep total height at 43 px
🤖 Prompt for AI Agents
In app/styles/modal/auth-modal.sass around lines 232 to 236, the
.clever-login-img class has a fixed height of 43px and padding of 5px, which
increases the rendered height and can break vertical alignment. To fix this, add
box-sizing: border-box to the .clever-login-img style so padding is included
within the fixed height, preserving the intended layout.

Comment on lines 704 to 708
gplus_linked: 'Google Account linked successfully',
plus_link_error: 'Google Account linked failed',
email_or_username_or_library: 'Email or Username or Library Card number',
schoology_login_failed: 'Login failed. You may need to sign up first.',
schoology_signup: 'Sign Up with Schoology',
login_failed: 'Login failed. You may need to sign up first.',
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify duplicate-key collision and cross-locale consistency

login_failed is a good generic replacement, but please confirm:

  1. The same key does not already exist elsewhere in this file (duplicate object keys are silently overwritten in JS).
  2. The now-unused keys (schoology_login_failed, schoology_signup) have been removed from all locale files and the codebase, otherwise dead code and fallback warnings may surface.
  3. Other locale files define login_failed; otherwise they will fall back to English.

Quick check script:


🏁 Script executed:

#!/bin/bash
# 1. look for duplicate keys in en.js
rg --line-number $'login_failed:' app/locale/en.js

# 2. ensure old keys are gone project-wide
rg --line-number -e $'schoology_login_failed' -e $'schoology_signup' app/locale

# 3. list locales missing the new key
fd -e js app/locale | while read f; do
  if ! rg -q $'login_failed:' "$f"; then echo "⚠️  missing in $f"; fi
done

Length of output: 12108


To verify that every locale file defines the new login_failed key, please run:


🏁 Script executed:

#!/bin/bash
# 3. list any locale files missing the new key
rg -L "login_failed:" -g '*.js' app/locale

Length of output: 6524


Cleanup obsolete schoology_* keys and propagate login_failed to all locales

Our checks revealed:

  • en.js has a single login_failed entry (no duplicates).
  • schoology_login_failed/schoology_signup are still present (commented) in nearly every locale and actively defined in rot13.js.
  • Many locale files lack the new login_failed key and will silently fall back to English.

Please:

  • Remove every schoology_login_failed and schoology_signup line (including the active entries in rot13.js and all commented blocks).
  • Add
    login_failed: 'Login failed. You may need to sign up first.',
    to each locale file missing it.
  • Verify with:
    rg -N "schoology_login_failed|schoology_signup" app/locale
    rg -L "login_failed:" -g '*.js' app/locale
    to confirm no stale keys remain and every locale defines login_failed.
🤖 Prompt for AI Agents
In app/locale/en.js around lines 704 to 708, remove all occurrences of the
obsolete keys `schoology_login_failed` and `schoology_signup` from all locale
files including rot13.js and any commented blocks. Then, ensure that the
`login_failed` key with the value 'Login failed. You may need to sign up first.'
is added to every locale file that currently lacks it. Finally, run the provided
ripgrep commands to verify no stale keys remain and all locales define
`login_failed`.

const state = Math.random().toString(36).substring(2)
const url = `/auth/oauth2/${this.providerName}?state=${state}`

window.open(url, '_blank', 'width=800,height=600', false)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for popup window opening.

The popup window might be blocked by browsers or fail to open, which should be handled gracefully.

-    window.open(url, '_blank', 'width=800,height=600', false)
+    const popup = window.open(url, '_blank', 'width=800,height=600', false)
+    if (!popup) {
+      throw new Error('Popup blocked - please allow popups for this site')
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
window.open(url, '_blank', 'width=800,height=600', false)
const popup = window.open(url, '_blank', 'width=800,height=600', false)
if (!popup) {
throw new Error('Popup blocked - please allow popups for this site')
}
🤖 Prompt for AI Agents
In app/core/social-handlers/EdlinkBaseHandler.js at line 13, the call to
window.open does not handle the case where the popup might be blocked or fail to
open. Modify the code to check the return value of window.open, and if it is
null or undefined, handle the error gracefully by logging a warning or notifying
the user that the popup could not be opened.

}

async logInWithEdlink () {
const state = Math.random().toString(36).substring(2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use cryptographically secure random generation for OAuth state.

The current implementation uses Math.random() which is not cryptographically secure and could lead to state prediction attacks in OAuth flows.

-    const state = Math.random().toString(36).substring(2)
+    const state = crypto.getRandomValues(new Uint32Array(1))[0].toString(36)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const state = Math.random().toString(36).substring(2)
- const state = Math.random().toString(36).substring(2)
+ const state = crypto.getRandomValues(new Uint32Array(1))[0].toString(36)
🤖 Prompt for AI Agents
In app/core/social-handlers/EdlinkBaseHandler.js at line 10, replace the use of
Math.random() for generating the OAuth state with a cryptographically secure
method. Use Node.js's crypto module, such as crypto.randomBytes, to generate a
secure random string for the state parameter to prevent prediction attacks.

Comment on lines +20 to +21
const result = localStorage.getItem(connectionTrackingKey)
const parsedResult = JSON.parse(result)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for JSON parsing.

The JSON parsing could fail if the localStorage data is corrupted or malformed.

           const result = localStorage.getItem(connectionTrackingKey)
-          const parsedResult = JSON.parse(result)
+          let parsedResult
+          try {
+            parsedResult = JSON.parse(result)
+          } catch (parseError) {
+            window.removeEventListener('storage', handleStorageEvent)
+            reject(new Error('Invalid OAuth response format'))
+            return
+          }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const result = localStorage.getItem(connectionTrackingKey)
const parsedResult = JSON.parse(result)
const result = localStorage.getItem(connectionTrackingKey)
let parsedResult
try {
parsedResult = JSON.parse(result)
} catch (parseError) {
window.removeEventListener('storage', handleStorageEvent)
reject(new Error('Invalid OAuth response format'))
return
}
🤖 Prompt for AI Agents
In app/core/social-handlers/EdlinkBaseHandler.js around lines 20 to 21, the
JSON.parse call on localStorage data can throw an error if the data is corrupted
or malformed. Wrap the JSON.parse call in a try-catch block to handle any
parsing errors gracefully. In the catch block, handle the error appropriately,
such as logging the error and setting parsedResult to a safe default value.

Comment on lines +17 to +38
return new Promise((resolve, reject) => {
const handleStorageEvent = (event) => {
if (event.key === connectionTrackingKey) {
const result = localStorage.getItem(connectionTrackingKey)
const parsedResult = JSON.parse(result)
if (parsedResult.state === state) {
localStorage.removeItem(connectionTrackingKey)
window.removeEventListener('storage', handleStorageEvent)
const { state, ...rest } = parsedResult
resolve(rest)
} else {
reject(new Error(`State mismatch: ${parsedResult.state} !== ${state}`))
}
}
}

try {
window.addEventListener('storage', handleStorageEvent)
} catch (error) {
reject(error)
}
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve event listener cleanup and error handling.

The current implementation has potential memory leaks if the promise is rejected before the storage event fires, and the event listener isn't cleaned up in all error scenarios.

     return new Promise((resolve, reject) => {
+      let timeoutId
       const handleStorageEvent = (event) => {
         if (event.key === connectionTrackingKey) {
           const result = localStorage.getItem(connectionTrackingKey)
           const parsedResult = JSON.parse(result)
           if (parsedResult.state === state) {
             localStorage.removeItem(connectionTrackingKey)
             window.removeEventListener('storage', handleStorageEvent)
+            if (timeoutId) clearTimeout(timeoutId)
             const { state, ...rest } = parsedResult
             resolve(rest)
           } else {
+            window.removeEventListener('storage', handleStorageEvent)
+            if (timeoutId) clearTimeout(timeoutId)
             reject(new Error(`State mismatch: ${parsedResult.state} !== ${state}`))
           }
         }
       }
 
       try {
         window.addEventListener('storage', handleStorageEvent)
+        // Add timeout to prevent hanging promises
+        timeoutId = setTimeout(() => {
+          window.removeEventListener('storage', handleStorageEvent)
+          reject(new Error('OAuth login timeout'))
+        }, 300000) // 5 minutes
       } catch (error) {
         reject(error)
       }
     })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return new Promise((resolve, reject) => {
const handleStorageEvent = (event) => {
if (event.key === connectionTrackingKey) {
const result = localStorage.getItem(connectionTrackingKey)
const parsedResult = JSON.parse(result)
if (parsedResult.state === state) {
localStorage.removeItem(connectionTrackingKey)
window.removeEventListener('storage', handleStorageEvent)
const { state, ...rest } = parsedResult
resolve(rest)
} else {
reject(new Error(`State mismatch: ${parsedResult.state} !== ${state}`))
}
}
}
try {
window.addEventListener('storage', handleStorageEvent)
} catch (error) {
reject(error)
}
})
return new Promise((resolve, reject) => {
let timeoutId
const handleStorageEvent = (event) => {
if (event.key === connectionTrackingKey) {
const result = localStorage.getItem(connectionTrackingKey)
const parsedResult = JSON.parse(result)
if (parsedResult.state === state) {
localStorage.removeItem(connectionTrackingKey)
window.removeEventListener('storage', handleStorageEvent)
if (timeoutId) clearTimeout(timeoutId)
const { state, ...rest } = parsedResult
resolve(rest)
} else {
window.removeEventListener('storage', handleStorageEvent)
if (timeoutId) clearTimeout(timeoutId)
reject(new Error(`State mismatch: ${parsedResult.state} !== ${state}`))
}
}
}
try {
window.addEventListener('storage', handleStorageEvent)
// Add timeout to prevent hanging promises
timeoutId = setTimeout(() => {
window.removeEventListener('storage', handleStorageEvent)
reject(new Error('OAuth login timeout'))
}, 300000) // 5 minutes
} catch (error) {
reject(error)
}
})
🤖 Prompt for AI Agents
In app/core/social-handlers/EdlinkBaseHandler.js between lines 17 and 38, the
event listener for the 'storage' event is only removed when the promise resolves
successfully, which can cause memory leaks if the promise is rejected or an
error occurs before the event fires. To fix this, ensure the event listener is
removed in all cases where the promise settles, including rejection scenarios
and errors during setup. Add cleanup logic to remove the event listener both
when rejecting the promise and in the catch block to prevent memory leaks.

Comment on lines 1 to 7
const EdlinkBaseHandler = require('./EdlinkBaseHandler')

const PROVIDER_NAME = 'schoology'

module.exports = class SchoologyHandler extends CocoClass {
static async logInWithSchoology () {
const state = Math.random().toString(36).substring(2)
const url = `/auth/oauth2/${PROVIDER_NAME}?state=${state}`

window.open(url, '_blank', 'width=800,height=600', false)

const connectionTrackingKey = `${PROVIDER_NAME}ConnectionTrackingKey`

return new Promise((resolve, reject) => {
const handleStorageEvent = (event) => {
if (event.key === connectionTrackingKey) {
const result = localStorage.getItem(connectionTrackingKey)
const parsedResult = JSON.parse(result)
if (parsedResult.state === state) {
localStorage.removeItem(connectionTrackingKey)
window.removeEventListener('storage', handleStorageEvent)
const { state, ...rest } = parsedResult
resolve(rest)
} else {
reject(new Error(`State mismatch: ${parsedResult.state} !== ${state}`))
}
}
}

try {
window.addEventListener('storage', handleStorageEvent)
} catch (error) {
reject(error)
}
})
}

async connect (options) {
this.trigger('connect')
const result = await SchoologyHandler.logInWithSchoology()

if (result.loggedIn) {
// login user if already signed up with Schoology
window.location.reload()
return
}

if (!result.email) {
throw new Error('No email found in Schoology response')
}

this.trigger('connect')
this.result = result
return options.success.bind(options.context)(result)
}

loadPerson (options) {
this.trigger('load-person', options)
return options.success.bind(options.context)(this.result)
module.exports = class SchoologyHandler extends EdlinkBaseHandler {
constructor () {
super('schoology')
}
} No newline at end of file
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

LGTM! Clean refactoring to use inheritance pattern.

The refactoring from a standalone OAuth2 implementation to extending EdlinkBaseHandler follows good software engineering practices and reduces code duplication.

Verify that the EdlinkBaseHandler class exists and provides the expected logInWithEdlink method:


🏁 Script executed:

#!/bin/bash
# Description: Verify EdlinkBaseHandler exists and has the expected method
# Expected: Should find the EdlinkBaseHandler class and logInWithEdlink method

# Check if EdlinkBaseHandler file exists
fd -t f "EdlinkBaseHandler.js"

# Look for the logInWithEdlink method in EdlinkBaseHandler
ast-grep --pattern $'class EdlinkBaseHandler {
  $$$
  logInWithEdlink($$$) {
    $$$
  }
  $$$
}'

Length of output: 176


Confirm EdlinkBaseHandler implementation
I located app/core/social-handlers/EdlinkBaseHandler.js, but it doesn’t define a logInWithEdlink method as expected. To complete this refactoring:

  • Implement or rename the OAuth2 login helper in EdlinkBaseHandler (e.g. logInWithEdlink(...)) so that subclasses can invoke it.
  • If the base class already provides equivalent functionality under a different name, update SchoologyHandler to call that method.
  • Add or update tests to cover the shared login flow in EdlinkBaseHandler.

Please address these so that SchoologyHandler can rely on the central OAuth2 logic.

🤖 Prompt for AI Agents
In app/core/social-handlers/SchoologyHandler.js lines 1 to 7, the base class
EdlinkBaseHandler lacks the expected logInWithEdlink method needed for OAuth2
login. Review EdlinkBaseHandler.js to either implement this method or rename an
existing OAuth2 login helper to logInWithEdlink so subclasses like
SchoologyHandler can call it. Then update SchoologyHandler to invoke this
centralized login method. Finally, add or update tests in EdlinkBaseHandler to
cover the shared OAuth2 login flow.

Comment on lines +22 to +23
const SchoologyHandler = require('core/social-handlers/SchoologyHandler')
const ClassLinkHandler = require('core/social-handlers/ClassLinkHandler')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

LGTM! Updated imports to support new handler architecture.

The import changes correctly reflect the refactoring from standalone functions to handler classes.

Verify that the ClassLinkHandler class exists:


🏁 Script executed:

#!/bin/bash
# Description: Verify ClassLinkHandler exists and follows the same pattern
# Expected: Should find the ClassLinkHandler class extending EdlinkBaseHandler

# Check if ClassLinkHandler file exists  
fd -t f "ClassLinkHandler.js"

# Look for ClassLinkHandler class structure
ast-grep --pattern $'class ClassLinkHandler extends EdlinkBaseHandler {
  $$$
}'

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Verify ClassLinkHandler class declaration and its base class
rg "class ClassLinkHandler" -n app/core/social-handlers/ClassLinkHandler.js
rg "extends" -n app/core/social-handlers/ClassLinkHandler.js

Length of output: 278


Validate ClassLinkHandler Import & Refactor Duplication

  • Confirmed ClassLinkHandler exists at app/core/social-handlers/ClassLinkHandler.js and extends EdlinkBaseHandler.
  • Imports in app/views/core/AuthModal.js (lines 22–23) are correct.
  • Both login handlers in AuthModal.js (Schoology: lines 323–330, ClassLink: lines 332–340) share identical success and error handling.

Consider extracting the shared login flow into a common helper to eliminate duplication.

🤖 Prompt for AI Agents
In app/views/core/AuthModal.js around lines 22-23 and 323-340, the login
handlers for Schoology and ClassLink share identical success and error handling
code. To fix this, create a common helper function that encapsulates the shared
login flow logic, then call this helper from both handlers to eliminate
duplication and improve maintainability.

Comment on lines 322 to 340
async onClickSchoologyLoginButton () {
const { loggedIn } = await logInWithSchoology()
const handler = new SchoologyHandler()
const { loggedIn } = await handler.logInWithEdlink()
if (loggedIn) {
window.location.reload()
} else {
noty({ text: $.i18n.t('login.schoology_login_failed'), layout: 'topCenter', type: 'error', timeout: 5000, killer: false, dismissQueue: true })
noty({ text: $.i18n.t('login.login_failed'), layout: 'topCenter', type: 'error', timeout: 5000, killer: false, dismissQueue: true })
}
}

async onClickClasslinkLoginButton () {
const handler = new ClassLinkHandler()
const { loggedIn } = await handler.logInWithEdlink()
if (loggedIn) {
window.location.reload()
} else {
noty({ text: $.i18n.t('login.login_failed'), layout: 'topCenter', type: 'error', timeout: 5000, killer: false, dismissQueue: true })
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor duplicate code in login methods.

Both onClickSchoologyLoginButton and onClickClasslinkLoginButton methods have identical logic except for the handler class instantiation. This creates unnecessary code duplication.

Consider refactoring to a common method:

+  async logInWithEdlinkProvider(HandlerClass) {
+    const handler = new HandlerClass()
+    const { loggedIn } = await handler.logInWithEdlink()
+    if (loggedIn) {
+      window.location.reload()
+    } else {
+      noty({ text: $.i18n.t('login.login_failed'), layout: 'topCenter', type: 'error', timeout: 5000, killer: false, dismissQueue: true })
+    }
+  }

   async onClickSchoologyLoginButton () {
-    const handler = new SchoologyHandler()
-    const { loggedIn } = await handler.logInWithEdlink()
-    if (loggedIn) {
-      window.location.reload()
-    } else {
-      noty({ text: $.i18n.t('login.login_failed'), layout: 'topCenter', type: 'error', timeout: 5000, killer: false, dismissQueue: true })
-    }
+    return this.logInWithEdlinkProvider(SchoologyHandler)
   }

   async onClickClasslinkLoginButton () {
-    const handler = new ClassLinkHandler()
-    const { loggedIn } = await handler.logInWithEdlink()
-    if (loggedIn) {
-      window.location.reload()
-    } else {
-      noty({ text: $.i18n.t('login.login_failed'), layout: 'topCenter', type: 'error', timeout: 5000, killer: false, dismissQueue: true })
-    }
+    return this.logInWithEdlinkProvider(ClassLinkHandler)
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async onClickSchoologyLoginButton () {
const { loggedIn } = await logInWithSchoology()
const handler = new SchoologyHandler()
const { loggedIn } = await handler.logInWithEdlink()
if (loggedIn) {
window.location.reload()
} else {
noty({ text: $.i18n.t('login.schoology_login_failed'), layout: 'topCenter', type: 'error', timeout: 5000, killer: false, dismissQueue: true })
noty({ text: $.i18n.t('login.login_failed'), layout: 'topCenter', type: 'error', timeout: 5000, killer: false, dismissQueue: true })
}
}
async onClickClasslinkLoginButton () {
const handler = new ClassLinkHandler()
const { loggedIn } = await handler.logInWithEdlink()
if (loggedIn) {
window.location.reload()
} else {
noty({ text: $.i18n.t('login.login_failed'), layout: 'topCenter', type: 'error', timeout: 5000, killer: false, dismissQueue: true })
}
}
// Extracted common login logic
async logInWithEdlinkProvider(HandlerClass) {
const handler = new HandlerClass()
const { loggedIn } = await handler.logInWithEdlink()
if (loggedIn) {
window.location.reload()
} else {
noty({
text: $.i18n.t('login.login_failed'),
layout: 'topCenter',
type: 'error',
timeout: 5000,
killer: false,
dismissQueue: true
})
}
}
async onClickSchoologyLoginButton () {
return this.logInWithEdlinkProvider(SchoologyHandler)
}
async onClickClasslinkLoginButton () {
return this.logInWithEdlinkProvider(ClassLinkHandler)
}
🤖 Prompt for AI Agents
In app/views/core/AuthModal.js around lines 322 to 340, the
onClickSchoologyLoginButton and onClickClasslinkLoginButton methods contain
duplicated logic differing only by the handler class used. Refactor by creating
a single reusable async method that accepts the handler class as a parameter,
instantiates it, calls logInWithEdlink, and handles the login result. Then
update both button click methods to call this common method with the appropriate
handler class to eliminate duplication.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant
0