npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ghosthunter-server

v0.4.3

Published

Make GhostHunter accessible as an API

Downloads

7

Readme

GhostHunter Server

Don't like the documentation? Help us make it better by emailing us ([email protected]), creating an issue or creating a pull request

A serverside implementation of GhostHunter for larger blogs

GhostHunter is the easiest drop-in for search with Ghost blogs, but as your blog grows, the index can grow large. The OG GhostHunter relies on the browsers localstorage for the index, and localstorage has an effective limit of 5MB.

GhostHunter Server, as the name suggests is GhostHunter which runs on node (serverside javascript), which means there isn't really an effective storage limit. It's a very simplistic implementation of GhostHunter coupled with an API endpoint which executes the search using GhostHunter's backend (lunr) and proxies the results as a JSON response

:warning: Warnings

  • GhostHunter 0.4.0 and beyond don't support the usage of client_id and client_secret for the instance object. Use clientID and clientSecret instead (case sensitive).
  • GhostHonter Frontend 0.4.0 and beyond don't support form.onsubmit (i.e. document.querySelector('#search').onsubmit(new Event('Search!'))). Use instance.submitted instead (i.e. window.search = new ghostHunterFrontend(...);... window.search.submitted(new Event('Search!')))
  • GhostHunter Frontend 0.4.0 implements a breaking change (although it will have minimal effect) where instance.before is called before the Endpoint Request is sent

Differences

There are a couple of notable differences compared to the original GhostHunter:

Frontend

  • result_template uses the pubDate helper instead of prettyPubDate helper
  • info_template supports the search and plural helpers
  • You can trigger a search by calling instance.submitted({{Event}})
  • before is called before an endpoint request is sent

Backend

  • The API can respond with absolute URLs to posts

If you find any other differences, please report it

Installation

1. Add dependency

  npm install --save ghosthunter-server

or if you prefer yarn

  yarn add ghosthunter-server

2. Configure

When you require('ghosthunter-server'), you get an object with 2 properties - an uninitialized GhostHunter class, and an easy-to-use server drop-in.

GhostHunter initialization

There isn't a whole lot to customize the GhostHunter backend, although there is some required data which relates to accessing your Ghost data.

The required options are wrapped in the instance key of the options object. You need to provide:

  • Instance URL (url) - the HTTP url of your blog instance
    • :warning: the url passed is expected to be a valid HTTP url; GhostHunter-Server doesn't do any additional validation to ensure it is - this url will be normalized and directly passed to got (the request library we use)
  • Client ID (clientID) - The Client ID for read requests via the Ghost API
    • This isn't used in the normal GhostHunter since Ghost provides a convenience API for this
    • While you're free to use the default Client ID provided by Ghost (ghost-frontend), we strongly recommend you create a new one for better security and tracking
  • Client Secret (clientSecret) - The Client Secret used to authenticate clientID (this is how oAuth works) via the Ghost API

Optional Paramaters:

  • Include Pages (includePages) - defaulted to false; setting this option to anything truthy will add blog pages to the index
  • Absolute URLs (absolute) - defaulted to true; setting this option to anything falsy will return the relative url. If you're hosting your blog on a subdomain (i.e. https://www.example.com/blog), you should keep this as true, unless you plan on adding the path to your render template

Example

const {GhostHunter} = require('ghosthunter-server');

const myInstance = new GhostHunter({
 instance: {
   // example data, doesn't work!
   url: 'https://demo.ghost.io/',
   clientID: 'ghost-search',
   clientSecret: 'abcd1ef2gh3'
 },
 includePages: true,
 absolute: false
});

Server drop-in initialization

While the GhostHunter class provides the logic for searching, the server function creates a fully functioning API to search using the GhostHunter class. It supports a variety of options:

  • Port (port) - the port for the server to listen on. Defaults to 3000
  • Path (path) - the subpath to respond to search requests. Defaults to /
    • For example, you can use NGINX to proxy /search to GhostHunter-Server and everything else to your Ghost Instance. In that case, you would need to set your path to /search
    • :warning: There is no validation on this. Make sure you include a prefixed / and take any necessary precautions
  • Host (host) - the host to listen on. Does not have a default, which means it will listen on :: or 0.0.0.0
  • GhostHunter Class Options (ghostHunter) - An object containing options to proxy to the GhostHunter Class upon initialization. Defaults to an empty object
  • Refresh Interval (refreshInterval) - How often to refresh the index. Will not refresh if a falsy value is provided. Defaults to 6 hours
  • Allowed Origins (allowedOrigins) - The origins which are allowed to access the endpoint, enforced by hostname. An array must be provided. Note: the check is pretty lenient in terms of origin requirements because enforcing is done by hostname (and not host).

Example


const {Server} = require('ghosthunter-server');

const searchInstance = new Server({
  port: 3000,
  path: '/feature/search/',
  host: '127.0.0.1',
  ghostHunter: {
    instance: {
      // example data, doesn't work!
      url: 'https://demo.ghost.io/',
      clientID: 'ghost-search',
      clientSecret: 'abcd1ef2gh3'
    },
	includePages: true,
	absolute: false
  },
  allowedOrigins: ['blog.demo.com', 'demo.ghost.io']
});

