5 Commits

Author SHA1 Message Date
db28a133bf 0.18.1 2025-12-14 11:04:21 +01:00
Bas
4d90cce9d0 Merge pull request #3 from xtr-dev/claude/fix-issue-2-6zYvD
Fix issue #2
2025-12-14 11:03:47 +01:00
Bas
f5e202384a Merge pull request #4 from xtr-dev/add-claude-github-actions-1765706414732
Add Claude Code GitHub Workflow
2025-12-14 11:01:07 +01:00
Bas
a21fb04a6f "Claude Code Review workflow" 2025-12-14 11:00:16 +01:00
Bas
6ee1d7b5a9 "Claude PR Assistant workflow" 2025-12-14 11:00:15 +01:00
6 changed files with 137 additions and 53 deletions

View 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
View 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:*)'

View File

@@ -49,8 +49,8 @@ const rondevu = await Rondevu.connect({
await rondevu.publishService({
service: 'chat:1.0.0',
maxOffers: 5, // Maintain up to 5 concurrent offers
offerFactory: async (pc) => {
// pc is created by Rondevu with ICE handlers already attached
offerFactory: async (rtcConfig) => {
const pc = new RTCPeerConnection(rtcConfig)
const dc = pc.createDataChannel('chat')
dc.addEventListener('open', () => {
@@ -64,7 +64,7 @@ await rondevu.publishService({
const offer = await pc.createOffer()
await pc.setLocalDescription(offer)
return { dc, offer }
return { pc, dc, offer }
}
})

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@xtr-dev/rondevu-client",
"version": "0.18.0",
"version": "0.18.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@xtr-dev/rondevu-client",
"version": "0.18.0",
"version": "0.18.1",
"license": "MIT",
"dependencies": {
"@noble/ed25519": "^3.0.0"

View File

@@ -1,6 +1,6 @@
{
"name": "@xtr-dev/rondevu-client",
"version": "0.18.0",
"version": "0.18.1",
"description": "TypeScript client for Rondevu with durable WebRTC connections, automatic reconnection, and message queuing",
"type": "module",
"main": "dist/index.js",

View File

@@ -63,19 +63,12 @@ export interface RondevuOptions {
}
export interface OfferContext {
pc: RTCPeerConnection
dc?: RTCDataChannel
offer: RTCSessionDescriptionInit
}
/**
* 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 type OfferFactory = (rtcConfig: RTCConfiguration) => Promise<OfferContext>
export interface PublishServiceOptions {
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({
* service: 'chat:2.0.0',
* maxOffers: 5, // Maintain up to 5 concurrent offers
* offerFactory: async (pc) => {
* // pc is created by Rondevu with ICE handlers already attached
* offerFactory: async (rtcConfig) => {
* const pc = new RTCPeerConnection(rtcConfig)
* const dc = pc.createDataChannel('chat')
* const offer = await pc.createOffer()
* 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
* 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 offer = await pc.createOffer()
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
*
* 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(
pc: RTCPeerConnection,
@@ -426,20 +415,23 @@ export class Rondevu {
iceServers: this.iceServers
}
this.debug('Creating new offer...')
// Create the offer using the factory
// Note: The factory may call setLocalDescription() which triggers ICE gathering
const { pc, dc, offer } = await this.offerFactory(rtcConfig)
// Auto-append username to service
const serviceFqn = `${this.currentService}@${this.username}`
this.debug('Creating new offer...')
// 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
// Queue to buffer ICE candidates generated before we have the offerId
// This fixes the race condition where ICE candidates are lost because
// they're generated before we can set up the handler with the offerId
const earlyIceCandidates: RTCIceCandidateInit[] = []
let offerId: string | undefined
let offerId: string | null = null
// Set up a queuing ICE candidate handler immediately after getting the pc
// This captures any candidates that fire before we have the offerId
pc.onicecandidate = async (event) => {
if (event.candidate) {
// Handle both browser and Node.js (wrtc) environments
@@ -462,22 +454,7 @@ export class Rondevu {
}
}
// 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
// Publish to server
const result = await this.api.publishService({
serviceFqn,
offers: [{ sdp: offer.sdp! }],
@@ -488,7 +465,7 @@ export class Rondevu {
offerId = result.offers[0].offerId
// 5. Store active offer
// Store active offer
this.activeOffers.set(offerId, {
offerId,
serviceFqn,
@@ -500,7 +477,7 @@ export class Rondevu {
this.debug(`Offer created: ${offerId}`)
// 6. Send any queued early ICE candidates
// Send any queued early ICE candidates
if (earlyIceCandidates.length > 0) {
this.debug(`Sending ${earlyIceCandidates.length} early ICE candidates`)
try {
@@ -510,7 +487,7 @@ export class Rondevu {
}
}
// 7. Monitor connection state
// Monitor connection state
pc.onconnectionstatechange = () => {
this.debug(`Offer ${offerId} connection state: ${pc.connectionState}`)