koa#Middleware TypeScript Examples

The following examples show how to use koa#Middleware. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: app.dev.ts    From nodestatus with MIT License 6 votes vote down vote up
createMiddleware = async (name: string, publicPath: string): Promise<Middleware> => {
  const vite = await createViteServer({
    root: path.dirname(require.resolve(`${name}/package.json`)),
    server: {
      hmr: {
        port: ++port
      },
      middlewareMode: 'html'
    }
  });

  return async (ctx, next) => {
    const { url } = ctx;
    if (url.startsWith('/api') || !url.startsWith(publicPath)) {
      await next();
    } else {
      await c2k(vite.middlewares)(ctx, next);
    }
  };
}
Example #2
Source File: watchServedFilesMiddleware.ts    From web with MIT License 6 votes vote down vote up
/**
 * Sets up a middleware which tracks served files and sends a reload message to any
 * active browsers when any of the files change.
 */
export function watchServedFilesMiddleware(fileWatcher: FSWatcher, rootDir: string): Middleware {
  return async (ctx, next) => {
    await next();

    if (ctx.response.status !== 404) {
      let filePath = getRequestFilePath(ctx.url, rootDir);
      // if the request ends with a / it might be an index.html, check if it exists
      // and watch it
      if (filePath.endsWith('/')) {
        filePath += 'index.html';
      }

      // watch file if it exists
      fs.stat(filePath, (err, stats) => {
        if (!err && !stats.isDirectory()) {
          fileWatcher.add(filePath);
        }
      });
    }
  };
}
Example #3
Source File: serveFilesMiddleware.ts    From web with MIT License 6 votes vote down vote up
/**
 * Creates multiple middleware used for serving files.
 */
export function serveFilesMiddleware(rootDir: string): Middleware[] {
  const koaStaticOptions: KoaStaticOptions = {
    hidden: true,
    defer: true,
    brotli: false,
    gzip: false,
    setHeaders(res) {
      res.setHeader('cache-control', 'no-cache');
    },
  };

  // the wds-root-dir parameter indicates using a different root directory as a path relative
  // from the regular root dir or as an absolute path
  const serveCustomRootDirMiddleware: Middleware = async (ctx, next) => {
    if (isOutsideRootDir(ctx.path)) {
      const { normalizedPath, newRootDir } = resolvePathOutsideRootDir(ctx.path, rootDir);
      await send(ctx, normalizedPath, { ...koaStaticOptions, root: newRootDir });
      return;
    }
    return next();
  };

  // serve static files from the regular root dir
  return [serveCustomRootDirMiddleware, koaStatic(rootDir, koaStaticOptions)];
}
Example #4
Source File: pluginMimeTypeMiddleware.ts    From web with MIT License 6 votes vote down vote up
/**
 * Sets up a middleware which allows plugins to resolve the mime type.
 */
export function pluginMimeTypeMiddleware(logger: Logger, plugins: Plugin[]): Middleware {
  const mimeTypePlugins = plugins.filter(p => 'resolveMimeType' in p);
  if (mimeTypePlugins.length === 0) {
    // nothing to transform
    return (ctx, next) => next();
  }

  return async (context, next) => {
    await next();

    if (context.status < 200 || context.status >= 300) {
      return undefined;
    }

    for (const plugin of mimeTypePlugins) {
      const result = await plugin.resolveMimeType?.(context);
      const type = typeof result === 'object' ? result.type : result;
      if (type) {
        logger.debug(`Plugin ${plugin.name} resolved mime type of ${context.path} to ${type}`);
        context.type = type;
      }
    }
  };
}
Example #5
Source File: pluginFileParsedMiddleware.ts    From web with MIT License 6 votes vote down vote up
/**
 * Calls fileParsed hook on plugins.
 */
export function pluginFileParsedMiddleware(plugins: Plugin[]): Middleware {
  const fileParsedPlugins = plugins.filter(p => 'fileParsed' in p);
  if (fileParsedPlugins.length === 0) {
    // nothing to call
    return (ctx, next) => next();
  }

  return async (context, next) => {
    await next();

    if (context.status < 200 || context.status >= 300) {
      return undefined;
    }

    for (const plugin of fileParsedPlugins) {
      plugin.fileParsed?.(context);
    }
  };
}
Example #6
Source File: etagCacheMiddleware.ts    From web with MIT License 6 votes vote down vote up
/**
 * Returns 304 response for cacheable requests if etag matches
 */