// Close the server after 100 seconds
setTimeout(() => searchInstance.server.close(), 100000);

3. Profit :sunglasses:

Properties

Both the initialized GhostHunter Class and server drop-in have properties bound to their respective instance

GhostHunter

initialized - Whether or not createIndex has been successfully run once

blogData - An object with keys (determined by Post ID) mapping to post data. Used by original GhostHunter

includePages - Whether or not pages should be included in the index

absolute - Whether or not absolute URLs should be used for posts when building blog data

instance - The instance properties you need to provide for GhostHunter to request data

index (not always present) - The lunr index of posts (and possibly pages)

Server

options - The options that were passed upon initialization

index - GhostHunter instance

refreshID (not always present) - Timer ID for refresh interval

server - the node HTTP Server instance

GhostHunter Methods

Here are the methods available to be called on a GhostHunter Instance. Note: initialized Server instances don't have any methods which can be run

createIndex - Parameters: (optional) refresh

  • Creates an index of posts by calling the Ghost API.
  • Only does this once, unless the index is being refreshed (determined by the refresh parameter)

url - (internal method) - Properly encodes the URL to access the Ghost Instance's API

find - Parameters: (required) value

  • Searches the index and returns a list of Posts related to value
  • Always returns a JSON Object
    • If there was an issue, the errors property will be set
    • Otherwise, the meta.count and data properties will be set
      • Data will be an array of Blog Posts containing the post title, description, published date as pubDate, featureImage, and link
      • Meta.count will list the length of the data array

Frontend

GhostHunter isn't just used for indexing, it's also used for rendering! That's why we've implemented a really similar version of their rendering library. We tried our best to make sure there aren't any regressions (if you find one, please file an issue) but there are some additional features (see the differences section)

The GhostHunter frontend is pretty easy to use - just load the frontend/frontend.min.js file that's included (either via a cdn like jsdelivr or packaging it with your theme) and instantiate it like below:

// the frontend library should already be loaded

window.search = new ghostHunterFrontend('.search-input', {
  results: '#results',
  endpoint: 'https://search.example.com/'
  // additional options
});

Loading frontend.js exposes 3 global functions

  • ghRequest (GhostHunter Request) is used to make a request to the endpoint, although you can use it in your app as an easy GET (JSOn) request function (using the node-style callback(error, response) function). ghRequest(url, callback)

  • htmlEscape will escape HTML attributes from the parameter. This is almost the same code used by mustache.js htmlEscape(unEscapedText)

  • ghostHunterFrontend is the thing you really came here for. Let's dive in!

ghostHunterFrontend

  • Parameters: Input (either a dom element or a querySelector-friendly string), Options (an object)
    • required: input, options.results, options.endpoint
  • Supported options:
    • result_template
      • Description: The template for results items
      • Default: <a href='{{link}}'><p><h2>{{title}}</h2><strong>{{pubDate}}</strong></p></a>
      • Available template data: title, description, pubDate, featureImage, link
      • Uses handlebars-like templating (although there's no logic allowed)
    • info_template
      • Description: The template for meta info
      • Default: <p>Number of posts found: {{amount}}</p>
      • Available template data: amount, plural, search
      • plural will just be a single s if the number of results is not 1 - i.e {{amount}} post{{plural}} found for {{search}}
      • search is HTML-escaped
      • Uses handlebars-like templating (although there's no logic allowed)
    • displaySearchInfo
      • Description: Whether to render the template for meta info in the results element
      • Default: true
    • zeroResultsInfo
      • Description: Wheter to render the template for meta info when there are no results
      • Default: true
    • before
      • Description: The function to run before the endpoint request is sent
      • Default: false
      • No parameters are supplied to the function
    • onComplete
      • Description: The function to run after the endpoint response is handled
      • Default: false
      • No parameters are supplied to the function
    • endpoint
      • Description: The endpoint which processes search requests
      • Default: none
      • This is required. ghostHunterFrontend will fail if you don't provide it
    • results
      • Description: The dom element or a querySelector-friendly string of the existing element to render results
      • Default: none
      • This is required. ghostHunterFrontend will fail if you don't provide it
  • Properties
    • options - The settings used for functionality. List of keys are available in the previous section
    • endpoint - The HTTP endpoint where the backend lives
    • input - The text input element
    • target - The form element containing input
    • submitted - The function that is executed when target is submitted. You can call it directly if you please
    • _search - The data processing portion of the search action
    • search - Sends an XHR request to the endpoint

Issues and Support

Feel free to create an issue if you have any questions, feature requests or found a bug. As of now, there's no specific template, but if this gets too much traction, something will be put in place. If you want to contact us directly, shoot us an email - [email protected]

Contributing

Feel free to create a Pull Request if you think any changes should be made. You don't have to explain yourself, but be able to if requested.