Skip to content

Commit cc45349

Browse files
committed
fix: #540 getUrl provides faulty, mangled Data URLs
Removes nativeContent from Scribe/_contentOps Moves base64FromUnicode/unicodeFromBase64 => tools/base64.js Renames tools/html5/urlEncode -> tools/html5/dataURI Fixes Vrapper.test to use proper Media type for testing
1 parent 333c335 commit cc45349

File tree

12 files changed

+98
-88
lines changed

12 files changed

+98
-88
lines changed

packages/engine/Vrapper/Vrapper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ export default class Vrapper extends Cog {
497497

498498

499499
getTypeName (options: any) {
500-
this.requireActive(options);
500+
if (this.isResource()) this.requireActive(options);
501501
return this._typeName;
502502
}
503503

packages/engine/Vrapper/Vrapper.test.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,23 @@ const createAInstance
4444
instancePrototype: "test",
4545
}, });
4646

47+
const createMedia = {
48+
type: "TRANSACTED",
49+
actions: [
50+
created({ id: "theContent", typeName: "Blob" }),
51+
created({ id: "theMedia", typeName: "Media", initialState: {
52+
owner: "child",
53+
name: "mediaName",
54+
content: "theContent",
55+
}, }),
56+
],
57+
};
58+
4759

4860
describe("Vrapper", () => {
4961
let harness: { createds: Object, engine: Object, prophet: Object, testEntities: Object };
5062
const testScriptPartitions = () => harness.createds.TestScriptyThing;
63+
const medias = () => harness.createds.Media;
5164
const entities = () => harness.createds.Entity;
5265

5366
const expectNoVrapper = rawId => { expect(harness.engine.tryVrapper(rawId)).toBeFalsy(); };
@@ -151,9 +164,9 @@ describe("Vrapper", () => {
151164

152165
beforeEach(() => {
153166
harness = createEngineTestHarness({ debug: 0, claimBaseBlock: false }, [
154-
transactionA, createAInstance,
167+
transactionA, createAInstance, createMedia,
155168
]);
156-
testVrapper = testScriptPartitions().child;
169+
testVrapper = medias().theMedia;
157170
});
158171

159172
describe("_getMediaTypeFromTags", () => {

packages/prophet/Oracle/OraclePartitionConnection.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export default class OraclePartitionConnection extends PartitionConnection {
7272
try {
7373
this.warnEvent(1, "\n\tBegun initializing connection with options", initialNarrateOptions,
7474
...dumpObject(this));
75-
const ret = _connect(this, initialNarrateOptions, onConnectData);
75+
const ret = await _connect(this, initialNarrateOptions, onConnectData);
7676
this.warnEvent(1, "\n\tDone initializing connection with options", initialNarrateOptions,
7777
"\n\tinitial narration:", ret);
7878
return ret;

packages/prophet/Oracle/_mediaOps.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export function _requestMediaContents (connection: OraclePartitionConnection,
3737
throw new Error(`OraclePartitionConnection has no authority connection specified ${
3838
""} and could not locate local media URL from Scribe`);
3939
}
40-
return authorityConnection.requestMediaContents([mediaInfo])[0];
40+
return authorityConnection.getMediaURL(mediaInfo);
4141
}
4242
const retrieveMediaContent = connection.getRetrieveMediaContent();
4343
if (!retrieveMediaContent) {

packages/prophet/Scribe/Scribe.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export default class Scribe extends Prophet {
139139
let alreadyWrapped;
140140
try {
141141
if (!bvobInfo) throw new Error(`Cannot find Bvob info '${bvobId}'`);
142-
return _decodeBvobContent(this, bvobInfo, decoder, contextInfo, onError);
142+
return _decodeBvobContent(this, bvobInfo, decoder, contextInfo, onError.bind(this));
143143
} catch (error) { throw onError.call(this, error); }
144144
function onError (error) {
145145
if (alreadyWrapped) return error;

packages/prophet/Scribe/ScribePartitionConnection.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export default class ScribePartitionConnection extends PartitionConnection {
155155
return mediaInfos.map(mediaInfo => {
156156
try {
157157
const mediaEntry = this._getMediaEntry(mediaInfo.mediaId, !!mediaInfo.asURL);
158-
if (mediaInfo.asURL) return _getMediaURL(this, mediaInfo, mediaEntry);
158+
if (mediaInfo.asURL) return _getMediaURL(this, mediaInfo, mediaEntry, onError.bind(this));
159159
let actualInfo = mediaInfo;
160160
if (!actualInfo.bvobId) {
161161
if (!mediaEntry || !mediaEntry.mediaInfo) {
@@ -195,11 +195,27 @@ export default class ScribePartitionConnection extends PartitionConnection {
195195

196196
prepareBvob (content: any, mediaInfo?: MediaInfo):
197197
{ buffer: ArrayBuffer, contentId: string, persistProcess: ?Promise<any> } {
198-
const { buffer, contentId } = bufferAndContentIdFromNative(content, mediaInfo);
199-
return {
200-
content, buffer, contentId,
201-
persistProcess: this._prophet._persistBvobContent(buffer, contentId),
202-
};
198+
try {
199+
const { buffer, contentId } = bufferAndContentIdFromNative(content, mediaInfo);
200+
if (mediaInfo.bvobId && (!mediaInfo.bvobId !== contentId)) {
201+
this.errorEvent(`\n\tINTERNAL ERROR: bvobId mismatch when preparing bvob for Media '${
202+
mediaInfo.name}', CONTENT IS NOT PERSISTED`,
203+
"\n\tactual content id:", contentId,
204+
"\n\trequested bvobId:", mediaInfo.bvobId,
205+
"\n\tmediaInfo:", ...dumpObject(mediaInfo),
206+
"\n\tcontent:", ...dumpObject({ content }),
207+
);
208+
return {};
209+
}
210+
return {
211+
content, buffer, contentId,
212+
persistProcess: this._prophet._persistBvobContent(buffer, contentId),
213+
};
214+
} catch (error) {
215+
throw this.wrapErrorEvent(error, `prepareBvob(${typeof content})`,
216+
"\n\tcontent:", ...dumpObject({ content }),
217+
"\n\tmediaInfo:", ...dumpObject(mediaInfo));
218+
}
203219
}
204220

205221
getMediaInfo (mediaId: VRef) {

packages/prophet/Scribe/_contentOps.js

Lines changed: 23 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { VRef, getRawIdFrom } from "~/raem/ValaaReference";
66
import type { MediaInfo, RetrieveMediaContent } from "~/prophet/api/Prophet";
77

88
import { dumpObject, invariantifyString, thenChainEagerly, vdon } from "~/tools";
9-
import { encodeDataURI } from "~/tools/html5/urlEncode";
9+
import { encodeDataURI } from "~/tools/html5/dataURI";
1010
import type MediaDecoder from "~/tools/MediaDecoder";
1111
import { stringFromUTF8ArrayBuffer } from "~/tools/textEncoding";
1212

@@ -26,7 +26,6 @@ export type MediaEntry = {
2626
mediaInfo: MediaInfo,
2727
isPersisted: boolean,
2828
isInMemory: boolean,
29-
nativeContent?: any,
3029
};
3130

3231
export type MediaLookup = {
@@ -45,7 +44,7 @@ export function _reprocessMedia (connection: ScribePartitionConnection, mediaEve
4544
let newEntry: MediaEntry;
4645
if (currentEntry) {
4746
mediaInfo = { ...currentEntry.mediaInfo };
48-
newEntry = { ...currentEntry, mediaInfo, nativeContent: undefined };
47+
newEntry = { ...currentEntry, mediaInfo };
4948
} else {
5049
if (mediaId.isInherited()) {
5150
mediaInfo = { ...connection._getMediaEntry(mediaId).mediaInfo };
@@ -84,18 +83,13 @@ export function _reprocessMedia (connection: ScribePartitionConnection, mediaEve
8483

8584
const tryRetrieve = async () => {
8685
try {
87-
if (mediaInfo.bvobId && connection._prophet.tryGetCachedBvobContent(mediaInfo.bvobId)) {
88-
if (currentEntry && (currentEntry.mediaInfo.bvobId === mediaInfo.bvobId)) {
89-
// content is in bvob buffer cache with equal bvobId. Reuse.
90-
newEntry.nativeContent = currentEntry.nativeContent;
91-
}
92-
} else if (mediaInfo.bvobId || mediaInfo.sourceURL) {
86+
if (mediaInfo.bvobId && !connection._prophet.tryGetCachedBvobContent(mediaInfo.bvobId)) {
9387
// TODO(iridian): Determine whether media content should be pre-cached or not.
94-
const content = await retrieveMediaContent(mediaId, mediaInfo);
88+
const content = await retrieveMediaContent(mediaId, { ...mediaInfo,
89+
type: "application", subtype: "octet-stream", mime: "application/octet-stream",
90+
});
9591
if (typeof content !== "undefined") {
96-
newEntry.nativeContent = content;
97-
const { persistProcess } = connection.prepareBvob(newEntry.nativeContent, mediaInfo);
98-
await persistProcess;
92+
await connection.prepareBvob(content, mediaInfo).persistProcess;
9993
}
10094
}
10195
// Delays actual media info content update into a finalizer function so that recordTruth
@@ -177,39 +171,30 @@ export function _decodeBvobContent (scribe: Scribe, bvobInfo: BvobInfo,
177171
}
178172
return decodedContent;
179173
},
180-
], onError.bind(scribe));
174+
], onError);
181175
}
182176

183177
export function _getMediaURL (connection: ScribePartitionConnection, mediaInfo: MediaInfo,
184-
mediaEntry: MediaEntry): any {
185-
let nativeContent;
178+
mediaEntry: MediaEntry, onError: Function): any {
186179
// Only use cached in-memory nativeContent if its id matches the requested id.
187-
if ((!mediaInfo || !mediaInfo.bvobId || (mediaInfo.bvobId === mediaEntry.mediaInfo.bvobId))
188-
&& (typeof mediaEntry.nativeContent !== "undefined")) {
189-
nativeContent = mediaEntry.nativeContent;
190-
} else {
191-
const bvobId = (mediaInfo && mediaInfo.bvobId) || mediaEntry.mediaInfo.bvobId;
192-
if (!bvobId) return undefined;
193-
const bufferCandidate = connection._prophet.tryGetCachedBvobContent(bvobId);
194-
nativeContent = bufferCandidate &&
195-
_nativeObjectFromBufferAndMediaInfo(bufferCandidate, mediaInfo || mediaEntry.mediaInfo);
196-
if (bvobId === mediaEntry.mediaInfo.bvobId) {
197-
mediaEntry.nativeContent = nativeContent;
198-
}
180+
const bvobId = (mediaInfo && mediaInfo.bvobId) || mediaEntry.mediaInfo.bvobId;
181+
const bvobInfo = connection._prophet._bvobLookup[bvobId || ""];
182+
// Media's with sourceURL or too large/missing bvobs will be handled by Oracle
183+
if (!bvobInfo || !(bvobInfo.byteLength <= 10000)) return undefined;
199184
}
200-
if ((typeof nativeContent === "string") && nativeContent.length < 10000) {
201-
// TODO(iridian): Is there a use case to create data URI's for json types?
202-
const { type, subtype } = mediaInfo || mediaEntry.mediaInfo;
203-
return encodeDataURI(nativeContent, type, subtype);
204-
}
205-
// TODO(iridian): With systems that support Service Workers we return URL's which the service
206-
// workers recognize and can redirect to 'smooth' IndexedDB accesses, see
185+
// TODO(iridian): With systems that support Service Workers we will eventually return URL's which
186+
// the service workers recognize and can redirect to 'smooth' IndexedDB accesses, see
207187
// https://gist.github.com/inexorabletash/687e7c5914049536f5a3
208188
// ( https://www.google.com/search?q=url+to+indexeddb )
209189
// Otherwise IndexedDB can't be accessed by the web pages directly, but horrible hacks must be
210190
// used like so:
211191
// https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
212-
return undefined;
192+
return thenChainEagerly(connection._prophet.readBvobContent(bvobInfo.bvobId),
193+
(buffer => {
194+
const { type, subtype } = mediaInfo || mediaEntry.mediaInfo;
195+
return encodeDataURI(buffer, type, subtype);
196+
})
197+
, onError);
213198
}
214199

215200
// Returns the requested media content immediately as a native object if it is in in-memory cache.
@@ -218,24 +203,10 @@ export function _getMediaURL (connection: ScribePartitionConnection, mediaInfo:
218203
// Otherwise throws an error.
219204
export function _readMediaContent (connection: ScribePartitionConnection, mediaInfo: MediaInfo,
220205
mediaEntry: MediaEntry, onError: Function): any {
221-
// Only return cached in-memory nativeContent if its id matches the requested id.
222206
const bvobId = mediaInfo.bvobId;
223-
if (mediaEntry && (typeof mediaEntry.nativeContent !== "undefined")
224-
&& (!bvobId || (bvobId === mediaEntry.mediaInfo.bvobId))) {
225-
return mediaEntry.nativeContent;
226-
}
227207
if (!bvobId) return undefined;
228-
return thenChainEagerly(
229-
connection._prophet.readBvobContent(bvobId),
230-
(buffer) => {
231-
if (!buffer) return undefined;
232-
// nativeContent should go in favor of bvobInfo decoded contents
233-
const nativeContent = _nativeObjectFromBufferAndMediaInfo(buffer, mediaInfo);
234-
if (mediaEntry && (bvobId === mediaEntry.mediaInfo.bvobId)) {
235-
mediaEntry.nativeContent = nativeContent;
236-
}
237-
return nativeContent;
238-
},
208+
return thenChainEagerly(connection._prophet.readBvobContent(bvobId),
209+
(buffer) => (!buffer ? undefined : _nativeObjectFromBufferAndMediaInfo(buffer, mediaInfo)),
239210
onError);
240211
}
241212

packages/prophet/Scribe/_databaseOps.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,18 @@ export function _readMediaInfos (connection: ScribePartitionConnection, results:
146146
resolve();
147147
return;
148148
}
149-
const thisMediaInfo = { ...cursor.value, isInMemory: true };
150-
if (thisMediaInfo.mediaInfo && thisMediaInfo.mediaInfo.bvobId
151-
&& thisMediaInfo.isInMemory) {
152-
connection._prophet._addContentInMemoryReference(thisMediaInfo.mediaInfo);
149+
const entry = { ...cursor.value, isInMemory: true };
150+
if (entry.mediaInfo && entry.mediaInfo.bvobId && entry.isInMemory) {
151+
if (!connection._prophet._bvobLookup[entry.mediaInfo.bvobId]) {
152+
connection.errorEvent(`Can't find Media "${entry.mediaInfo.name
153+
}" in-memory Bvob info for ${entry.mediaInfo.bvobId
154+
} when reading partition media infos`);
155+
} else {
156+
connection._prophet._addContentInMemoryReference(entry.mediaInfo);
157+
}
153158
}
154-
results[cursor.key] = thisMediaInfo;
155-
cursor.update(thisMediaInfo);
159+
results[cursor.key] = entry;
160+
cursor.update(entry);
156161
cursor.continue();
157162
};
158163
req.onerror = (evt) => reject(new Error(evt.target.error.message));

packages/prophet/api/PartitionConnection.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ export default class PartitionConnection extends LogEventGenerator {
162162
delete mediaInfo.mime;
163163
delete mediaInfo.type;
164164
delete mediaInfo.subtype;
165-
return this.requestMediaContents([mediaInfo])[0];
165+
return thenChainEagerly(this.requestMediaContents([mediaInfo]), results => results[0]);
166166
}
167167

168168
/**
@@ -205,7 +205,7 @@ export default class PartitionConnection extends LogEventGenerator {
205205
return thenChainEagerly(this.requestMediaContents([mediaInfo]), results => results[0]);
206206
}
207207

208-
requestMediaContents (mediaInfos: MediaInfo[]) {
208+
requestMediaContents (mediaInfos: MediaInfo[]): Promise<(Promise | any)[]> | (Promise | any)[] {
209209
return this._upstreamConnection.requestMediaContents(mediaInfos);
210210
}
211211

packages/tools/base64.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
import { TextEncoder, TextDecoder } from "text-encoding";
44
import base64js from "base64-js";
55

6+
export function base64FromUnicode (unicodeString: string) {
7+
return btoa(encodeURIComponent(unicodeString).replace(/%([0-9A-F]{2})/g,
8+
(match: any, p1: any) => String.fromCharCode(parseInt(p1, 16))
9+
));
10+
}
11+
12+
export function unicodeFromBase64 (base64String: string) {
13+
return decodeURIComponent(atob(base64String).split("").map(
14+
(c) => `%${`${"00"}${c.charCodeAt(0).toString(16)}`.slice(-2)}`
15+
).join(""));
16+
}
17+
618
export function base64FromArrayBuffer (data: ArrayBuffer) {
719
return base64js.fromByteArray(new Uint8Array(data));
820
}

0 commit comments

Comments
 (0)