export function etagCacheMiddleware(): Middleware {
  return async (ctx, next) => {
    await next();

    if (!ctx.body || ctx.status === 304) {
      return;
    }

    if (!['GET', 'HEAD'].includes(ctx.method)) {
      return;
    }

    if (ctx.status < 200 || ctx.status >= 300) {
      return;
    }

    // let koa check if the respone is still fresh this means
    // the etags of the request and response match, so the browser
    // still has a fresh copy so it doesn't need to re-download it
    if (ctx.fresh) {
      ctx.status = 304;
      ctx.response.remove('content-type');
      ctx.response.remove('content-length');
    }

    // don't send last-modified since it doesn't do microseconds, we already
    // generate an etag based on the timestamp from the filesystem
    ctx.response.remove('last-modified');
  };
}
Example #7
Source File: basePathMiddleware.ts    From web with MIT License 6 votes vote down vote up
/**
 * Creates middleware which strips a base path from each request
 */
export function basePathMiddleware(basePath: string): Middleware {
  const pathToStrip = basePath.endsWith('/')
    ? basePath.substring(0, basePath.length - 1)
    : basePath;

  return (ctx, next) => {
    if (ctx.url.startsWith(pathToStrip)) {
      ctx.url = ctx.url.replace(pathToStrip, '');
    }

    return next();
  };
}
Example #8
Source File: adapter.ts    From farrow with MIT License 6 votes vote down vote up
adapter = (httpPipeline: HttpPipeline): Middleware => {
  return (ctx, next) => {
    return httpPipeline.handle(ctx.req, ctx.res, {
      onLast: () => {
        return next()
      },
    })
  }
}
Example #9
Source File: web.ts    From nodestatus with MIT License 6 votes vote down vote up
modifyOrder: Middleware = async ctx => {
  const { order = [] } = ctx.request.body as { order: number[] };
  if (!order.length) {
    ctx.status = 400;
    ctx.body = createRes(1, 'Wrong request');
    return;
  }
  await handleRequest(ctx, updateOrder(order.join(',')));
}
Example #10
Source File: web.ts    From nodestatus with MIT License 6 votes vote down vote up
removeServer: Middleware = async ctx => {
  const { username = '' } = ctx.params;
  if (!username) {
    ctx.status = 400;
    ctx.body = createRes(1, 'Wrong request');
    return;
  }
  await handleRequest(ctx, deleteServer(username));
}
Example #11
Source File: web.ts    From nodestatus with MIT License 6 votes vote down vote up
addServer: Middleware = async ctx => {
  const data = ctx.request.body;
  if (!data) {
    ctx.status = 400;
    ctx.body = createRes(1, 'Wrong request');
    return;
  }
  if (Object.hasOwnProperty.call(data, 'data')) {
    try {
      const d = JSON.parse(data.data);
      await handleRequest(ctx, bulkCreateServer(d));
    } catch (error: any) {
      ctx.status = 400;
      ctx.body = createRes(1, 'Wrong request');
    }
  } else {
    await handleRequest(ctx, createServer(data));
  }
}
Example #12
Source File: web.ts    From nodestatus with MIT License 6 votes vote down vote up
setServer: Middleware = async ctx => {
  const { username } = ctx.request.body;
  const { data } = ctx.request.body;
  if (!username || !data) {
    ctx.status = 400;
    ctx.body = createRes(1, 'Wrong request');
    return;
  }
  if (username === data.username) delete data.username;
  await handleRequest(ctx, updateServer(username, data));
}
Example #13
Source File: web.ts    From nodestatus with MIT License 5 votes vote down vote up
removeEvent: Middleware = async ctx => {
  if (ctx.params.id) {
    await handleRequest(ctx, deleteEvent(Number(ctx.params.id)));
  } else {
    await handleRequest(ctx, deleteAllEvents());
  }
}
Example #14
Source File: web.ts    From nodestatus with MIT License 5 votes vote down vote up
queryEvents: Middleware = async ctx => {
  const size = Number(ctx.query.size) || 10;
  const offset = Number(ctx.query.offset) || 0;
  await handleRequest(ctx, readEvents(size, offset).then(([count, list]) => ({ count, list })));
}
Example #15
Source File: historyApiFallbackMiddleware.ts    From web with MIT License 5 votes vote down vote up
/**
 * Serves index.html when a non-file request within the scope of the app index is made.
 * This allows SPA routing.
 */
