Overview
The rss-handler.ts module contains the core business logic for fetching, filtering, and sending Shopify changelog updates to Google Chat.
handleRSSFeed()
Main orchestrator function that coordinates the entire RSS feed processing workflow.
Signature
export async function handleRSSFeed () : Promise < void >
Description
Orchestrates the complete workflow:
Fetches the RSS feed
Filters for new items
Sends new items to Google Chat (if any)
Updates the processed state
Parameters
None
Return Value
Returns a promise that resolves when processing completes
Behavior
If new items exist: sends to Google Chat and updates state
If no new items: logs “No new items to process” and returns
Source Code
rss-handler.ts (lines 24-34)
export async function handleRSSFeed () {
const feed = await getRSSFeed ();
const newItems = await filterNewItems ( feed );
if ( newItems . length > 0 ) {
await sendToGchat ( newItems );
await updateProcessedState ( newItems );
} else {
console . log ( "No new items to process" );
}
}
Example Usage
import { handleRSSFeed } from './rss-handler.js' ;
// Process RSS feed
await handleRSSFeed ();
// Console: "Successfully sent 3 update(s) to Google Chat"
Fetches and parses the Shopify changelog RSS feed.
Signature
async function getRSSFeed () : Promise < Parser . Output < any >>
Description
Uses the rss-parser library to fetch and parse the RSS feed from https://shopify.dev/changelog/feed.xml.
Parameters
None
Return Value
Parsed RSS feed object containing items array and metadata
interface FeedOutput {
items : Array <{
title ?: string ;
link ?: string ;
pubDate ?: string ;
isoDate ?: string ;
content ?: string ;
// ... other RSS fields
}>;
// ... feed metadata
}
Source Code
rss-handler.ts (lines 1-4, 36-38)
import Parser from "rss-parser" ;
const parser = new Parser ();
const RSS_FEED_URL = "https://shopify.dev/changelog/feed.xml" ;
// ...
async function getRSSFeed () {
return await parser . parseURL ( RSS_FEED_URL );
}
Example Usage
const feed = await getRSSFeed ();
console . log ( `Found ${ feed . items . length } total items in feed` );
filterNewItems()
Filters RSS feed items to return only items newer than the last processed date.
Signature
async function filterNewItems (
feed : Awaited < ReturnType < typeof getRSSFeed >>
) : Promise < typeof feed . items >
Description
Compares each RSS item’s publication date against the last processed date and returns items that are newer. Items are sorted by date (newest first).
Parameters
Return Value
Array of new feed items, sorted by date (newest first)
Filtering Logic
Retrieves last processed date (defaults to yesterday at 00:00:00)
Filters items where isoDate or pubDate is after the last processed date
Sorts results by date (newest first)
Returns filtered and sorted array
Source Code
rss-handler.ts (lines 40-64)
async function filterNewItems (
feed : Awaited < ReturnType < typeof getRSSFeed >>
) : Promise < typeof feed . items > {
const state = await getProcessedState ();
const lastProcessedDate = new Date ( state . lastProcessedDate );
// Filter items that are newer than the last processed date
const newItems = feed . items . filter (( item ) => {
const itemDate = item . isoDate || item . pubDate ;
if ( ! itemDate ) return false ;
const itemDateObj = new Date ( itemDate );
// Include items that are after the last processed date
return itemDateObj > lastProcessedDate ;
});
// Sort by date (newest first)
newItems . sort (( a , b ) => {
const dateA = new Date ( a . isoDate || a . pubDate || 0 ). getTime ();
const dateB = new Date ( b . isoDate || b . pubDate || 0 ). getTime ();
return dateB - dateA ;
});
return newItems ;
}
Example Usage
const feed = await getRSSFeed ();
const newItems = await filterNewItems ( feed );
if ( newItems . length > 0 ) {
console . log ( `Found ${ newItems . length } new items` );
console . log ( `Latest: ${ newItems [ 0 ]. title } ` );
}
sendToGchat()
Sends RSS feed items to Google Chat using a webhook with formatted card messages.
Signature
async function sendToGchat (
items : Awaited < ReturnType < typeof getRSSFeed >>[ "items" ]
) : Promise < void >
Description
Formats RSS items into a Google Chat card message and sends via webhook. Creates a rich card with header, item list, and action buttons.
Parameters
Return Value
Returns a promise that resolves when the message is successfully sent
Environment Variables
Error Handling
Missing Webhook URL Error : WEBHOOK_URL environment variable is not set
Failed Webhook Request Error : Failed to send to Gchat : { statusText } - { errorText }
Validation
Items without title and link are filtered out
If no valid items remain after filtering, logs “No valid items to send” and returns
If items array is empty, logs “No items to send” and returns
Creates a Google Chat card with:
Header : “Shopify Changelog Updates” with Shopify logo
Subtitle : Count of updates and latest date
Item List : Each item as a decorated text widget with:
Bookmark icon
Clickable title (bold)
Formatted publication date (gray text)
Action Button : “View All Changelogs” linking to Shopify changelog
Source Code
rss-handler.ts (lines 115-227)
async function sendToGchat (
items : Awaited < ReturnType < typeof getRSSFeed >>[ "items" ]
) {
const webhookUrl = process . env . WEBHOOK_URL ;
if ( ! webhookUrl ) {
throw new Error ( "WEBHOOK_URL environment variable is not set" );
}
if ( items . length === 0 ) {
console . log ( "No items to send" );
return ;
}
try {
// Filter out items without title or link
const validItems = items . filter (( item ) => item . title && item . link );
if ( validItems . length === 0 ) {
console . log ( "No valid items to send" );
return ;
}
// Get the most recent date for the subtitle
const mostRecentDate = validItems [ 0 ]?. isoDate || validItems [ 0 ]?. pubDate ;
const formattedDate = formatDate ( mostRecentDate );
const dateSubtitle = ` ${ validItems . length } new update ${
validItems . length > 1 ? "s" : ""
} • Latest: ${ formattedDate } ` ;
// Build list widgets using DecoratedText for better formatting
const listWidgets = validItems . map (( item ) => {
const title = item . title || "Untitled" ;
const link = item . link || "#" ;
const itemDate = item . isoDate || item . pubDate ;
const formattedItemDate = formatDate ( itemDate );
return {
decoratedText: {
startIcon: {
knownIcon: "BOOKMARK" ,
},
text: `<a href=" ${ link } "><b> ${ title } </b></a><br><font color="#5f6368"> ${ formattedItemDate } </font>` ,
},
};
});
// Build a single card with all updates
const cardMessage = {
cardsV2: [
{
card: {
header: {
title: "Shopify Changelog Updates" ,
subtitle: dateSubtitle ,
imageUrl: getImageUrl (),
},
sections: [
{
widgets: listWidgets ,
},
],
},
},
],
accessoryWidgets: [
{
buttonList: {
buttons: [
{
text: "View All Changelogs" ,
icon: { materialIcon: { name: "open_in_new" } },
onClick: {
openLink: {
url: "https://shopify.dev/changelog" ,
},
},
},
],
},
},
],
};
// Send to Google Chat
const response = await fetch ( webhookUrl , {
method: "POST" ,
headers: {
"Content-Type" : "application/json" ,
},
body: JSON . stringify ( cardMessage ),
});
if ( ! response . ok ) {
const errorText = await response . text ();
console . error (
`Failed to send updates to Gchat:` ,
response . statusText ,
errorText
);
throw new Error (
`Failed to send to Gchat: ${ response . statusText } - ${ errorText } `
);
}
const result = await response . json ();
console . log (
`Successfully sent ${ validItems . length } update(s) to Google Chat`
);
} catch ( error ) {
console . error ( `Error sending updates to Gchat:` , error );
throw error ;
}
}
Example Usage
const newItems = [
{
title: "New API endpoint for products" ,
link: "https://shopify.dev/changelog/new-api" ,
isoDate: "2026-02-28T10:00:00.000Z"
},
{
title: "GraphQL schema update" ,
link: "https://shopify.dev/changelog/graphql-update" ,
isoDate: "2026-02-27T15:30:00.000Z"
}
];
await sendToGchat ( newItems );
// Console: "Successfully sent 2 update(s) to Google Chat"
Example Google Chat Card Output
{
"cardsV2" : [{
"card" : {
"header" : {
"title" : "Shopify Changelog Updates" ,
"subtitle" : "2 new updates • Latest: February 28, 2026" ,
"imageUrl" : "https://lh6.googleusercontent.com/..."
},
"sections" : [{
"widgets" : [
{
"decoratedText" : {
"startIcon" : { "knownIcon" : "BOOKMARK" },
"text" : "<a href='...'><b>New API endpoint for products</b></a><br><font color='#5f6368'>February 28, 2026</font>"
}
},
{
"decoratedText" : {
"startIcon" : { "knownIcon" : "BOOKMARK" },
"text" : "<a href='...'><b>GraphQL schema update</b></a><br><font color='#5f6368'>February 27, 2026</font>"
}
}
]
}]
}
}],
"accessoryWidgets" : [{
"buttonList" : {
"buttons" : [{
"text" : "View All Changelogs" ,
"icon" : { "materialIcon" : { "name" : "open_in_new" } },
"onClick" : {
"openLink" : { "url" : "https://shopify.dev/changelog" }
}
}]
}
}]
}
Helper Functions
The module includes several helper functions used by the main API.
Formats ISO date strings into readable format.
rss-handler.ts (lines 88-105)
function formatDate ( dateString ?: string ) : string {
if ( ! dateString ) return "Unknown date" ;
try {
const date = new Date ( dateString );
if ( isNaN ( date . getTime ())) {
return dateString ;
}
return date . toLocaleDateString ( "en-US" , {
year: "numeric" ,
month: "long" ,
day: "numeric" ,
});
} catch ( error ) {
return dateString ;
}
}
Example:
formatDate ( "2026-02-28T10:00:00.000Z" ) // "February 28, 2026"
formatDate ( undefined ) // "Unknown date"
getImageUrl()
Returns the Shopify logo URL for Google Chat cards.
rss-handler.ts (lines 110-113)
function getImageUrl () : string {
// Try Shopify logo first, fallback to placeholder
return PLACEHOLDER_IMAGE ;
}
getProcessedState()
Retrieves the last processed date state (defaults to yesterday).
rss-handler.ts (lines 19-22)
async function getProcessedState () : Promise < ProcessedState > {
// Initialize with yesterday's date
return { lastProcessedDate: getYesterdayDate () };
}
getYesterdayDate()
Calculates yesterday’s date at 00:00:00 in ISO format.
rss-handler.ts (lines 12-17)
function getYesterdayDate () : string {
const yesterday = new Date ();
yesterday . setDate ( yesterday . getDate () - 1 );
yesterday . setHours ( 0 , 0 , 0 , 0 );
return yesterday . toISOString ();
}
Type Definitions
ProcessedState
rss-handler.ts (lines 8-10)
interface ProcessedState {
lastProcessedDate : string ;
}
Stores the timestamp of the last processed RSS item.