Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

angular-resource

Package Overview
Dependencies
Maintainers
2
Versions
103
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

angular-resource - npm Package Compare versions

Comparing version 1.5.8 to 1.6.0-rc.0

162

angular-resource.js
/**
* @license AngularJS v1.5.8
* @license AngularJS v1.6.0-rc.0
* (c) 2010-2016 Google, Inc. http://angularjs.org

@@ -140,6 +140,9 @@ * License: MIT

*
* @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend
* the default set of resource actions. The declaration should be created in the format of {@link
* ng.$http#usage $http.config}:
* @param {Object.<Object>=} actions Hash with declaration of custom actions that will be available
* in addition to the default set of resource actions (see below). If a custom action has the same
* key as a default action (e.g. `save`), then the default action will be *overwritten*, and not
* extended.
*
* The declaration should be created in the format of {@link ng.$http#usage $http.config}:
*
* {action1: {method:?, params:?, isArray:?, headers:?, ...},

@@ -168,8 +171,9 @@ * action2: {method:?, params:?, isArray:?, headers:?, ...},

* By default, transformRequest will contain one function that checks if the request data is
* an object and serializes to using `angular.toJson`. To prevent this behavior, set
* an object and serializes it using `angular.toJson`. To prevent this behavior, set
* `transformRequest` to an empty array: `transformRequest: []`
* - **`transformResponse`** –
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
* `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` –
* transform function or an array of such functions. The transform function takes the http
* response body and headers and returns its transformed (typically deserialized) version.
* response body, headers and status and returns its transformed (typically deserialized)
* version.
* By default, transformResponse will contain one function that checks if the response looks

@@ -248,5 +252,5 @@ * like a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior,

*
* Success callback is called with (value, responseHeaders) arguments, where the value is
* the populated resource instance or collection object. The error callback is called
* with (httpResponse) argument.
* Success callback is called with (value (Object|Array), responseHeaders (Function),
* status (number), statusText (string)) arguments, where the value is the populated resource
* instance or collection object. The error callback is called with (httpResponse) argument.
*

@@ -436,4 +440,5 @@ * Class actions return empty instance (with additional properties below).

angular.module('ngResource', ['ng']).
provider('$resource', function() {
provider('$resource', function ResourceProvider() {
var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/;
var provider = this;

@@ -482,3 +487,3 @@

* module('myApp').
* config(['resourceProvider', function ($resourceProvider) {
* config(['$resourceProvider', function ($resourceProvider) {
* $resourceProvider.defaults.actions.update = {

@@ -495,3 +500,3 @@ * method: 'PUT'

* module('myApp').
* config(['resourceProvider', function ($resourceProvider) {
* config(['$resourceProvider', function ($resourceProvider) {
* $resourceProvider.defaults.actions = {

@@ -528,46 +533,12 @@ * create: {method: 'POST'}

var noop = angular.noop,
forEach = angular.forEach,
extend = angular.extend,
copy = angular.copy,
isFunction = angular.isFunction;
forEach = angular.forEach,
extend = angular.extend,
copy = angular.copy,
isArray = angular.isArray,
isDefined = angular.isDefined,
isFunction = angular.isFunction,
isNumber = angular.isNumber,
encodeUriQuery = angular.$$encodeUriQuery,
encodeUriSegment = angular.$$encodeUriSegment;
/**
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set
* (pchar) allowed in path segments:
* segment = *pchar
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* pct-encoded = "%" HEXDIG HEXDIG
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*/
function encodeUriSegment(val) {
return encodeUriQuery(val, true).
replace(/%26/gi, '&').
replace(/%3D/gi, '=').
replace(/%2B/gi, '+');
}
/**
* This method is intended for encoding *key* or *value* parts of query component. We need a
* custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't
* have to be encoded per http://tools.ietf.org/html/rfc3986:
* query = *( pchar / "/" / "?" )
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* pct-encoded = "%" HEXDIG HEXDIG
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*/
function encodeUriQuery(val, pctEncodeSpaces) {
return encodeURIComponent(val).
replace(/%40/gi, '@').
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
}
function Route(template, defaults) {

@@ -587,11 +558,11 @@ this.template = template;

var urlParams = self.urlParams = {};
var urlParams = self.urlParams = Object.create(null);
forEach(url.split(/\W/), function(param) {
if (param === 'hasOwnProperty') {
throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name.");
throw $resourceMinErr('badname', 'hasOwnProperty is not a valid parameter name.');
}
if (!(new RegExp("^\\d+$").test(param)) && param &&
(new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
if (!(new RegExp('^\\d+$').test(param)) && param &&
(new RegExp('(^|[^\\\\]):' + param + '(\\W|$)').test(url))) {
urlParams[param] = {
isQueryParamValue: (new RegExp("\\?.*=:" + param + "(?:\\W|$)")).test(url)
isQueryParamValue: (new RegExp('\\?.*=:' + param + '(?:\\W|$)')).test(url)
};

@@ -609,3 +580,3 @@ }

val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
if (angular.isDefined(val) && val !== null) {
if (isDefined(val) && val !== null) {
if (paramInfo.isQueryParamValue) {

@@ -616,9 +587,9 @@ encodedVal = encodeUriQuery(val, true);

}
url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) {
url = url.replace(new RegExp(':' + urlParam + '(\\W|$)', 'g'), function(match, p1) {
return encodedVal + p1;
});
} else {
url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
url = url.replace(new RegExp('(/?):' + urlParam + '(\\W|$)', 'g'), function(match,
leadingSlashes, tail) {
if (tail.charAt(0) == '/') {
if (tail.charAt(0) === '/') {
return tail;

@@ -665,3 +636,3 @@ } else {

if (isFunction(value)) { value = value(data); }
ids[key] = value && value.charAt && value.charAt(0) == '@' ?
ids[key] = value && value.charAt && value.charAt(0) === '@' ?
lookupDottedPath(data, value.substr(1)) : value;

@@ -690,7 +661,6 @@ });

var numericTimeout = action.timeout;
var cancellable = angular.isDefined(action.cancellable) ? action.cancellable :
(options && angular.isDefined(options.cancellable)) ? options.cancellable :
provider.defaults.cancellable;
var cancellable = isDefined(action.cancellable) ?
action.cancellable : route.defaults.cancellable;
if (numericTimeout && !angular.isNumber(numericTimeout)) {
if (numericTimeout && !isNumber(numericTimeout)) {
$log.debug('ngResource:\n' +

@@ -708,3 +678,2 @@ ' Only numeric values are allowed as `timeout`.\n' +

/* jshint -W086 */ /* (purposefully fall through case statements) */
switch (arguments.length) {

@@ -714,3 +683,3 @@ case 4:

success = a3;
//fallthrough
// falls through
case 3:

@@ -727,3 +696,3 @@ case 2:

error = a3;
//fallthrough
// falls through
} else {

@@ -735,2 +704,3 @@ params = a1;

}
// falls through
case 1:

@@ -744,6 +714,5 @@ if (isFunction(a1)) success = a1;

throw $resourceMinErr('badargs',
"Expected up to 4 arguments [params, data, success, error], got {0} arguments",
'Expected up to 4 arguments [params, data, success, error], got {0} arguments',
arguments.length);
}
/* jshint +W086 */ /* (purposefully fall through case statements) */

@@ -757,2 +726,4 @@ var isInstanceCall = this instanceof Resource;

undefined;
var hasError = !!error;
var hasResponseErrorInterceptor = !!responseErrorInterceptor;
var timeoutDeferred;

@@ -793,14 +764,12 @@ var numericTimeoutPromise;

// Need to convert action.isArray to boolean in case it is undefined
// jshint -W018
if (angular.isArray(data) !== (!!action.isArray)) {
if (isArray(data) !== (!!action.isArray)) {
throw $resourceMinErr('badcfg',
'Error in resource configuration for action `{0}`. Expected response to ' +
'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object',
angular.isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url);
isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url);
}
// jshint +W018
if (action.isArray) {
value.length = 0;
forEach(data, function(item) {
if (typeof item === "object") {
if (typeof item === 'object') {
value.push(new Resource(item));

@@ -823,11 +792,8 @@ } else {

return response;
}, function(response) {
(error || noop)(response);
return $q.reject(response);
});
promise['finally'](function() {
promise = promise['finally'](function() {
value.$resolved = true;
if (!isInstanceCall && cancellable) {
value.$cancelRequest = angular.noop;
value.$cancelRequest = noop;
$timeout.cancel(numericTimeoutPromise);

@@ -841,6 +807,18 @@ timeoutDeferred = numericTimeoutPromise = httpConfig.timeout = null;

var value = responseInterceptor(response);
(success || noop)(value, response.headers);
(success || noop)(value, response.headers, response.status, response.statusText);
return value;
},
responseErrorInterceptor);
(hasError || hasResponseErrorInterceptor) ?
function(response) {
if (hasError) error(response);
return hasResponseErrorInterceptor ?
responseErrorInterceptor(response) :
$q.reject(response);
} :
undefined);
if (hasError && !hasResponseErrorInterceptor) {
// Avoid `Possibly Unhandled Rejection` error,
// but still fulfill the returned promise with a rejection
promise.catch(noop);
}

@@ -853,3 +831,3 @@ if (!isInstanceCall) {

value.$resolved = false;
if (cancellable) value.$cancelRequest = timeoutDeferred.resolve;
if (cancellable) value.$cancelRequest = cancelRequest;

@@ -861,2 +839,7 @@ return value;

return promise;
function cancelRequest(value) {
promise.catch(noop);
timeoutDeferred.resolve(value);
}
};

@@ -875,3 +858,4 @@

Resource.bind = function(additionalParamDefaults) {
return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
var extendedParamDefaults = extend({}, paramDefaults, additionalParamDefaults);
return resourceFactory(url, extendedParamDefaults, actions, options);
};

@@ -878,0 +862,0 @@

/*
AngularJS v1.5.8
AngularJS v1.6.0-rc.0
(c) 2010-2016 Google, Inc. http://angularjs.org
License: MIT
*/
(function(P,d){'use strict';function G(t,g){g=g||{};d.forEach(g,function(d,q){delete g[q]});for(var q in t)!t.hasOwnProperty(q)||"$"===q.charAt(0)&&"$"===q.charAt(1)||(g[q]=t[q]);return g}var z=d.$$minErr("$resource"),M=/^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;d.module("ngResource",["ng"]).provider("$resource",function(){var t=/^https?:\/\/[^\/]*/,g=this;this.defaults={stripTrailingSlashes:!0,cancellable:!1,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},
"delete":{method:"DELETE"}}};this.$get=["$http","$log","$q","$timeout",function(q,L,H,I){function A(d,h){return encodeURIComponent(d).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,h?"%20":"+")}function B(d,h){this.template=d;this.defaults=v({},g.defaults,h);this.urlParams={}}function J(e,h,n,k){function b(a,c){var b={};c=v({},h,c);u(c,function(c,h){x(c)&&(c=c(a));var f;if(c&&c.charAt&&"@"==c.charAt(0)){f=a;var l=c.substr(1);if(null==l||""===l||"hasOwnProperty"===
l||!M.test("."+l))throw z("badmember",l);for(var l=l.split("."),m=0,k=l.length;m<k&&d.isDefined(f);m++){var r=l[m];f=null!==f?f[r]:void 0}}else f=c;b[h]=f});return b}function N(a){return a.resource}function m(a){G(a||{},this)}var t=new B(e,k);n=v({},g.defaults.actions,n);m.prototype.toJSON=function(){var a=v({},this);delete a.$promise;delete a.$resolved;return a};u(n,function(a,c){var h=/^(POST|PUT|PATCH)$/i.test(a.method),e=a.timeout,E=d.isDefined(a.cancellable)?a.cancellable:k&&d.isDefined(k.cancellable)?
k.cancellable:g.defaults.cancellable;e&&!d.isNumber(e)&&(L.debug("ngResource:\n Only numeric values are allowed as `timeout`.\n Promises are not supported in $resource, because the same value would be used for multiple requests. If you are looking for a way to cancel requests, you should use the `cancellable` option."),delete a.timeout,e=null);m[c]=function(f,l,k,g){var r={},n,w,C;switch(arguments.length){case 4:C=g,w=k;case 3:case 2:if(x(l)){if(x(f)){w=f;C=l;break}w=l;C=k}else{r=f;n=l;w=k;break}case 1:x(f)?
w=f:h?n=f:r=f;break;case 0:break;default:throw z("badargs",arguments.length);}var D=this instanceof m,p=D?n:a.isArray?[]:new m(n),s={},A=a.interceptor&&a.interceptor.response||N,B=a.interceptor&&a.interceptor.responseError||void 0,y,F;u(a,function(a,c){switch(c){default:s[c]=O(a);case "params":case "isArray":case "interceptor":case "cancellable":}});!D&&E&&(y=H.defer(),s.timeout=y.promise,e&&(F=I(y.resolve,e)));h&&(s.data=n);t.setUrlParams(s,v({},b(n,a.params||{}),r),a.url);r=q(s).then(function(f){var b=
f.data;if(b){if(d.isArray(b)!==!!a.isArray)throw z("badcfg",c,a.isArray?"array":"object",d.isArray(b)?"array":"object",s.method,s.url);if(a.isArray)p.length=0,u(b,function(a){"object"===typeof a?p.push(new m(a)):p.push(a)});else{var l=p.$promise;G(b,p);p.$promise=l}}f.resource=p;return f},function(a){(C||K)(a);return H.reject(a)});r["finally"](function(){p.$resolved=!0;!D&&E&&(p.$cancelRequest=d.noop,I.cancel(F),y=F=s.timeout=null)});r=r.then(function(a){var c=A(a);(w||K)(c,a.headers);return c},B);
return D?r:(p.$promise=r,p.$resolved=!1,E&&(p.$cancelRequest=y.resolve),p)};m.prototype["$"+c]=function(a,b,d){x(a)&&(d=b,b=a,a={});a=m[c].call(this,a,this,b,d);return a.$promise||a}});m.bind=function(a){return J(e,v({},h,a),n)};return m}var K=d.noop,u=d.forEach,v=d.extend,O=d.copy,x=d.isFunction;B.prototype={setUrlParams:function(e,h,n){var k=this,b=n||k.template,g,m,q="",a=k.urlParams={};u(b.split(/\W/),function(c){if("hasOwnProperty"===c)throw z("badname");!/^\d+$/.test(c)&&c&&(new RegExp("(^|[^\\\\]):"+
c+"(\\W|$)")).test(b)&&(a[c]={isQueryParamValue:(new RegExp("\\?.*=:"+c+"(?:\\W|$)")).test(b)})});b=b.replace(/\\:/g,":");b=b.replace(t,function(a){q=a;return""});h=h||{};u(k.urlParams,function(a,e){g=h.hasOwnProperty(e)?h[e]:k.defaults[e];d.isDefined(g)&&null!==g?(m=a.isQueryParamValue?A(g,!0):A(g,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+"),b=b.replace(new RegExp(":"+e+"(\\W|$)","g"),function(a,c){return m+c})):b=b.replace(new RegExp("(/?):"+e+"(\\W|$)","g"),function(a,c,b){return"/"==
b.charAt(0)?b:c+b})});k.defaults.stripTrailingSlashes&&(b=b.replace(/\/+$/,"")||"/");b=b.replace(/\/\.(?=\w+($|\?))/,".");e.url=q+b.replace(/\/\\\./,"/.");u(h,function(a,b){k.urlParams[b]||(e.params=e.params||{},e.params[b]=a)})}};return J}]})})(window,window.angular);
(function(W,b){'use strict';function K(q,g){g=g||{};b.forEach(g,function(b,h){delete g[h]});for(var h in q)!q.hasOwnProperty(h)||"$"===h.charAt(0)&&"$"===h.charAt(1)||(g[h]=q[h]);return g}var B=b.$$minErr("$resource"),Q=/^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;b.module("ngResource",["ng"]).provider("$resource",function(){var q=/^https?:\/\/[^\/]*/,g=this;this.defaults={stripTrailingSlashes:!0,cancellable:!1,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},
"delete":{method:"DELETE"}}};this.$get=["$http","$log","$q","$timeout",function(h,P,L,M){function C(b,e){this.template=b;this.defaults=p({},g.defaults,e);this.urlParams={}}function x(D,e,u,m){function c(a,d){var c={};d=p({},e,d);t(d,function(d,l){y(d)&&(d=d(a));var f;if(d&&d.charAt&&"@"===d.charAt(0)){f=a;var k=d.substr(1);if(null==k||""===k||"hasOwnProperty"===k||!Q.test("."+k))throw B("badmember",k);for(var k=k.split("."),e=0,g=k.length;e<g&&b.isDefined(f);e++){var h=k[e];f=null!==f?f[h]:void 0}}else f=
d;c[l]=f});return c}function R(a){return a.resource}function l(a){K(a||{},this)}var q=new C(D,m);u=p({},g.defaults.actions,u);l.prototype.toJSON=function(){var a=p({},this);delete a.$promise;delete a.$resolved;return a};t(u,function(a,d){var b=/^(POST|PUT|PATCH)$/i.test(a.method),e=a.timeout,g=N(a.cancellable)?a.cancellable:q.defaults.cancellable;e&&!S(e)&&(P.debug("ngResource:\n Only numeric values are allowed as `timeout`.\n Promises are not supported in $resource, because the same value would be used for multiple requests. If you are looking for a way to cancel requests, you should use the `cancellable` option."),
delete a.timeout,e=null);l[d]=function(f,k,m,D){function u(a){r.catch(E);z.resolve(a)}var G={},v,w,A;switch(arguments.length){case 4:A=D,w=m;case 3:case 2:if(y(k)){if(y(f)){w=f;A=k;break}w=k;A=m}else{G=f;v=k;w=m;break}case 1:y(f)?w=f:b?v=f:G=f;break;case 0:break;default:throw B("badargs",arguments.length);}var F=this instanceof l,n=F?v:a.isArray?[]:new l(v),s={},C=a.interceptor&&a.interceptor.response||R,x=a.interceptor&&a.interceptor.responseError||void 0,H=!!A,I=!!x,z,J;t(a,function(a,d){switch(d){default:s[d]=
T(a);case "params":case "isArray":case "interceptor":case "cancellable":}});!F&&g&&(z=L.defer(),s.timeout=z.promise,e&&(J=M(z.resolve,e)));b&&(s.data=v);q.setUrlParams(s,p({},c(v,a.params||{}),G),a.url);var r=h(s).then(function(f){var c=f.data;if(c){if(O(c)!==!!a.isArray)throw B("badcfg",d,a.isArray?"array":"object",O(c)?"array":"object",s.method,s.url);if(a.isArray)n.length=0,t(c,function(a){"object"===typeof a?n.push(new l(a)):n.push(a)});else{var b=n.$promise;K(c,n);n.$promise=b}}f.resource=n;
return f}),r=r["finally"](function(){n.$resolved=!0;!F&&g&&(n.$cancelRequest=E,M.cancel(J),z=J=s.timeout=null)}),r=r.then(function(a){var d=C(a);(w||E)(d,a.headers,a.status,a.statusText);return d},H||I?function(a){H&&A(a);return I?x(a):L.reject(a)}:void 0);H&&!I&&r.catch(E);return F?r:(n.$promise=r,n.$resolved=!1,g&&(n.$cancelRequest=u),n)};l.prototype["$"+d]=function(a,c,b){y(a)&&(b=c,c=a,a={});a=l[d].call(this,a,this,c,b);return a.$promise||a}});l.bind=function(a){a=p({},e,a);return x(D,a,u,m)};
return l}var E=b.noop,t=b.forEach,p=b.extend,T=b.copy,O=b.isArray,N=b.isDefined,y=b.isFunction,S=b.isNumber,U=b.$$encodeUriQuery,V=b.$$encodeUriSegment;C.prototype={setUrlParams:function(b,e,g){var m=this,c=g||m.template,h,l,p="",a=m.urlParams=Object.create(null);t(c.split(/\W/),function(d){if("hasOwnProperty"===d)throw B("badname");!/^\d+$/.test(d)&&d&&(new RegExp("(^|[^\\\\]):"+d+"(\\W|$)")).test(c)&&(a[d]={isQueryParamValue:(new RegExp("\\?.*=:"+d+"(?:\\W|$)")).test(c)})});c=c.replace(/\\:/g,":");
c=c.replace(q,function(a){p=a;return""});e=e||{};t(m.urlParams,function(a,b){h=e.hasOwnProperty(b)?e[b]:m.defaults[b];N(h)&&null!==h?(l=a.isQueryParamValue?U(h,!0):V(h),c=c.replace(new RegExp(":"+b+"(\\W|$)","g"),function(a,b){return l+b})):c=c.replace(new RegExp("(/?):"+b+"(\\W|$)","g"),function(a,b,d){return"/"===d.charAt(0)?d:b+d})});m.defaults.stripTrailingSlashes&&(c=c.replace(/\/+$/,"")||"/");c=c.replace(/\/\.(?=\w+($|\?))/,".");b.url=p+c.replace(/\/\\\./,"/.");t(e,function(a,c){m.urlParams[c]||
(b.params=b.params||{},b.params[c]=a)})}};return x}]})})(window,window.angular);
//# sourceMappingURL=angular-resource.min.js.map
{
"name": "angular-resource",
"version": "1.5.8",
"version": "1.6.0-rc.0",
"license": "MIT",

@@ -8,4 +8,4 @@ "main": "./angular-resource.js",

"dependencies": {
"angular": "1.5.8"
"angular": "1.6.0-rc.0"
}
}
{
"name": "angular-resource",
"version": "1.5.8",
"version": "1.6.0-rc.0",
"description": "AngularJS module for interacting with RESTful server-side data sources",

@@ -5,0 +5,0 @@ "main": "index.js",

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc