mirror of
https://github.com/xtr-dev/rondevu-client.git
synced 2025-12-15 05:13:23 +00:00
Compare commits
2 Commits
claude/fix
...
add-claude
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a21fb04a6f | ||
|
|
6ee1d7b5a9 |
57
.github/workflows/claude-code-review.yml
vendored
Normal file
57
.github/workflows/claude-code-review.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
name: Claude Code Review
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize]
|
||||||
|
# Optional: Only run on specific file changes
|
||||||
|
# paths:
|
||||||
|
# - "src/**/*.ts"
|
||||||
|
# - "src/**/*.tsx"
|
||||||
|
# - "src/**/*.js"
|
||||||
|
# - "src/**/*.jsx"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
claude-review:
|
||||||
|
# Optional: Filter by PR author
|
||||||
|
# if: |
|
||||||
|
# github.event.pull_request.user.login == 'external-contributor' ||
|
||||||
|
# github.event.pull_request.user.login == 'new-developer' ||
|
||||||
|
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
issues: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Run Claude Code Review
|
||||||
|
id: claude-review
|
||||||
|
uses: anthropics/claude-code-action@v1
|
||||||
|
with:
|
||||||
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
|
prompt: |
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
PR NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
|
||||||
|
Please review this pull request and provide feedback on:
|
||||||
|
- Code quality and best practices
|
||||||
|
- Potential bugs or issues
|
||||||
|
- Performance considerations
|
||||||
|
- Security concerns
|
||||||
|
- Test coverage
|
||||||
|
|
||||||
|
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
|
||||||
|
|
||||||
|
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
|
||||||
|
|
||||||
|
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||||
|
# or https://code.claude.com/docs/en/cli-reference for available options
|
||||||
|
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
|
||||||
|
|
||||||
50
.github/workflows/claude.yml
vendored
Normal file
50
.github/workflows/claude.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: Claude Code
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
pull_request_review_comment:
|
||||||
|
types: [created]
|
||||||
|
issues:
|
||||||
|
types: [opened, assigned]
|
||||||
|
pull_request_review:
|
||||||
|
types: [submitted]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
claude:
|
||||||
|
if: |
|
||||||
|
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||||
|
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||||
|
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
||||||
|
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
issues: read
|
||||||
|
id-token: write
|
||||||
|
actions: read # Required for Claude to read CI results on PRs
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Run Claude Code
|
||||||
|
id: claude
|
||||||
|
uses: anthropics/claude-code-action@v1
|
||||||
|
with:
|
||||||
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
|
|
||||||
|
# This is an optional setting that allows Claude to read CI results on PRs
|
||||||
|
additional_permissions: |
|
||||||
|
actions: read
|
||||||
|
|
||||||
|
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
||||||
|
# prompt: 'Update the pull request description to include a summary of changes.'
|
||||||
|
|
||||||
|
# Optional: Add claude_args to customize behavior and configuration
|
||||||
|
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||||
|
# or https://code.claude.com/docs/en/cli-reference for available options
|
||||||
|
# claude_args: '--allowed-tools Bash(gh pr:*)'
|
||||||
|
|
||||||
@@ -49,8 +49,8 @@ const rondevu = await Rondevu.connect({
|
|||||||
await rondevu.publishService({
|
await rondevu.publishService({
|
||||||
service: 'chat:1.0.0',
|
service: 'chat:1.0.0',
|
||||||
maxOffers: 5, // Maintain up to 5 concurrent offers
|
maxOffers: 5, // Maintain up to 5 concurrent offers
|
||||||
offerFactory: async (pc) => {
|
offerFactory: async (rtcConfig) => {
|
||||||
// pc is created by Rondevu with ICE handlers already attached
|
const pc = new RTCPeerConnection(rtcConfig)
|
||||||
const dc = pc.createDataChannel('chat')
|
const dc = pc.createDataChannel('chat')
|
||||||
|
|
||||||
dc.addEventListener('open', () => {
|
dc.addEventListener('open', () => {
|
||||||
@@ -64,7 +64,7 @@ await rondevu.publishService({
|
|||||||
|
|
||||||
const offer = await pc.createOffer()
|
const offer = await pc.createOffer()
|
||||||
await pc.setLocalDescription(offer)
|
await pc.setLocalDescription(offer)
|
||||||
return { dc, offer }
|
return { pc, dc, offer }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -9,7 +9,8 @@
|
|||||||
"version": "0.18.0",
|
"version": "0.18.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/ed25519": "^3.0.0"
|
"@noble/ed25519": "^3.0.0",
|
||||||
|
"@xtr-dev/rondevu-client": "^0.9.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
@@ -1309,6 +1310,15 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@xtr-dev/rondevu-client": {
|
||||||
|
"version": "0.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@xtr-dev/rondevu-client/-/rondevu-client-0.9.2.tgz",
|
||||||
|
"integrity": "sha512-DVow5AOPU40dqQtlfQK7J2GNX8dz2/4UzltMqublaPZubbkRYgocvp0b76NQu5F6v150IstMV2N49uxAYqogVw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/ed25519": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.15.0",
|
"version": "8.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
|
|||||||
101
src/rondevu.ts
101
src/rondevu.ts
@@ -63,19 +63,12 @@ export interface RondevuOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface OfferContext {
|
export interface OfferContext {
|
||||||
|
pc: RTCPeerConnection
|
||||||
dc?: RTCDataChannel
|
dc?: RTCDataChannel
|
||||||
offer: RTCSessionDescriptionInit
|
offer: RTCSessionDescriptionInit
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export type OfferFactory = (rtcConfig: RTCConfiguration) => Promise<OfferContext>
|
||||||
* Factory function for creating WebRTC offers.
|
|
||||||
* Rondevu creates the RTCPeerConnection and passes it to the factory,
|
|
||||||
* allowing ICE candidate handlers to be set up before setLocalDescription() is called.
|
|
||||||
*
|
|
||||||
* @param pc - The RTCPeerConnection created by Rondevu (already configured with ICE servers)
|
|
||||||
* @returns Promise containing the data channel (optional) and offer SDP
|
|
||||||
*/
|
|
||||||
export type OfferFactory = (pc: RTCPeerConnection) => Promise<OfferContext>
|
|
||||||
|
|
||||||
export interface PublishServiceOptions {
|
export interface PublishServiceOptions {
|
||||||
service: string // Service name and version (e.g., "chat:2.0.0") - username will be auto-appended
|
service: string // Service name and version (e.g., "chat:2.0.0") - username will be auto-appended
|
||||||
@@ -142,12 +135,12 @@ interface ActiveOffer {
|
|||||||
* await rondevu.publishService({
|
* await rondevu.publishService({
|
||||||
* service: 'chat:2.0.0',
|
* service: 'chat:2.0.0',
|
||||||
* maxOffers: 5, // Maintain up to 5 concurrent offers
|
* maxOffers: 5, // Maintain up to 5 concurrent offers
|
||||||
* offerFactory: async (pc) => {
|
* offerFactory: async (rtcConfig) => {
|
||||||
* // pc is created by Rondevu with ICE handlers already attached
|
* const pc = new RTCPeerConnection(rtcConfig)
|
||||||
* const dc = pc.createDataChannel('chat')
|
* const dc = pc.createDataChannel('chat')
|
||||||
* const offer = await pc.createOffer()
|
* const offer = await pc.createOffer()
|
||||||
* await pc.setLocalDescription(offer)
|
* await pc.setLocalDescription(offer)
|
||||||
* return { dc, offer }
|
* return { pc, dc, offer }
|
||||||
* }
|
* }
|
||||||
* })
|
* })
|
||||||
*
|
*
|
||||||
@@ -344,15 +337,15 @@ export class Rondevu {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Default offer factory - creates a simple data channel connection
|
* Default offer factory - creates a simple data channel connection
|
||||||
* The RTCPeerConnection is created by Rondevu and passed in
|
|
||||||
*/
|
*/
|
||||||
private async defaultOfferFactory(pc: RTCPeerConnection): Promise<OfferContext> {
|
private async defaultOfferFactory(rtcConfig: RTCConfiguration): Promise<OfferContext> {
|
||||||
|
const pc = new RTCPeerConnection(rtcConfig)
|
||||||
const dc = pc.createDataChannel('default')
|
const dc = pc.createDataChannel('default')
|
||||||
|
|
||||||
const offer = await pc.createOffer()
|
const offer = await pc.createOffer()
|
||||||
await pc.setLocalDescription(offer)
|
await pc.setLocalDescription(offer)
|
||||||
|
|
||||||
return { dc, offer }
|
return { pc, dc, offer }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -382,10 +375,6 @@ export class Rondevu {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up ICE candidate handler to send candidates to the server
|
* Set up ICE candidate handler to send candidates to the server
|
||||||
*
|
|
||||||
* Note: This is used by connectToService() where the offerId is already known.
|
|
||||||
* For createOffer(), we use inline ICE handling with early candidate queuing
|
|
||||||
* since the offerId isn't available until after the factory completes.
|
|
||||||
*/
|
*/
|
||||||
private setupIceCandidateHandler(
|
private setupIceCandidateHandler(
|
||||||
pc: RTCPeerConnection,
|
pc: RTCPeerConnection,
|
||||||
@@ -426,58 +415,15 @@ export class Rondevu {
|
|||||||
iceServers: this.iceServers
|
iceServers: this.iceServers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.debug('Creating new offer...')
|
||||||
|
|
||||||
|
// Create the offer using the factory
|
||||||
|
const { pc, dc, offer } = await this.offerFactory(rtcConfig)
|
||||||
|
|
||||||
// Auto-append username to service
|
// Auto-append username to service
|
||||||
const serviceFqn = `${this.currentService}@${this.username}`
|
const serviceFqn = `${this.currentService}@${this.username}`
|
||||||
|
|
||||||
this.debug('Creating new offer...')
|
// Publish to server
|
||||||
|
|
||||||
// 1. Create the RTCPeerConnection - Rondevu controls this to set up handlers early
|
|
||||||
const pc = new RTCPeerConnection(rtcConfig)
|
|
||||||
|
|
||||||
// 2. Set up ICE candidate handler with queuing BEFORE the factory runs
|
|
||||||
// This ensures we capture all candidates, even those generated immediately
|
|
||||||
// when setLocalDescription() is called in the factory
|
|
||||||
const earlyIceCandidates: RTCIceCandidateInit[] = []
|
|
||||||
let offerId: string | undefined
|
|
||||||
|
|
||||||
pc.onicecandidate = async (event) => {
|
|
||||||
if (event.candidate) {
|
|
||||||
// Handle both browser and Node.js (wrtc) environments
|
|
||||||
const candidateData = typeof event.candidate.toJSON === 'function'
|
|
||||||
? event.candidate.toJSON()
|
|
||||||
: event.candidate
|
|
||||||
|
|
||||||
if (offerId) {
|
|
||||||
// We have the offerId, send directly
|
|
||||||
try {
|
|
||||||
await this.api.addOfferIceCandidates(serviceFqn, offerId, [candidateData])
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[Rondevu] Failed to send ICE candidate:', err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Queue for later - we don't have the offerId yet
|
|
||||||
this.debug('Queuing early ICE candidate')
|
|
||||||
earlyIceCandidates.push(candidateData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Call the factory with the pc - factory creates data channel and offer
|
|
||||||
// When factory calls setLocalDescription(), ICE gathering starts and
|
|
||||||
// candidates are captured by the handler we set up above
|
|
||||||
let dc: RTCDataChannel | undefined
|
|
||||||
let offer: RTCSessionDescriptionInit
|
|
||||||
try {
|
|
||||||
const factoryResult = await this.offerFactory(pc)
|
|
||||||
dc = factoryResult.dc
|
|
||||||
offer = factoryResult.offer
|
|
||||||
} catch (err) {
|
|
||||||
// Clean up the connection if factory fails
|
|
||||||
pc.close()
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Publish to server to get offerId
|
|
||||||
const result = await this.api.publishService({
|
const result = await this.api.publishService({
|
||||||
serviceFqn,
|
serviceFqn,
|
||||||
offers: [{ sdp: offer.sdp! }],
|
offers: [{ sdp: offer.sdp! }],
|
||||||
@@ -486,9 +432,9 @@ export class Rondevu {
|
|||||||
message: '',
|
message: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
offerId = result.offers[0].offerId
|
const offerId = result.offers[0].offerId
|
||||||
|
|
||||||
// 5. Store active offer
|
// Store active offer
|
||||||
this.activeOffers.set(offerId, {
|
this.activeOffers.set(offerId, {
|
||||||
offerId,
|
offerId,
|
||||||
serviceFqn,
|
serviceFqn,
|
||||||
@@ -500,22 +446,15 @@ export class Rondevu {
|
|||||||
|
|
||||||
this.debug(`Offer created: ${offerId}`)
|
this.debug(`Offer created: ${offerId}`)
|
||||||
|
|
||||||
// 6. Send any queued early ICE candidates
|
// Set up ICE candidate handler
|
||||||
if (earlyIceCandidates.length > 0) {
|
this.setupIceCandidateHandler(pc, serviceFqn, offerId)
|
||||||
this.debug(`Sending ${earlyIceCandidates.length} early ICE candidates`)
|
|
||||||
try {
|
|
||||||
await this.api.addOfferIceCandidates(serviceFqn, offerId, earlyIceCandidates)
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[Rondevu] Failed to send early ICE candidates:', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. Monitor connection state
|
// Monitor connection state
|
||||||
pc.onconnectionstatechange = () => {
|
pc.onconnectionstatechange = () => {
|
||||||
this.debug(`Offer ${offerId} connection state: ${pc.connectionState}`)
|
this.debug(`Offer ${offerId} connection state: ${pc.connectionState}`)
|
||||||
|
|
||||||
if (pc.connectionState === 'failed' || pc.connectionState === 'closed') {
|
if (pc.connectionState === 'failed' || pc.connectionState === 'closed') {
|
||||||
this.activeOffers.delete(offerId!)
|
this.activeOffers.delete(offerId)
|
||||||
this.fillOffers() // Try to replace failed offer
|
this.fillOffers() // Try to replace failed offer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user