Skip to content

Instantly share code, notes, and snippets.

@dherges
Created June 15, 2018 16:11
Show Gist options
  • Save dherges/d5f29bee6757221d44319eb6402aab78 to your computer and use it in GitHub Desktop.
Save dherges/d5f29bee6757221d44319eb6402aab78 to your computer and use it in GitHub Desktop.
reframe stuff
import { Injectable, Inject } from "@angular/core";
import { ParsedUrl } from './url/url.interfaces';
import { AppResolverOptions, APP_RESOLVER_OPTIONS } from './app-launcher.interfaces';
@Injectable()
export class AppResolver {
constructor(
@Inject(APP_RESOLVER_OPTIONS) private options: AppResolverOptions
) {}
public resolveIframeUrl(url: ParsedUrl) {
let value = this.options.prefix
.concat('u/', url.appName, '/');
if (url.entryPoint) {
value = value.concat('#/external/', url.entryPoint);
}
if (url.params) {
// TODO: append query params also to url?!?
}
return value;
}
}
import { ParsedUrl } from './url.interfaces';
const PREFIX = 'u://';
type STATE = 'key' | 'value';
const QUERY_PARAM_RE = /^[^=?&#]+/;
// Return the name of the query param at the start of the string or an empty string
function matchQueryParamName(str: string): string {
const match = str.match(QUERY_PARAM_RE);
return match ? match[0] : '';
}
const QUERY_PARAM_VALUE_RE = /^[^?&#]+/;
// Return the value of the query param at the start of the string or an empty string
function matchQueryParamValue(str: string): string {
const match = str.match(QUERY_PARAM_VALUE_RE);
return match ? match[0] : '';
}
// Query keys/values should have the "+" replaced first, as "+" in a query string is " ".
// decodeURIComponent function will not decode "+" as a space.
function decodeQuery(s: string): string {
return decodeURIComponent(s.replace(/\+/g, '%20'));
}
export class UrlParser {
private remaining: string;
constructor(url: string) {
this.remaining = url;
}
parse(): ParsedUrl {
const url = '' + this.remaining;
if (!this.consumeOptional(PREFIX)) {
throw new Error(`Url must start with ${PREFIX}, given ${this.remaining}`);
}
const appName = this.captureOptional('/');
const entryPoint = this.captureOptional('?');
const params = {};
do {
const key = matchQueryParamName(this.remaining);
if (!key) {
break;
}
this.consumeOptional(key);
let value: any = '';
if (this.consumeOptional('=')) {
value = matchQueryParamValue(this.remaining);
if (value) {
this.consumeOptional(value);
} else {
value = '';
}
}
const decodedKey = decodeQuery(key);
const decodedVal = decodeQuery(value);
if (params.hasOwnProperty(decodedKey)) {
// Append to existing values
let currentVal = params[decodedKey];
if (!Array.isArray(currentVal)) {
currentVal = [currentVal];
params[decodedKey] = currentVal;
}
currentVal.push(decodedVal);
} else {
// Create a new value
params[decodedKey] = decodedVal;
}
} while (this.consumeOptional('&'));
return {
url,
appName,
entryPoint,
params
};
}
private peekStartsWith(str: string): boolean {
return this.remaining.startsWith(str);
}
// Consumes the prefix when it is present and returns whether it has been consumed
private consumeOptional(str: string): boolean {
if (this.peekStartsWith(str)) {
this.remaining = this.remaining.substring(str.length);
return true;
}
return false;
}
private captureOptional(str: string): string {
const idx = this.remaining.indexOf(str);
let value: string;
if (idx >= 0) {
value = this.remaining.substr(0, idx);
this.remaining = this.remaining.substr(idx + 1);
} else {
value = this.remaining.substr(0);
this.remaining = '';
}
return value;
}
}
export function deserializeUrl(url: string): ParsedUrl {
return new UrlParser(url).parse();
}
export function serializeUrl(url: ParsedUrl) {
let value = `${PREFIX}/${url.appName}/${url.entryPoint}`;
if (url.params) {
const queryString = Object.keys(url.params).map(key => {
const value = url.params[key];
return encodeURIComponent(key) + '=' + encodeURIComponent(value);
}).join('&');
value = value + '?' + queryString
}
return value;
}
import { UrlParser } from './url-parser';
describe('UrlParser', () => {
it('parses u://appName', () => {
const url = 'u://appName';
const result = new UrlParser(url).parse();
expect(result.url).toBe(url);
expect(result.appName).toBe('appName');
});
it('parses u://appName/entryPoint', () => {
const url = 'u://appName/entryPoint';
const result = new UrlParser(url).parse();
expect(result.url).toBe(url);
expect(result.appName).toBe('appName');
expect(result.entryPoint).toBe('entryPoint');
});
it('parses u://app/entry?a=b', () => {
const url = 'u://app/entry?a=b';
const result = new UrlParser(url).parse();
expect(result.params).toBeTruthy();
expect(result.params.a).toBe('b');
});
it('parses u://app/entry?a=b&x=yz', () => {
const url = 'u://app/entry?a=b&x=yz';
const result = new UrlParser(url).parse();
expect(result.params).toBeTruthy();
expect(result.params.a).toBe('b');
expect(result.params.x).toBe('yz');
});
it('parses u://app/entry?a=b&a=x&a=y', () => {
const url = 'u://app/entry?a=b&a=x&a=y';
const result = new UrlParser(url).parse();
expect(result.params).toBeTruthy();
expect(result.params.a.length).toEqual(3);
expect(result.params.a[0]).toEqual('b');
expect(result.params.a[1]).toEqual('x');
expect(result.params.a[2]).toEqual('y');
});
it('parses u://app/entry?a', () => {
const url = 'u://app/entry?a';
const result = new UrlParser(url).parse();
expect(result.params).toBeTruthy();
expect(result.params.a).toEqual('');
});
it('parses u://app/entry?a&a', () => {
const url = 'u://app/entry?a&a';
const result = new UrlParser(url).parse();
expect(result.params.a.length).toEqual(2);
expect(result.params.a[0]).toEqual('');
expect(result.params.a[1]).toEqual('');
});
});
import { Injectable } from "@angular/core";
import { ParsedUrl } from './url.interfaces';
import { deserializeUrl, serializeUrl } from './url-parser';
@Injectable()
export class UrlSerializer {
public serialize(url: ParsedUrl): string {
return serializeUrl(url);
}
public deserialize(url: string): ParsedUrl {
return deserializeUrl(url);
}
}
export interface ParsedUrl {
url: string;
appName: string;
entryPoint: string;
params?: {
[key: string]: any
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment