UNPKG

@usebool/sdk-react

Version:

React SDK for Bool feature flag system

630 lines (624 loc) 22.5 kB
// src/FeatureFlagProvider.tsx import React from "react"; // ../core-js/lib/index.module.js var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); var require_browser_ponyfill = __commonJS({ "../../node_modules/.pnpm/cross-fetch@3.1.5/node_modules/cross-fetch/dist/browser-ponyfill.js"(exports, module) { var global = typeof self !== "undefined" ? self : exports; var __self__ = function() { function F() { this.fetch = false; this.DOMException = global.DOMException; } F.prototype = global; return new F(); }(); (function(self2) { var irrelevant = function(exports2) { var support = { searchParams: "URLSearchParams" in self2, iterable: "Symbol" in self2 && "iterator" in Symbol, blob: "FileReader" in self2 && "Blob" in self2 && function() { try { new Blob(); return true; } catch (e) { return false; } }(), formData: "FormData" in self2, arrayBuffer: "ArrayBuffer" in self2 }; function isDataView(obj) { return obj && DataView.prototype.isPrototypeOf(obj); } if (support.arrayBuffer) { var viewClasses = [ "[object Int8Array]", "[object Uint8Array]", "[object Uint8ClampedArray]", "[object Int16Array]", "[object Uint16Array]", "[object Int32Array]", "[object Uint32Array]", "[object Float32Array]", "[object Float64Array]" ]; var isArrayBufferView = ArrayBuffer.isView || function(obj) { return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1; }; } function normalizeName(name) { if (typeof name !== "string") { name = String(name); } if (/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(name)) { throw new TypeError("Invalid character in header field name"); } return name.toLowerCase(); } function normalizeValue(value) { if (typeof value !== "string") { value = String(value); } return value; } function iteratorFor(items) { var iterator = { next: function() { var value = items.shift(); return { done: value === void 0, value }; } }; if (support.iterable) { iterator[Symbol.iterator] = function() { return iterator; }; } return iterator; } function Headers(headers) { this.map = {}; if (headers instanceof Headers) { headers.forEach(function(value, name) { this.append(name, value); }, this); } else if (Array.isArray(headers)) { headers.forEach(function(header) { this.append(header[0], header[1]); }, this); } else if (headers) { Object.getOwnPropertyNames(headers).forEach(function(name) { this.append(name, headers[name]); }, this); } } Headers.prototype.append = function(name, value) { name = normalizeName(name); value = normalizeValue(value); var oldValue = this.map[name]; this.map[name] = oldValue ? oldValue + ", " + value : value; }; Headers.prototype["delete"] = function(name) { delete this.map[normalizeName(name)]; }; Headers.prototype.get = function(name) { name = normalizeName(name); return this.has(name) ? this.map[name] : null; }; Headers.prototype.has = function(name) { return this.map.hasOwnProperty(normalizeName(name)); }; Headers.prototype.set = function(name, value) { this.map[normalizeName(name)] = normalizeValue(value); }; Headers.prototype.forEach = function(callback, thisArg) { for (var name in this.map) { if (this.map.hasOwnProperty(name)) { callback.call(thisArg, this.map[name], name, this); } } }; Headers.prototype.keys = function() { var items = []; this.forEach(function(value, name) { items.push(name); }); return iteratorFor(items); }; Headers.prototype.values = function() { var items = []; this.forEach(function(value) { items.push(value); }); return iteratorFor(items); }; Headers.prototype.entries = function() { var items = []; this.forEach(function(value, name) { items.push([name, value]); }); return iteratorFor(items); }; if (support.iterable) { Headers.prototype[Symbol.iterator] = Headers.prototype.entries; } function consumed(body) { if (body.bodyUsed) { return Promise.reject(new TypeError("Already read")); } body.bodyUsed = true; } function fileReaderReady(reader) { return new Promise(function(resolve, reject) { reader.onload = function() { resolve(reader.result); }; reader.onerror = function() { reject(reader.error); }; }); } function readBlobAsArrayBuffer(blob) { var reader = new FileReader(); var promise = fileReaderReady(reader); reader.readAsArrayBuffer(blob); return promise; } function readBlobAsText(blob) { var reader = new FileReader(); var promise = fileReaderReady(reader); reader.readAsText(blob); return promise; } function readArrayBufferAsText(buf) { var view = new Uint8Array(buf); var chars = new Array(view.length); for (var i = 0; i < view.length; i++) { chars[i] = String.fromCharCode(view[i]); } return chars.join(""); } function bufferClone(buf) { if (buf.slice) { return buf.slice(0); } else { var view = new Uint8Array(buf.byteLength); view.set(new Uint8Array(buf)); return view.buffer; } } function Body() { this.bodyUsed = false; this._initBody = function(body) { this._bodyInit = body; if (!body) { this._bodyText = ""; } else if (typeof body === "string") { this._bodyText = body; } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { this._bodyBlob = body; } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { this._bodyFormData = body; } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { this._bodyText = body.toString(); } else if (support.arrayBuffer && support.blob && isDataView(body)) { this._bodyArrayBuffer = bufferClone(body.buffer); this._bodyInit = new Blob([this._bodyArrayBuffer]); } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { this._bodyArrayBuffer = bufferClone(body); } else { this._bodyText = body = Object.prototype.toString.call(body); } if (!this.headers.get("content-type")) { if (typeof body === "string") { this.headers.set("content-type", "text/plain;charset=UTF-8"); } else if (this._bodyBlob && this._bodyBlob.type) { this.headers.set("content-type", this._bodyBlob.type); } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { this.headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8"); } } }; if (support.blob) { this.blob = function() { var rejected = consumed(this); if (rejected) { return rejected; } if (this._bodyBlob) { return Promise.resolve(this._bodyBlob); } else if (this._bodyArrayBuffer) { return Promise.resolve(new Blob([this._bodyArrayBuffer])); } else if (this._bodyFormData) { throw new Error("could not read FormData body as blob"); } else { return Promise.resolve(new Blob([this._bodyText])); } }; this.arrayBuffer = function() { if (this._bodyArrayBuffer) { return consumed(this) || Promise.resolve(this._bodyArrayBuffer); } else { return this.blob().then(readBlobAsArrayBuffer); } }; } this.text = function() { var rejected = consumed(this); if (rejected) { return rejected; } if (this._bodyBlob) { return readBlobAsText(this._bodyBlob); } else if (this._bodyArrayBuffer) { return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)); } else if (this._bodyFormData) { throw new Error("could not read FormData body as text"); } else { return Promise.resolve(this._bodyText); } }; if (support.formData) { this.formData = function() { return this.text().then(decode); }; } this.json = function() { return this.text().then(JSON.parse); }; return this; } var methods = ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"]; function normalizeMethod(method) { var upcased = method.toUpperCase(); return methods.indexOf(upcased) > -1 ? upcased : method; } function Request(input, options) { options = options || {}; var body = options.body; if (input instanceof Request) { if (input.bodyUsed) { throw new TypeError("Already read"); } this.url = input.url; this.credentials = input.credentials; if (!options.headers) { this.headers = new Headers(input.headers); } this.method = input.method; this.mode = input.mode; this.signal = input.signal; if (!body && input._bodyInit != null) { body = input._bodyInit; input.bodyUsed = true; } } else { this.url = String(input); } this.credentials = options.credentials || this.credentials || "same-origin"; if (options.headers || !this.headers) { this.headers = new Headers(options.headers); } this.method = normalizeMethod(options.method || this.method || "GET"); this.mode = options.mode || this.mode || null; this.signal = options.signal || this.signal; this.referrer = null; if ((this.method === "GET" || this.method === "HEAD") && body) { throw new TypeError("Body not allowed for GET or HEAD requests"); } this._initBody(body); } Request.prototype.clone = function() { return new Request(this, { body: this._bodyInit }); }; function decode(body) { var form = new FormData(); body.trim().split("&").forEach(function(bytes) { if (bytes) { var split = bytes.split("="); var name = split.shift().replace(/\+/g, " "); var value = split.join("=").replace(/\+/g, " "); form.append(decodeURIComponent(name), decodeURIComponent(value)); } }); return form; } function parseHeaders(rawHeaders) { var headers = new Headers(); var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, " "); preProcessedHeaders.split(/\r?\n/).forEach(function(line) { var parts = line.split(":"); var key = parts.shift().trim(); if (key) { var value = parts.join(":").trim(); headers.append(key, value); } }); return headers; } Body.call(Request.prototype); function Response(bodyInit, options) { if (!options) { options = {}; } this.type = "default"; this.status = options.status === void 0 ? 200 : options.status; this.ok = this.status >= 200 && this.status < 300; this.statusText = "statusText" in options ? options.statusText : "OK"; this.headers = new Headers(options.headers); this.url = options.url || ""; this._initBody(bodyInit); } Body.call(Response.prototype); Response.prototype.clone = function() { return new Response(this._bodyInit, { status: this.status, statusText: this.statusText, headers: new Headers(this.headers), url: this.url }); }; Response.error = function() { var response = new Response(null, { status: 0, statusText: "" }); response.type = "error"; return response; }; var redirectStatuses = [301, 302, 303, 307, 308]; Response.redirect = function(url, status) { if (redirectStatuses.indexOf(status) === -1) { throw new RangeError("Invalid status code"); } return new Response(null, { status, headers: { location: url } }); }; exports2.DOMException = self2.DOMException; try { new exports2.DOMException(); } catch (err) { exports2.DOMException = function(message, name) { this.message = message; this.name = name; var error = Error(message); this.stack = error.stack; }; exports2.DOMException.prototype = Object.create(Error.prototype); exports2.DOMException.prototype.constructor = exports2.DOMException; } function fetch2(input, init2) { return new Promise(function(resolve, reject) { var request = new Request(input, init2); if (request.signal && request.signal.aborted) { return reject(new exports2.DOMException("Aborted", "AbortError")); } var xhr = new XMLHttpRequest(); function abortXhr() { xhr.abort(); } xhr.onload = function() { var options = { status: xhr.status, statusText: xhr.statusText, headers: parseHeaders(xhr.getAllResponseHeaders() || "") }; options.url = "responseURL" in xhr ? xhr.responseURL : options.headers.get("X-Request-URL"); var body = "response" in xhr ? xhr.response : xhr.responseText; resolve(new Response(body, options)); }; xhr.onerror = function() { reject(new TypeError("Network request failed")); }; xhr.ontimeout = function() { reject(new TypeError("Network request failed")); }; xhr.onabort = function() { reject(new exports2.DOMException("Aborted", "AbortError")); }; xhr.open(request.method, request.url, true); if (request.credentials === "include") { xhr.withCredentials = true; } else if (request.credentials === "omit") { xhr.withCredentials = false; } if ("responseType" in xhr && support.blob) { xhr.responseType = "blob"; } request.headers.forEach(function(value, name) { xhr.setRequestHeader(name, value); }); if (request.signal) { request.signal.addEventListener("abort", abortXhr); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { request.signal.removeEventListener("abort", abortXhr); } }; } xhr.send(typeof request._bodyInit === "undefined" ? null : request._bodyInit); }); } fetch2.polyfill = true; if (!self2.fetch) { self2.fetch = fetch2; self2.Headers = Headers; self2.Request = Request; self2.Response = Response; } exports2.Headers = Headers; exports2.Request = Request; exports2.Response = Response; exports2.fetch = fetch2; Object.defineProperty(exports2, "__esModule", { value: true }); return exports2; }({}); })(__self__); __self__.fetch.ponyfill = true; delete __self__.fetch.polyfill; var ctx = __self__; exports = ctx.fetch; exports.default = ctx.fetch; exports.fetch = ctx.fetch; exports.Headers = ctx.Headers; exports.Request = ctx.Request; exports.Response = ctx.Response; module.exports = exports; } }); var import_cross_fetch = __toESM(require_browser_ponyfill(), 1); var URL = "https://api.usebool.com/v1/graphql"; var GET_FEATURE_FLAGS_QUERY = ` query GetFeatureFlags { Bool_FeatureFlag { id key name value description } } `; var query = async (queryString, operationName, idToken) => { const graphqlQuery = { operationName, query: queryString }; const options = { method: "POST", headers: { "content-type": "application/json", Authorization: `Bearer ${idToken}` }, body: JSON.stringify(graphqlQuery) }; const response = await (0, import_cross_fetch.default)(URL, options); return await response.json(); }; var hasFeature = async (idToken, key) => { const { data } = await query(GET_FEATURE_FLAGS_QUERY, "GetFeatureFlags", idToken); return getSingleFlagValue(data, key); }; var getSingleFlagValue = (data, key) => { try { const flag = data.Bool_FeatureFlag.find((featureFlag) => featureFlag.key === key); if (!flag) { throw new Error(`Flag with key "${key}" not found`); } return flag.value; } catch (error) { if (error instanceof Error) { console.error(error.message); } else { console.error("Feature flag data could not be fetched"); } return false; } }; var getFeatures = async (idToken) => { const { data } = await query(GET_FEATURE_FLAGS_QUERY, "GetFeatureFlags", idToken); return composeFeatureFlags(data); }; var composeFeatureFlags = (data) => { try { const featureFlags = data?.Bool_FeatureFlag; if (!Array.isArray(featureFlags)) { throw new Error("Feature flags could not be fetched"); } return featureFlags; } catch (error) { if (error instanceof Error) { console.error(error.message); } else { console.error("Feature flags could not be fetched"); } return []; } }; var init = ({ idToken }) => { return { hasFeature: (featureName) => hasFeature(idToken, featureName), getFeatures: () => getFeatures(idToken) }; }; // src/context.ts import { createContext } from "react"; var context = createContext({ flags: [], client: void 0 }); var { Provider, Consumer } = context; // src/FeatureFlagProvider.tsx var FeatureFlagProvider = class extends React.Component { constructor(props) { super(props); this.initializeClient = async () => { const { idToken } = this.props; const { client } = this.state; if (client) { const featureValues = await client.getFeatures(); this.setState({ flags: featureValues }); } else { const initializedClient = init({ idToken }); const featureValues = await initializedClient.getFeatures(); this.setState({ flags: featureValues, client: initializedClient }); } }; this.state = { flags: [], client: void 0 }; } async componentDidMount() { await this.initializeClient(); } render() { return /* @__PURE__ */ React.createElement(Provider, { value: this.state }, this.props.children); } }; // src/useGetFeatures.ts import { useContext } from "react"; var useGetFeatures = () => { const { flags, client } = useContext(context); if (!client) return []; if (!flags.length) { console.error("Something went wrong. It seems you don't have any feature flags."); return []; } return flags; }; // src/useHasFeature.ts import { useContext as useContext2 } from "react"; var useHasFeature = (featureKey) => { const { flags, client } = useContext2(context); if (!client) return false; const feature = flags.find((flag) => flag.key === featureKey); if (!feature) { console.error(`A feature with key ${featureKey} was not found. Did you create it in your dashboard?`); return false; } return feature.value; }; export { FeatureFlagProvider, useGetFeatures, useHasFeature };