{"id":13527538,"url":"https://github.com/lmammino/tall","last_synced_at":"2025-05-10T22:51:26.319Z","repository":{"id":19213035,"uuid":"86499838","full_name":"lmammino/tall","owner":"lmammino","description":"Promise-based, No-dependency URL unshortner (expander) module for Node.js","archived":false,"fork":false,"pushed_at":"2023-04-18T08:39:17.000Z","size":611,"stargazers_count":73,"open_issues_count":4,"forks_count":7,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-10T22:51:22.809Z","etag":null,"topics":["library","module","no-dependencies","node","nodejs","promise","promises","typescript","typescript-library","url","url-expand","url-parsing","url-shortener","url-unshorten"],"latest_commit_sha":null,"homepage":"https://lmammino.github.io/tall/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lmammino.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-03-28T19:37:25.000Z","updated_at":"2024-12-27T06:49:34.000Z","dependencies_parsed_at":"2024-01-13T22:53:30.385Z","dependency_job_id":null,"html_url":"https://github.com/lmammino/tall","commit_stats":{"total_commits":45,"total_committers":9,"mean_commits":5.0,"dds":0.4444444444444444,"last_synced_commit":"bdd5b0f4a86fa2b25583d1d7293f11eec14947b0"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lmammino%2Ftall","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lmammino%2Ftall/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lmammino%2Ftall/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lmammino%2Ftall/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lmammino","download_url":"https://codeload.github.com/lmammino/tall/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253492615,"owners_count":21916964,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["library","module","no-dependencies","node","nodejs","promise","promises","typescript","typescript-library","url","url-expand","url-parsing","url-shortener","url-unshorten"],"created_at":"2024-08-01T06:01:50.600Z","updated_at":"2025-05-10T22:51:26.302Z","avatar_url":"https://github.com/lmammino.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# tall\n\n[![npm version](https://img.shields.io/npm/v/tall)](https://npm.im/tall)\n[![Build Status](https://github.com/lmammino/tall/workflows/main/badge.svg)](https://github.com/lmammino/tall/actions?query=workflow%3Amain)\n[![codecov.io](https://codecov.io/gh/lmammino/tall/coverage.svg?branch=master)](https://codecov.io/gh/lmammino/tall)\n[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)\n[![Written in TypeScript](https://badgen.net/badge/-/TypeScript?icon=typescript\u0026label\u0026labelColor=blue\u0026color=555555)](https://www.typescriptlang.org/)\n\nPromise-based, No-dependency URL unshortner (expander) module for Node.js 16+.\n\n**Note**: This library is written in **TypeScript** and type definitions are provided.\n\n## Install\n\nUsing npm\n\n```bash\nnpm install --save tall\n```\n\nor with yarn\n\n```bash\nyarn add tall\n```\n\n## Usage\n\nES6+ usage:\n\n```javascript\nimport { tall } from 'tall'\n\ntall('http://www.loige.link/codemotion-rome-2017')\n  .then((unshortenedUrl) =\u003e console.log('Tall url', unshortenedUrl))\n  .catch((err) =\u003e console.error('AAAW 👻', err))\n```\n\nWith Async await:\n\n```javascript\nimport { tall } from 'tall'\n\nasync function someFunction() {\n  try {\n    const unshortenedUrl = await tall(\n      'http://www.loige.link/codemotion-rome-2017'\n    )\n    console.log('Tall url', unshortenedUrl)\n  } catch (err) {\n    console.error('AAAW 👻', err)\n  }\n}\n\nsomeFunction()\n```\n\nES5:\n\n```javascript\nvar { tall } = require('tall')\ntall('http://www.loige.link/codemotion-rome-2017')\n  .then(function (unshortenedUrl) {\n    console.log('Tall url', unshortenedUrl)\n  })\n  .catch(function (err) {\n    console.error('AAAW 👻', err)\n  })\n```\n\n## Options\n\nIt is possible to specify some options as second parameter to the `tall` function.\n\nAvailable options are the following:\n\n- `method` (default `\"GET\"`): any available HTTP method\n- `maxRedirects` (default `3`): the number of maximum redirects that will be followed in case of multiple redirects.\n- `headers` (default `{}`): change request headers - e.g. `{'User-Agent': 'your-custom-user-agent'}`\n- `timeout`: (default: `120000`): timeout in milliseconds after which the request will be cancelled\n- `plugins`: (default: `[locationHeaderPlugin]`): a list of [plugins](#plugins) for adding advanced behaviours\n\nIn addition, any other options available on [http.request()](`https://nodejs.org/api/http.html#httprequestoptions-callback`) or `https.request()` are accepted. This for example includes `rejectUnauthorized` to disable certificate checks.\n\nExample:\n\n```javascript\nimport { tall } from 'tall'\n\ntall('http://www.loige.link/codemotion-rome-2017', {\n  method: 'HEAD',\n  maxRedirect: 10\n})\n  .then((unshortenedUrl) =\u003e console.log('Tall url', unshortenedUrl))\n  .catch((err) =\u003e console.error('AAAW 👻', err))\n```\n\n## Plugins\n\nSince `tall` v5+, a plugin system for extending the default behaviour of tall is available.\n\nBy default `tall` comes with 1 single plugin, the `locationHeaderPlugin` which is enabled by default. This plugin follows redirects by looking at the `location` header in the HTTP response received from the source URL.\n\nYou might want to write your own plugins to have more sophisticated behaviours.\n\nSome example?\n\n- Normalise the final URL if the final page has a `\u003clink rel=\"canonical\" href=\"http://example.com/page/\" /\u003e` tag in the `\u003chead\u003e` of the document\n- Follow HTML meta refresh redirects (`\u003cmeta http-equiv=\"refresh\" content=\"0;URL='http://example.com/'\" /\u003e`)\n\n### Known plugins\n\n- [`tall-plugin-meta-refresh`](https://npm.im/tall-plugin-meta-refresh) (official): follows redirects in `\u003cmeta http-equiv=\"refresh\"\u003e` tags\n\nDid you create a plugin for `tall`? Send us a PR to have it listed here!\n\n## How to write a plugin\n\nA plugin is simply a function with a specific signature:\n\n```typescript\nexport interface TallPlugin {\n  (url: URL, response: IncomingMessage, previous: Follow | Stop): Promise\u003c\n    Follow | Stop\n  \u003e\n}\n```\n\nSo the only thing you need to do is to write your custom behaviour following this interface. But let's discuss briefly what the different elements mean here:\n\n- `url`: Is the current URL being crawled\n- `response`: is the actual HTTP response object representing the current\n- `previous`: the decision from the previous plugin execution (continue following a given URL or stop at a given URL)\n\nEvery plugin is executed asynchronously, so a plugin returns a Promise that needs to resolve to a `Follow` or a `Stop` decision.\n\nLet's deep dive into these two concepts. `Follow` and `Stop` are defined as _follows_ (touché):\n\n```typescript\nexport class Follow {\n  follow: URL\n  constructor(follow: URL) {\n    this.follow = follow\n  }\n}\n\nexport class Stop {\n  stop: URL\n  constructor(stop: URL) {\n    this.stop = stop\n  }\n}\n```\n\n`Follow` and `Stop` are effectively simple classes to express an intent: _should we follow the `follow` URL or should we stop at the `stop` URL?_\n\nPlugins are executed following the middleware pattern (or chain of responsibility): they are executed in order and the information is propagated from one to the other.\n\nFor example, if we initialise `tall` with `{ plugins: [plugin1, plugin2] }`, for every URL, `plugin1` will be executed before `plugin2` and the decision of `plugin1` will be passed over onto `plugin2` using the `previous`) parameter.\n\n## How to write and enable a plugin\n\nLet's say we want to add a plugin that allows us to follow HTML meta refresh redirects, the code could look like this:\n\n```typescript\n// metarefresh-plugin.ts\nimport { IncomingMessage } from 'http'\nimport { Follow, Stop } from 'tall'\n\nexport async function metaRefreshPlugin(\n  url: URL,\n  response: IncomingMessage,\n  previous: Follow | Stop\n): Promise\u003cFollow | Stop\u003e {\n  let html = ''\n  for await (const chunk of response) {\n    html += chunk.toString()\n  }\n\n  // note: This is just a dummy example to illustrate how to use the plugin API.\n  // It's not a great idea to parse HTML using regexes.\n  // If you are looking for a plugin that does this in a better way check out\n  // https://npm.im/tall-plugin-meta-refresh\n  const metaHttpEquivUrl = html.match(\n    /meta +http-equiv=\"refresh\" +content=\"\\d;url=(http[^\"]+)\"/\n  )?.[1]\n\n  if (metaHttpEquivUrl) {\n    return new Follow(new URL(metaHttpEquivUrl))\n  }\n\n  return previous\n}\n```\n\nThen, this is how you would use your shiny new plugin:\n\n```typescript\nimport { tall, locationHeaderPlugin } from 'tall'\nimport { metaRefreshPlugin } from './metarefresh-plugin'\n\nconst finalUrl = await tall('https://loige.link/senior', {\n  plugins: [locationHeaderPlugin, metaRefreshPlugin]\n})\n\nconsole.log(finalUrl)\n```\n\nNote that we have to explicitly pass the `locationHeaderPlugin` if we want to retain `tall` original behaviour.\n\n## Contributing\n\nEveryone is very welcome to contribute to this project.\nYou can contribute just by submitting bugs or suggesting improvements by\n[opening an issue on GitHub](https://github.com/lmammino/tall/issues).\n\n\u003e **Note**: Since Tall v6, the project structure is a monorepo, so you'll need to use a recent version of npm that supports workspaces (e.g. npm 8.5+)\n\n## License\n\nLicensed under [MIT License](LICENSE). © Luciano Mammino.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flmammino%2Ftall","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flmammino%2Ftall","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flmammino%2Ftall/lists"}