
Research
Malicious npm Packages Impersonate Flashbots SDKs, Targeting Ethereum Wallet Credentials
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
Real-time broadcasting generator for Rails + Inertia.js applications. Generate a complete WebSocket-based real-time system with zero runtime dependencies.
Pulse Zero generates a complete real-time broadcasting system directly into your Rails application. Unlike traditional gems, all code is copied into your project, giving you full ownership and the ability to customize everything.
Inspired by Turbo Rails, Pulse Zero brings familiar broadcasting patterns to Inertia.js applications. If you've used broadcasts_to
in Turbo Rails, you'll feel right at home with Pulse Zero's API.
Features:
Add this gem to your application's Gemfile:
group :development do
gem 'pulse_zero'
end
Then run:
bundle install
rails generate pulse_zero:install
lib/pulse/
- Core broadcasting systemapp/models/concerns/pulse/broadcastable.rb
- Model broadcasting DSLapp/controllers/concerns/pulse/request_id_tracking.rb
- Request trackingapp/channels/pulse/channel.rb
- WebSocket channelapp/jobs/pulse/broadcast_job.rb
- Async broadcastingconfig/initializers/pulse.rb
- Configurationapp/frontend/lib/pulse.ts
- Subscription managerapp/frontend/lib/pulse-connection.ts
- Connection monitoringapp/frontend/lib/pulse-recovery-strategy.ts
- Recovery logicapp/frontend/lib/pulse-visibility-manager.ts
- Tab visibility handlingapp/frontend/hooks/use-pulse.ts
- React subscription hookapp/frontend/hooks/use-visibility-refresh.ts
- Tab refresh hookclass Post < ApplicationRecord
include Pulse::Broadcastable
# Broadcast to a simple channel
broadcasts_to ->(post) { "posts" }
# Or broadcast to account-scoped channel
# broadcasts_to ->(post) { [post.account, "posts"] }
end
class PostsController < ApplicationController
def index
@posts = Post.all
render inertia: "Post/Index", props: {
posts: @posts.map do |post|
serialize_post(post)
end,
pulseStream: Pulse::Streams::StreamName.signed_stream_name("posts")
}
end
private
def serialize_post(post)
{
id: post.id,
title: post.title,
content: post.content,
created_at: post.created_at
}
end
end
Why pulseStream? Following Turbo Rails' security model, Pulse uses signed stream names to prevent unauthorized access to WebSocket channels. Since Inertia.js doesn't have a built-in way to access streams like Turbo does, we pass the signed stream name as a prop. This approach:
import { useState } from 'react'
import { usePulse } from '@/hooks/use-pulse'
import { useVisibilityRefresh } from '@/hooks/use-visibility-refresh'
import { router } from '@inertiajs/react'
interface IndexProps {
posts: Array<{
id: number
title: string
content: string
created_at: string
}>
pulseStream: string
flash: {
success?: string
error?: string
}
}
export default function Index({ posts: initialPosts, flash, pulseStream }: IndexProps) {
// Use local state for posts to enable real-time updates
const [posts, setPosts] = useState(initialPosts)
// Automatically refresh data when returning to the tab after 30+ seconds
// This ensures users see fresh data after being away, handling cases where
// WebSocket messages might have been missed during browser suspension
useVisibilityRefresh(30, () => {
router.reload({ only: ['posts'] })
})
// Subscribe to Pulse updates for real-time changes
usePulse(pulseStream, (message) => {
switch (message.event) {
case 'created':
// Add the new post to the beginning of the list
setPosts(prev => [message.payload, ...prev])
break
case 'updated':
// Replace the updated post in the list
setPosts(prev =>
prev.map(post => post.id === message.payload.id ? message.payload : post)
)
break
case 'deleted':
// Remove the deleted post from the list
setPosts(prev =>
prev.filter(post => post.id !== message.payload.id)
)
break
case 'refresh':
// Full reload for refresh events
router.reload()
break
}
})
return (
<>
{flash.success && <div className="alert-success">{flash.success}</div>}
<PostsList posts={posts} />
</>
)
}
Note: This example shows optimistic UI updates using local state. Alternatively, you can use router.reload({ only: ['posts'] })
for all events to fetch fresh data from the server, which ensures consistency but may feel less responsive.
Why useVisibilityRefresh? When users switch tabs or minimize their browser, WebSocket connections can be suspended and messages may be lost. The useVisibilityRefresh
hook detects when users return to your app and automatically refreshes the data if they've been away for more than the specified threshold (30 seconds in this example). This ensures users always see up-to-date information without manual refreshing.
Pulse broadcasts four types of events:
created
- When a record is created{
"event": "created",
"payload": { "id": 123, "content": "New post" },
"requestId": "uuid-123",
"at": 1234567890.123
}
updated
- When a record is updated{
"event": "updated",
"payload": { "id": 123, "content": "Updated post" },
"requestId": "uuid-456",
"at": 1234567891.456
}
deleted
- When a record is destroyed{
"event": "deleted",
"payload": { "id": 123 },
"requestId": "uuid-789",
"at": 1234567892.789
}
refresh
- Force a full refresh{
"event": "refresh",
"payload": {},
"requestId": "uuid-012",
"at": 1234567893.012
}
# Broadcast with custom payload
post.broadcast_updated_to(
[Current.account, "posts"],
payload: { id: post.id, featured: true }
)
# Async broadcasting
post.broadcast_updated_later_to([Current.account, "posts"])
Post.suppressing_pulse_broadcasts do
Post.where(account: account).update_all(featured: true)
end
# Then send one refresh broadcast
Post.new.broadcast_refresh_to([account, "posts"])
# config/initializers/pulse.rb
Rails.application.configure do
config.pulse.serializer = ->(record) {
case record
when Post
record.as_json(only: [:id, :title, :state])
else
record.as_json
end
}
end
# config/initializers/pulse.rb
Rails.application.configure do
# Debounce window in milliseconds (default: 300)
config.pulse.debounce_ms = 300
# Background job queue (default: :default)
config.pulse.queue_name = :low
# Custom serializer
config.pulse.serializer = ->(record) { record.as_json }
end
Pulse includes sophisticated handling for browser tab suspension:
Platform-aware thresholds:
# In your test files
test "broadcasts on update" do
post = posts(:one)
assert_broadcast_on([post.account, "posts"]) do
post.update!(title: "New Title")
end
end
# Suppress broadcasts in tests
Post.suppressing_pulse_broadcasts do
# Your test code
end
Enable debug logging:
// In browser console
localStorage.setItem('PULSE_DEBUG', 'true')
Check connection health:
import { getPulseMonitorStats } from '@/lib/pulse-connection'
const stats = getPulseMonitorStats()
console.log(stats)
Pulse Zero follows the same philosophy as authentication-zero, and is heavily inspired by Turbo Rails. The API design closely mirrors Turbo Rails patterns, making it intuitive for developers already familiar with the Hotwire ecosystem.
Bug reports and pull requests are welcome on GitHub.
The gem is available as open source under the terms of the MIT License.
FAQs
Unknown package
We found that pulse_zero demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
Security News
Ruby maintainers from Bundler and rbenv teams are building rv to bring Python uv's speed and unified tooling approach to Ruby development.
Security News
Following last week’s supply chain attack, Nx published findings on the GitHub Actions exploit and moved npm publishing to Trusted Publishers.