export function historyApiFallbackMiddleware(
  appIndex: string,
  rootDir: string,
  logger: Logger,
): Middleware {
  const resolvedAppIndex = path.resolve(appIndex);
  const relativeAppIndex = path.relative(rootDir, resolvedAppIndex);
  const appIndexBrowserPath = `/${toBrowserPath(relativeAppIndex)}`;
  const appIndexBrowserPathPrefix = path.dirname(appIndexBrowserPath);

  return (ctx, next) => {
    if (ctx.method !== 'GET' || path.extname(ctx.path)) {
      // not a GET, or a direct file request
      return next();
    }

    if (!ctx.headers || typeof ctx.headers.accept !== 'string') {
      return next();
    }

    if (ctx.headers.accept.includes('application/json')) {
      return next();
    }

    if (!(ctx.headers.accept.includes('text/html') || ctx.headers.accept.includes('*/*'))) {
      return next();
    }

    if (!ctx.url.startsWith(appIndexBrowserPathPrefix)) {
      return next();
    }

    // rewrite url and let static serve take it further
    logger.debug(`Rewriting ${ctx.url} to app index ${appIndexBrowserPath}`);
    ctx.url = appIndexBrowserPath;
    return next();
  };
}
Example #16
Source File: pluginServeMiddleware.ts    From web with MIT License 5 votes vote down vote up
/**
 * Sets up a middleware which allows plugins to serve files instead of looking it up in the file system.
 */
export function pluginServeMiddleware(logger: Logger, plugins: Plugin[]): Middleware {
  const servePlugins = plugins.filter(p => 'serve' in p);
  if (servePlugins.length === 0) {
    // nothing to serve
    return (ctx, next) => next();
  }

  return async (context, next) => {
    for (const plugin of servePlugins) {
      const response = await plugin.serve?.(context);

      if (typeof response === 'object') {
        if (response.body == null) {
          throw new Error(
            'A serve result must contain a body. Use the transform hook to change only the mime type.',
          );
        }

        context.body = response.body;
        if (response.type != null) {
          context.type = response.type;
        } else {
          context.type = path.extname(path.basename(context.path));
        }

        if (response.headers) {
          for (const [k, v] of Object.entries(response.headers)) {
            context.response.set(k, v);
          }
        }

        logger.debug(`Plugin ${plugin.name} served ${context.path}.`);
        context.status = 200;
        return;
      } else if (typeof response === 'string') {
        context.body = response;
        context.type = path.extname(path.basename(context.path));
        logger.debug(`Plugin ${plugin.name} served ${context.path}.`);
        context.status = 200;
        return;
      }
    }
    return next();
  };
}
Example #17
Source File: web.ts    From nodestatus with MIT License 5 votes vote down vote up
getListServers: Middleware = async ctx => {
  await handleRequest(ctx, readServersList().then(data => data.sort((x, y) => y.order - x.order)));
}
Example #18
Source File: app.dev.ts    From nodestatus with MIT License 5 votes vote down vote up
middlewares: Record<string, Middleware> = {}
Example #19
Source File: createMiddleware.ts    From web with MIT License 5 votes vote down vote up
/**
 * Creates middlewares based on the given configuration. The middlewares can be
 * used by a koa server using `app.use()`:
 */
export function createMiddleware(
  config: DevServerCoreConfig,
  logger: Logger,
  fileWatcher: FSWatcher,
) {
  const middlewares: Middleware[] = [];

  middlewares.push(async (ctx, next) => {
    logger.debug(`Receiving request: ${ctx.url}`);
    await next();
    logger.debug(`Responding to request: ${ctx.url} with status ${ctx.status}`);
  });

  // strips a base path from requests
  if (config.basePath) {
    middlewares.push(basePathMiddleware(config.basePath));
  }

  // adds custom user's middlewares
  for (const m of config.middleware ?? []) {
    middlewares.push(m);
  }

  // watch files that are served
  middlewares.push(watchServedFilesMiddleware(fileWatcher, config.rootDir));

  // serves 304 responses if resource hasn't changed
  middlewares.push(etagCacheMiddleware());

  // adds etag headers for caching
  middlewares.push(koaEtag());

  // serves index.html for non-file requests for SPA routing
  if (config.appIndex) {
    middlewares.push(historyApiFallbackMiddleware(config.appIndex, config.rootDir, logger));
  }

  const plugins = config.plugins ?? [];
  middlewares.push(pluginFileParsedMiddleware(plugins));
  middlewares.push(pluginTransformMiddleware(logger, config, fileWatcher));
  middlewares.push(pluginMimeTypeMiddleware(logger, plugins));
  middlewares.push(pluginServeMiddleware(logger, plugins));
  middlewares.push(...serveFilesMiddleware(config.rootDir));

  return middlewares;
}
Example #20
Source File: pluginTransformMiddleware.ts    From web with MIT License 4 votes vote down vote up
/**
 * Sets up a middleware which allows plugins to transform files before they are served to the browser.
 */
export function pluginTransformMiddleware(
  logger: Logger,
  config: DevServerCoreConfig,
  fileWatcher: FSWatcher,
): Middleware {
  const cache = new PluginTransformCache(fileWatcher, config.rootDir);
  const transformPlugins = (config.plugins ?? []).filter(p => 'transform' in p);
  if (transformPlugins.length === 0) {
    // nothing to transform
    return (ctx, next) => next();
  }

  return async (context, next) => {
    // The cache key is the request URL plus any specific cache keys provided by plugins.
    // For example plugins might do different transformations based on user agent.
    const cacheKey =
      context.url +
      (await Promise.all(transformPlugins.map(p => p.transformCacheKey?.(context))))
        .filter(_ => _)
        .join('_');
    const result = await cache.get(cacheKey);
    if (result) {
      context.body = result.body;
      for (const [k, v] of Object.entries(result.headers)) {
        context.response.set(k, v);
      }
      logger.debug(`Serving cache key "${cacheKey}" from plugin transform cache`);
      return;
    }

    await next();

    if (context.status < 200 || context.status >= 300) {
      return;
    }

    try {
      // ensure response body is turned into a string or buffer
      await getResponseBody(context);

      let disableCache = false;
      let transformedCode = false;
      for (const plugin of transformPlugins) {
        const result = await plugin.transform?.(context);

        if (typeof result === 'object') {
          disableCache = result.transformCache === false ? true : disableCache;
          if (result.body != null) {
            context.body = result.body;
            transformedCode = true;
            logger.debug(`Plugin ${plugin.name} transformed ${context.path}.`);
          }

          if (result.headers) {
            for (const [k, v] of Object.entries(result.headers)) {
              context.response.set(k, v);
            }
          }
        } else if (typeof result === 'string') {
          context.body = result;
          transformedCode = true;
          logger.debug(`Plugin ${plugin.name} transformed ${context.path}.`);
        }
      }

      if (transformedCode && !disableCache) {
        logger.debug(`Added cache key "${cacheKey}" to plugin transform cache`);
        const filePath = getRequestFilePath(context.url, config.rootDir);
        cache.set(
          filePath,
          context.body,
          context.response.headers as Record<string, string>,
          cacheKey,
        );
      }
    } catch (error) {
      if (error instanceof RequestCancelledError) {
        return undefined;
      }
      context.body = 'Error while transforming file. See the terminal for more information.';
      context.status = 500;

      if (error.name === 'PluginSyntaxError') {
        logger.logSyntaxError(error);
        return;
      }

      if (error.name === 'PluginError') {
        logger.error(error.message);
        return;
      }

      throw error;
    }
  };
}